antd+vue3实现动态表单的自动校验
由于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 },