天天看点

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   },