在表單設計裡,輸入框是最常出現的元件,但它同時也有許多變化,例如文字、數字、日期、多行文字等等。如果每一種情境都獨立寫一份程式碼,不僅會造成重複,更難以維護。這也是為什麼要有 Base Input 元件設計,透過 props 與彈性設置,將各種輸入型態統一管理。
參考文件:https://vuetifyjs.com/en/components/inputs/#usage
<template>
<div class="text-h5-medium mb-3">
{{ props.label }}
<span v-if="props.required" style="color: red;">*</span>
</div>
<!-- 一般輸入框 -->
<v-text-field
v-if="props.type === 'text'"
v-model="inputVal"
variant="outlined"
:placeholder="props.placeholder"
:required="props.required"
:rules="props.rules"
:disabled="props.disabled"
hide-details="auto"
rounded="pill"
></v-text-field>
<!-- 多行輸入框 -->
<v-textarea
v-if="props.type === 'textarea'"
v-model="inputVal"
variant="outlined"
:required="props.required"
:disabled="props.disabled"
rows="4"
rounded="xl"
>
</v-textarea>
</template>
<script setup>
const props = defineProps({
modelValue: { // 父元件傳進來
type: String,
default: '',
required: false,
},
type: { // 種類:text, textarea
type: String,
default: 'text',
required: false,
},
label: { // 欄位名
type: String,
default: '',
required: true
},
placeholder: { // 輸入前的提示文字
type: String,
default: '',
required: false,
},
required: { // 是否必填
type: Boolean,
default: false,
required: false,
},
disabled: { // disabled 狀態
type: Boolean,
default: false,
required: false,
},
rules: { // 有關錯誤提示的規則
type: Array,
default: () => [],
required: false,
}
});
const emit = defineEmits(['update:modelValue']);
const inputVal = computed({
get: () => props.modelValue,
set: (val) => {
return emit('update:modelValue', val);
},
});
</script>
在表單的設計裡,父元件與子元件之間需要保持資料的 雙向同步。以這個案例來說,Contact
(父元件)引用了 CustomInput
(子元件):
contact.vue
中輸入資料,值會先傳遞到 custom-input.vue
。custom-input.vue
再將更新後的值回傳給父元件 contact.vue
。contact.vue
就能取得完整的輸入內容,並呼叫 API 進行送出。這樣的資料流設計,確保了父子元件之間的狀態一致性,同時讓表單邏輯更清晰、可控。
在 Vue 裡,v-model
是一個語法糖(shorthand syntax),用來處理 父子元件之間的雙向綁定。
它其實就是在做:
:model-value="值"
把資料傳給子元件。emit('update:modelValue', 新值)
把資料回傳給父元件。<CustomInput v-model="username" />
其實就是:
<CustomInput
:model-value="username"
@update:model-value="username = $event"
/>
**v-model
= :model-value
+ @update:model-value
,**就是 Vue 幫你省略寫法,讓父子元件雙向同步變得直觀。
接著就把之前所有有用到 v-text-field
的地方,置換成 CustomInput
~
<CustomInput
v-model="state.form.name" // 把輸入的內容寫進state裡
label="姓名" // 欄位名
:placeholder="'請輸入您的姓名'"
:required="true" // 必填
:rules="[v => !!v || '必填']" // 依據規則顯示錯誤提示
/>
另外,在送出按鈕的互動設計中,我希望能加上驗證限制,如果使用者未填寫必填欄位時,點按送出按鈕即會提示必填訊息(如圖),而不觸發 api
請求,這樣的行為溝通時會說是“前端阻擋" XD。若必填都有正確填寫,才會發送 api
,並將填寫內容記錄在state.form
,作為 payload 送給後端。
可以用 formRef
搭配 <v-form>
做驗證,當使用者按下送出按鈕時,會先呼叫 formRef.value.validate()
,若驗證未通過則直接 return
,確保表單完整,程式碼如下:
<template>
<v-form ref="formRef">
....表單欄位內容
<v-form>
</template>
<script setup>
const formRef = ref(null);
const send = async () => {
const { valid } = await formRef.value.validate()
if (!valid) return;
state.dialog = false;
console.log(state.form);
console.log('send');
}
</scrpipt>
累死,今天就到這!