天天看點

antd+vue3實作動态表單的自動校驗

由于vue3用的人還不多,是以有些問題部落客踩了坑隻能自己爬出來了,特此做個記錄。如有錯誤,請大家指正。

  回歸正題,我所做的業務是,動态添加表單項,對每一項單獨做校驗,效果如下:

  

antd+vue3實作動态表單的自動校驗
  主要代碼如下:

1 <a-form
 2     name="custom-validation"
 3     ref="formRef"
 4     :model="modelRef"
 5     :rules="rules"
 6     v-bind="layout"
 7     @finish="handleFinish"
 8     @finishFailed="handleFinishFailed"
 9   >
10     <div class="card-box">
11       <div class="card-head">基礎資訊</div>
12       <div class="card-body">
13         <a-form-item label="食材名稱" name="name">
14           <a-input v-model:value="modelRef.name" autocomplete="off" />
15         </a-form-item>
16         <a-form-item label="食材編号" name="foodNumber">
17           <a-input v-model:value="modelRef.foodNumber" autocomplete="off" />
18         </a-form-item>
19         <a-form-item label="食材類型" name="foodType">
20           <a-select v-model:value="modelRef.foodType" placeholder="">
21             <a-select-option value="shanghai">Zone one</a-select-option>
22             <a-select-option value="beijing">Zone two</a-select-option>
23           </a-select>
24         </a-form-item>
25         <a-form-item label="食材産地" name="birthplace">
26           <a-input v-model:value="modelRef.birthplace" autocomplete="off" />
27         </a-form-item>
28       </div>
29     </div>
30     <div class="card-box">
31       <div class="card-head">營養成分資訊</div>
32       <div class="card-body">
33         <a-button primary shape="round" @click="onAdd">
34           <PlusSquareOutlined /> 新增
35         </a-button>
36         <div class="nutrients-content-box">
37           <a-row type="flex" justify="space-between" align="middle">
38             <a-col :span="5" v-for="(item,index) in modelRef.nutrients" :key="index">
39               <div class="nutrients-input-box card-box">
40                 <div>{{item.name}}</div>
41                 <div class="flex-align-end">
42                   <div>      
注:form.item的name必須與modelRef裡面的字段保持一緻,否則無法實作自動校驗,是以此處name使用動态資料,      
當數組nutrients值改變時,就往modelRef裡面加字段(與這裡的name保持一緻)。下面代碼有說明
43                     <a-form-item :name="item.id+'nutrients'"      
當name設定成功了,此處的規則便會在change觸發後執行
44                                  :rules="[{validator: validateNutrients, trigger: 'change'}]">
45                       <a-input v-model:value="item.value" @change="onFieldChange(item)"/>
46                     </a-form-item>
47                   </div>
48                   <span>{{item.unit}}</span>
49                 </div>
50               </div>
51             </a-col>
52           </a-row>
53         </div>
54       </div>
55     </div>
56     <div class="op-btn-box">
57       <a-form-item :wrapper-col="{ span: 12, offset: 18 }">
58         <a-button>取消</a-button>
59         <a-button type="primary" style="margin-left: 10px" html-type="submit">儲存</a-button>
60       </a-form-item>
61     </div>
62   </a-form>      
1 import { onMounted, reactive, toRefs, watch } from 'vue'
 2 setup() {      
//表單校驗裡的name值必須與此處的字段保持一緻
 3     const modelRef = reactive({
 4       name: '',
 5       foodNumber: '',
 6       foodType: null,
 7       birthplace: '',
 8       nutrients: [],
 9     })
10     const layout = {
11       labelCol: { span: 2 },
12       wrapperCol: { span: 6 },
13     }      
//此處為動态表單的自定義規則
14     const validateNutrients = async (rule, value) => {
15       if (!value) {
16         return Promise.reject(new Error('請輸入數值'))
17       }
18       const numReg = /^(?!0+(?:\.0+)?$)(?:[1-9]\d*|0)(?:\.\d{1,2})?$/
19       if (!numReg.exec(value)) {
20         return Promise.reject(new Error('請輸入正确數字'))
21       }
22     }
23      
//其他普通的校驗,可做統一處理
24     const rules = {
25       name: [
26         { required: true, message: '請輸入食材名稱', trigger: 'change' },
27         { max: 20, message: '最多輸入20字', trigger: 'change' },
28       ],
29       foodNumber: [
30         { required: true, message: '請輸入食材編号', trigger: 'change' },
31         { max: 20, message: '最多輸入20字', trigger: 'change' },
32       ],
33       foodType: [
34         { required: true, message: '請選擇食材類型', trigger: 'change' },
35       ],
36       birthplace: [
37         { required: true, message: '請輸入食材産地', trigger: 'change' },
38         { max: 20, message: '最多輸入20字', trigger: 'change' },
39       ],
40     }
41      
//此處是關鍵,modelRef.nutrients是周遊動态表單所用的數組,當數組值改變時,往modelRef裡面加字段,與上面的動态循環出來的form.item的name保持一緻
42     watch(
43       () => modelRef.nutrients,
44       val => {
45         if (val.length) {
46           val.forEach(item => {
47             modelRef[`${item.id}nutrients`] = item.value
48           })
49         }
50       },
51     )
52     /* 送出儲存 */
53     const handleFinish = (values) => {
54       console.log(values)
55     }
56     const handleFinishFailed = (errors) => {
57       console.log(errors)
58     }      
//輸入框的值改變時,需要更新modelRef裡動态添加的字段的值,否則校驗會出錯。
59     const onFieldChange = (item) => {
60       modelRef[`${item.id}nutrients`] = item.value
61     }
62 
63     return {
64       ...toRefs(state),
65       modelRef,
66       rules,
67       layout,
68       handleFinish,
69       handleFinishFailed,
70       validateNutrients,
71       onFieldChange,
72     }
73   },