amis框架FormItem表单项校验优化代码示例
前言在 amis 低代码表单开发中校验逻辑的编排往往比想象中更复杂。本文聚焦两个最常困扰开发者的核心问题一是点击「重置」按钮时如何让日期范围等组件主动触发校验避免重置后校验状态残留二是当「保存」和「提交」共用一个 form 组件的 api 参数时如何让保存操作绕过必填等输入框校验、不被阻塞同时保证提交操作仍执行完整校验。围绕这两个问题下文给出多种可落地的解决方案并总结不同校验方式的适用场景与最佳实践。一、FormItem表单项自定义校验amis表达式地址amis - 低代码前端框架-表达式https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/expression#startofhttps://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/expression#startofamis组件地址amis - 低代码前端框架-自定义表单项校验https://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E4%BF%A1%E6%81%AFhttps://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E4%BF%A1%E6%81%AF搜索“自定义校验信息”注意表单项值发生变化即校验请配置validateOnChange: true支持的格式校验isDateTimeSameOrBefore日期早于目标日期或和目标日期相同支持指定粒度默认到毫秒millisecondisDateTimeSameOrAfter日期晚于目标日期或和目标日期相同支持指定粒度默认到毫秒millisecond......代码示例显示效果日期选择器会限制选择日期不能早于当月1号同学日期不能早于xxxx-xx-xx不能晚于当月最后一天(同学日期不能晚于xxxx-xx-xx){ type: page, body: { type: form, api: /amis/api/mock2/form/saveForm, body: [ { type: input-date, label: 日期选择, validateOnChange: true, name: date1, format: YYYY-MM-DD, validations: { isDateTimeSameOrAfter: ${DATETOSTR(STARTOF(NOW(),month),YYYY-MM-DD)}, isDateTimeSameOrBefore: ${DATETOSTR(ENDOF(NOW(),month),YYYY-MM-DD)} }, validationErrors: { isDateTimeSameOrAfter: 同学日期不能早于$1, isDateTimeSameOrBefore: 同学日期不能晚于$1 }, description: 请选择日期 } ] } }二、form自定义校验amis组件地址amis - 低代码前端框架-表单校验https://aisuda.bce.baidu.com/amis/zh-CN/components/form/index#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8Chttps://aisuda.bce.baidu.com/amis/zh-CN/components/form/index#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8Cform自定义校验数字输入框控件formitem方式代码示例{ type: page, body: { type: form, api: /xxx, debug: true, id: form-1, actions: [ { type: button, label: 校验最小值和最大值, onEvent: { click: { actions: [ { actionType: validate, componentId: form-1 } ] } } } ], body: [ { id: aaa, name: a, type: input-number, label: 最小值, width: 180, clearable: true, precision: 4, validations: { maximum: ${b999999999999.9999?b:999999999999.9999}, minimum: 0 }, remark: 请输入0~999999999999.9999的数字,最小值不能高于最大值, validateOnChange: true, validationErrors: { maximum: 请输入0~999999999999.9999的数字,最小值不能高于最大值, minimum: 数值至少0 } }, { id: bbb, name: b, type: input-number, label: 最大值, width: 180, clearable: true, precision: 4, validations: { minimum: ${a 0 ? 0 : a}, maximum: ${999999999999.9999} }, remark: 请输入0~999999999999.9999的数字,最大值不能低于最小值, validateOnChange: true, validationErrors: { maximum: 请输入0~999999999999.9999的数字,最大值不能低于最小值, minimum: 数值至少$1 } } ] } }form自定义校验日期控件formitem方式代码示例{ type: page, body: { type: form, api: /amis/api/form/saveForm, body: [ { type: input-date, id: u:ab95103ba6f1, name: effectiveDate, label: 生效日期, placeholder: -, validations: { isRequired: true, isDateTimeBefore: ${endDate} }, valueFormat: YYYY-MM-DD, validateOnChange: true, validationErrors: { isRequired: 请选择生效日期 }, format: YYYY-MM-DD }, { type: input-date, id: u:e6de4a6ebca8, name: endDate, label: 失效日期, placeholder: -, validations: { isRequired: true, isDateTimeAfter: ${effectiveDate} }, valueFormat: YYYY-MM-DD, validateOnChange: true, validationErrors: { isRequired: 请选择失效日期 }, format: YYYY-MM-DD } ] } }form自定义校验日期控件form规则方式代码示例{ type: form, debug: true, id: form-1, body: [ { id: u:be42bf73aa0d, body: [ { id: u:227b2cf26b1b, name: params.date_range, type: combo, items: [ { id: u:d72067beda27, body: [ { id: u:ed3239592c13, name: value, type: input-date-range, format: YYYY-MM-DD HH:mm:ss, shortcuts: [ 7daysago, 2weeksago, 1weekslater, thismonth, prevmonth ] } ], type: container, style: { display: flex, flex-direction: column } } ], label: 请选择日期, className: mt-4, columnRatio: 4 }, { id: u:4c618510f9a0, type: button-toolbar, label: , buttons: [ { id: u:dec11d54212e, type: submit, label: 查询, level: primary, target: form-1 } ], className: mt-4, labelWidth: 10 } ], type: group } ], mode: horizontal, rules: [ { rule: ${DATETOSTR(SPLIT(params.date_range.value,,)[1],YYYYMMDD)-DATETOSTR(ENDOF(NOW(),month),YYYYMMDD)0}, message: 结束日期选择错误 }, { rule: ${DATETOSTR(SPLIT(params.date_range.value,,)[0],YYYYMMDD)-DATETOSTR(STARTOF(NOW(),month),YYYYMMDD)0}, message: 开始日期选择错误 } ], title: , actions: [], panelClassName: mb-6 shadow-none, canAccessSuperData: false }form和formitem自定义校验结合使用代码示例{ type: form, debug: true, id: form-1, body: [ { id: u:be42bf73aa0d, body: [ { id: u:227b2cf26b1b, name: params.date_range, type: combo, items: [ { id: u:d72067beda27, body: [ { id: u:ed3239592c13, name: value, type: input-date-range, format: YYYY-MM-DD HH:mm:ss, shortcuts: [ 7daysago, 2weeksago, 1weekslater, thismonth, prevmonth ] }, { type: input-date, label: 开始日期:, value: ${SPLIT(value,,)[0]?DATETOSTR(SPLIT(value,,)[0],YYYYMMDD):}, validateOnChange: true, name: start-date, format: YYYY-MM-DD, strictMode: false, static: true, style: { margin: 0, padding: 0 }, validations: { isDateTimeSameOrAfter: ${DATETOSTR(STARTOF(NOW(),month),YYYY-MM-DD)} }, validationErrors: { isDateTimeSameOrAfter: 同学开始日期不能早于$1 }, disabled: true }, { type: input-date, label: 结束日期:, static: true, value: ${SPLIT(value,,)[1]?DATETOSTR(SPLIT(value,,)[1],YYYYMMDD):}, validateOnChange: true, name: end-date, format: YYYY-MM-DD, strictMode: false, style: { margin: 0, padding: 0 }, validations: { isDateTimeSameOrBefore: ${DATETOSTR(ENDOF(NOW(),month),YYYY-MM-DD)} }, validationErrors: { isDateTimeSameOrBefore: 同学结束日期日期不能晚于$1 }, disabled: true }, { type: tpl, tpl: br/ } ], type: container, style: { display: flex, flex-direction: column } } ], label: 请选择日期, className: mt-4, columnRatio: 4 }, { id: u:4c618510f9a0, type: button-toolbar, label: , buttons: [ { id: u:dec11d54212e, type: submit, label: 查询, level: primary, target: form-1 } ], className: mt-4, labelWidth: 10 } ], type: group } ], mode: horizontal, rules: [ { rule: ${!!params.date_range.value}, message: 开始日期、结束日期不能为空 }, { rule: ${DATETOSTR(SPLIT(params.date_range.value,,)[1],YYYYMMDD)-DATETOSTR(ENDOF(NOW(),month),YYYYMMDD)0}, message: 结束日期选择错误 }, { rule: ${DATETOSTR(SPLIT(params.date_range.value,,)[0],YYYYMMDD)-DATETOSTR(STARTOF(NOW(),month),YYYYMMDD)0}, message: 开始日期选择错误 } ], title: , actions: [], panelClassName: mb-6 shadow-none, canAccessSuperData: false }三、服务端校验1、整个表单提交校验表单提交校验https://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%A0%A1%E9%AA%8Chttps://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%A0%A1%E9%AA%8C代码示例{ type: page, body: { type: form, api: /api/xxx, body: [ { name: test1, type: input-text, label: 服务端验证1 } ] } }xxx接口返回结果{ status: 422, msg: , errors: { test1: 服务器端说这个有错误。 }, data: null }2、单个表单项校验表单项校验https://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E9%80%9A%E8%BF%87%E8%A1%A8%E5%8D%95%E9%A1%B9%E6%A0%A1%E9%AA%8C%E6%8E%A5%E5%8F%A3https://aisuda.bce.baidu.com/amis/zh-CN/components/form/formitem#%E9%80%9A%E8%BF%87%E8%A1%A8%E5%8D%95%E9%A1%B9%E6%A0%A1%E9%AA%8C%E6%8E%A5%E5%8F%A3代码示例1{ type: page, body: { type: form, mode: horizontal, api: /api/save, body: [ { label: name, type: input-text, name: name, required: true, validateApi: /api/xxx } ] } }xxx接口返回结果{ status: 422, msg: , errors: 当前用户已存在, data: null }如果表单项接口是类似详情接口需要和当前输入控件值进行比较校验需要将报错结果封装成上述xxx接口返回结果 {status: 422,msg: ,errors: xxx,data: null}并且由于validateApi和 validations、validationErrors不能一起使用可以利用requestAdaptor的mockResponse实现一些不用接口也能校验的功能拦截请求https://aisuda.bce.baidu.com/amis/zh-CN/docs/types/api#%E6%8B%A6%E6%88%AA%E8%AF%B7%E6%B1%82https://aisuda.bce.baidu.com/amis/zh-CN/docs/types/api#%E6%8B%A6%E6%88%AA%E8%AF%B7%E6%B1%82则代码示例{ type: input-number, id: u:xxx, max: 9999999999, min: 0, mode: inline, name: myAmount, step: 1, value: 0, keyboard: true, clearable: true, precision: 2, strictMode: false, placeholder: -, validateApi: { url: /api/xxx, data: { myAmount: ${myAmount}, isSubmit: ${isSubmit} }, method: get, adaptor: \n const {myAmount,isSubmit}context;\n\n const {amount1,amount2}payload.datapayload.data.ret||{};if(payload.isSubmitFlgN){return payload};if(isSubmit myAmount-00){payload.errorsmyAmount0?金额必须大于0:!myAmount?金额不能为空:;};\n\n if(amount1 0 myAmount-0 amount1){payload.errors金额必须小于等于${amount1};} else if(amount2 0 myAmount-0 amount2){payload.errors金额必须小于等于${amount2};};\n\n payload.statuspayload.errors?422:payload.status;\n\n \n\n return payload, headers: { a: ${a} }, requestAdaptor: const {url} api;const {myAmount,isSubmit}api.query;\n\n\n let query{};api.urlurl.split(?)[0]queryStr;\n\n const errMsg!isSubmit?:(myAmount0?金额必须大于0:!myAmount?金额不能为空:);const obj{status:errMsg?422:0,errors:errMsg,isSubmitFlg:isSubmit?Y:N};const mockResponse{status:200,data:obj};\n\n return myAmount0isSubmit?api:{...api,mockResponse}; }, validateOnChange: true, canAccessSuperData: true }四、解决submit查询按钮在校验失败后点击重置按钮再点击查询还是报错的问题1、clearError6.11.1 及以上版本 清除表单校验产生的错误状态2、低版本推荐在点击重置按钮时针对校验目标setValue通过setValue更新指定表单的数据主动触发日期范围组件的校验效果代码示例{ type: form, initApi: /xxx, id: form-1, body: [ { id: u:02b1fa2eb7bd, mode: normal, name: conditions, type: combo, items: [ { id: u:8055f4f39a93, body: [ { id: u:e8295a081bde, name: date_range, type: combo, items: [ { id: effect-date-range, name: value, size: full, type: input-date-range, label: 日期范围, value: ${DATETOSTR(DATEMODIFY(NOW(), -30, days), YYYY-MM-DD) , DATETOSTR(NOW(), YYYY-MM-DD)}, clearable: true, valueFormat: YYYY-MM-DD, validateOnChange: true } ], className: mt-4, strictMode: true, syncFields: [], columnRatio: 4 } ], mode: normal, type: group } ], label: false, className: mb-4, strictMode: true, syncFields: [] }, { id: u:7b60ac5b3768, body: [ { id: u:box, type: flex, items: [ { id: u:submit-1, type: submit, label: 查询, level: primary, target: form-1, themeCss: { className: { width:default: 80px } } }, { id: u:reset-1, type: reset, label: 重置, target: search-form, onEvent: { click: { actions: [ { value: ${DATETOSTR(DATEMODIFY(NOW(), -30, days), YYYY-MM-DD) , DATETOSTR(NOW(), YYYY-MM-DD)}, actionType: setValue, componentId: effect-date-range } ] } }, themeCss: { className: { width:default: 80px } } } ], justify: center, className: mt-2, alignItems: center } ], mode: normal, type: group } ], data: { conditions: { date_range: { value: ${DATETOSTR(DATEMODIFY(NOW(), -30, days), YYYY-MM-DD) , DATETOSTR(NOW(), YYYY-MM-DD)}, column: date_range, queryType: interval } } }, feat: Insert, rules: [ { rule: ${DAYS(DATERANGESPLIT(conditions.date_range.value,1,YYYY-MM-DD),DATERANGESPLIT(conditions.date_range.value,0,YYYY-MM-DD))365}, message: 日期范围请选择在一年内 }, { rule: ${!!conditions.date_range.value}, message: 请选择生效日期 } ], title: , actions: [], panelClassName: mb-6 shadow-none, canAccessSuperData: false }五、保存接口和提交接口都在一个form组件的api参数中处理保存却因为输入框校验问题阻塞调用保存接口的解决方案1、使用actionType: ajax优点可以直接在button组件上使用、和提交接口分开2、使用service组件点击保存按钮的时候强制reload这个组件优点1可以在form组件中使用可以直接复制form的api配置不过接口要根据service组件的属性专门修改比如设置fetchinited为false修改fetchinited事件initFetchboolean布尔值是否默认拉取fetchinited事件对象api 接口请求完成。2可以和提交接口分开也可以一起(比如提交按钮可以先使用validate排除掉所有的错误校验信息后再reload这个service)参考资料amis - 低代码前端框架-fetchinitedhttps://aisuda.bce.baidu.com/amis/zh-CN/components/service#fetchinitedhttps://aisuda.bce.baidu.com/amis/zh-CN/components/service#fetchinited3、给每个输入控件的requiredOn或者isRequired参数加一个变量isSubmittrue时正常校验false时不校验问题如果使用了validationErrors参数isSubmit控制不起作用会受到minimummaximum之类的校验影响如果要求就是保存需要保留这种自定义校验的话适合这种方案。解决步骤1form的message对象设置validateFailed为字符串以免全局错误信息影响因为保存理想效果要屏蔽必填校验只校验自定义校验信息messagesObject消息提示覆写默认消息读取的是 API 返回的消息但是在此可以覆写它。messages.fetchSuccessstring获取成功时提示messages.fetchFailedstring获取失败时提示messages.saveSuccessstring保存成功时提示messages.saveFailedstring保存失败时提示messages.validateFailedstring依赖的部分字段没有通过验证表单验证失败时的提示{ type: form, api: {...略}, messages: { validateFailed: }, onEvent: { submitFail: { actions: [ { args: { value: { isSubmit: true } }, actionType: setValue, componentId: page-1 }, { args: { value: { isSubmit: true } }, actionType: setValue, componentId: form-1 } ] }, submitSucc: { actions: [ { args: { link: ./xxx1 }, actionType: link, expression: !this.isSubmit }, { args: { link: ../xxx2 }, actionType: link, expression: this.isSubmit }, { args: { value: { isSubmit: true } }, actionType: setValue, componentId: page-1 }, { args: { value: { orderCode: ${event.data.result.data.ret.orderCode}, idQuoteOpen: ${event.data.result.data.ret.id} } }, actionType: setValue, componentId: form-1 } ] }, validateError: { actions: [ { args: { value: { isSubmit: true } }, actionType: setValue, componentId: page-1 }, { args: { value: { isSubmit: true } }, actionType: setValue, componentId: form-1 } ] } }, 其他配置略 }2当isSubmit设置为false之后使用form的validate事件触发校验规则然后筛选需要的校验信息弹窗提示出来3然后触发submit事件参考资料amis - 低代码前端框架-获取组件间数据https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/event-action#%E8%8E%B7%E5%8F%96%E7%BB%84%E4%BB%B6%E7%9B%B8%E5%85%B3%E6%95%B0%E6%8D%AEhttps://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/event-action#%E8%8E%B7%E5%8F%96%E7%BB%84%E4%BB%B6%E7%9B%B8%E5%85%B3%E6%95%B0%E6%8D%AEGETRENDERERDATA可以获取数据常用于保存或者提交按钮获取form组件的数据或者接口获取相应数据。GETRENDERERPROP(id, path)分别获取指定组件的数据和属性但无法根据name拿到输入控件的label属性的值除非所有的id都设置得有规律可循否则不适用于多个输入控件都有校验的场景但可以使用css伪类选择器:has找到输入控件的父元素再向下查找输入控件的标题。参考文档伪类 - CSS层叠样式表 | MDNCSS 伪类是添加到选择器的关键字用于指定所选元素的特定状态。例如:hover 伪类可用于在用户指针悬停在按钮上时选择该按钮然后对选定的按钮进行样式设计。https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference/Selectors/Pseudo-classeshttps://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference/Selectors/Pseudo-classes:has() - CSS层叠样式表 | MDNCSS 函数式伪类 :has() 表示一个元素如果作为参数传递的任何相对选择器在锚定到该元素时至少匹配一个元素。这个伪类通过把可容错相对选择器列表作为参数提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法。https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference/Selectors/:hashttps://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference/Selectors/:has保存按钮代码示例{ type: button, id: saveBtn, label: 保存, level: primary, onEvent: { click: { actions: [ { args: { msg: 请选择公司, msgType: warning }, actionType: toast, expression: ${!(GETRENDERERDATA(\form-1\, \companyName\).label||GETRENDERERDATA(\form-1\, \companyName\).length0)} }, { actionType: custom, expression: ${!!(GETRENDERERDATA(\form-1\, \companyName\).label||GETRENDERERDATA(\form-1\, \companyName\).length0)}, script: \ndoAction({\n componentId: \form-1\,\n actionType: \setValue\,\n args: {\n value: {\n isSubmit: false\n }\n }\n});\n\ndoAction({\n componentId: \page-1\,\n actionType: \setValue\,\n args: {\n value: {\n isSubmit: false\n }\n }\n});\n\n\n }, { args: { timeout: 300 }, actionType: wait }, { actionType: validate, expression: ${!!(GETRENDERERDATA(\form-1\, \companyName\).label||GETRENDERERDATA(\form-1\, \companyName\).length0)}, componentId: form-1 }, { args: {}, actionType: custom, script: function transData(params) {\r\n return Object.entries(params.errors);\r\n};\r\nconst arr transData(event.data.validateResult);\r\nconst lastIdx arr.length - 1\r\nconst errName arr[lastIdx][0];\r\nconst errMsg arr[lastIdx].flatMap(ee ee).slice(1).join(,);\r\nconst errDom document.querySelector(.cxd-Form-item:has(input[name\${errName}\]) label .cxd-TplField);\r\nif (errMsg.includes(必填)) {\r\n return;\r\n}\r\n\r\nif (errDom) {\r\n const errLabel errDom.innerText;\r\n doAction({\r\n actionType: toast,\r\n args: {\r\n msg: ${errLabel}: ${errMsg},\r\n msgType: warning,\r\n }\r\n });\r\n}\r\n }, { actionType: custom, script: \ndoAction({\n componentId: \form-1\,\n actionType: \setValue\,\n args: {\n value: {\n isSubmit: false\n }\n }\n});\n\ndoAction({\n componentId: \page-1\,\n actionType: \setValue\,\n args: {\n value: {\n isSubmit: false\n }\n }\n});\n\nwindow.setTimeoutDebounce(() {\n doAction({\n actionType: submit,\n componentId: form-1,\n ignoreError: true,\n });\n}, 800)()\n, args: {}, expression: ${!!(GETRENDERERDATA(\form-1\, \companyName\).label||GETRENDERERDATA(\form-1\, \companyName\).length0)} } ], debounce: { wait: 700 } } }, visibleOn: ${!isEditStatus}, disabledOn: ${!isSubmit} }提交按钮代码示例{ type: button, id: submitBtn, label: 提交, level: primary, onEvent: { click: { actions: [ { actionType: validate, componentId: form-1 }, { actionType: toast, args: { msg: 请核查所有必填项是否均已填写/上传, msgType: warning }, expression: ${event.data.validateResult.errors} }, { actionType: submit, componentId: form-1 } ], debounce: { wait: 700 } } }, debounce: { wait: 700 }, visibleOn: ${!isEditStatus} }4、把每个输入控件的validations换成validateApi在requestAdaptor使用mockResponse拦截并自定义错误信息requestAdaptor配置const { isSubmit, minimum, maximum, } api.data; const { xxxAmount , } context; let errMsg ; if (xxxAmount minimum) { errMsg 金额不能小于${minimum}; } if (xxxAmount maximum) { errMsg 金额不能大于${maximum}; } if (!isSubmit) { errMsg ; } return { mockResponse: { status: 200, data: { // http 返回结果 status: errMsg ? 422 : 200, errors: errMsg, } } };代码示例{ type: input-number, id: xxxAmount, name: xxxAmount, step: 0, label: xxx金额, keyboard: true, clearable: true, precision: 2, requiredOn: ${isSubmit}, columnRatio: 4, kilobitSeparator: true, validateApi: { url: /xxx, method: post, data: { isSubmit: ${GETRENDERERDATA(form-1,isSubmit)}, maximum: ${GETRENDERERDATA(form-1,amount_2)}, minimum: 0 }, requestAdaptor: const {\r\n isSubmit,\r\n minimum,\r\n maximum,\r\n} api.data;\r\nconst {\r\n xxxAmount,\r\n} context;\r\n \r\nlet errMsg ;\r\nif (xxxAmount minimum) {\r\n errMsg 金额不能小于${minimum};\r\n}\r\nif (xxxAmount maximum) {\r\n errMsg 金额不能大于于${maximum};\r\n}\r\nif (!isSubmit) {\r\n errMsg ;\r\n}\r\n\r\nreturn {\r\n // 模拟 http 请求返回\r\n mockResponse: {\r\n status: 200, // http 返回状态\r\n data: {\r\n // http 返回结果\r\n status: errMsg ? 422 : 200, // amis 返回数据的状态\r\n errors: errMsg\r\n }\r\n }\r\n};, adaptor: , messages: {} } }六、实战场景与最佳实践总结在 amis 表单开发中FormItem 校验、Form 校验和服务端校验各有其适用场景与优劣势。合理组合使用才能在保证用户体验的同时守住数据安全底线。下面从适用场景、优缺点对比和选择建议三个维度进行总结。1、三种校验方式的适用场景校验方式适用场景FormItem 校验单个字段的格式、范围、必填等基础校验如邮箱、URL、数字范围、日期范围、正则匹配等需要输入时即时反馈配合validateOnChange: true。Form 校验跨字段的联动校验如开始日期不能晚于结束日期、最小值不能大于最大值、组合字段整体非空等需要基于多个字段值综合判断的业务规则。服务端校验唯一性校验如用户名、手机号是否已存在、权限校验、依赖数据库或外部系统的业务规则校验以及所有不能在前端暴露逻辑的安全敏感校验。2、优缺点对比校验方式优点缺点FormItem 校验配置简单、即时反馈、体验好内置大量常用规则isEmail、isNumeric、isDateTimeSameOrAfter等错误信息可精确到单个控件。只能校验单个字段自身无法处理跨字段联动逻辑在前端可被绕过。Form 校验适合跨字段联动规则通过rules表达式统一管理逻辑集中可与 FormItem 校验结合使用。表达式复杂时不易维护同样只在前端生效不能替代服务端安全校验。服务端校验数据安全可靠无法被绕过可访问数据库、外部系统等资源是最终的数据正确性保障。需要网络请求反馈有延迟需要额外开发接口无法做到输入时的即时提示。3、选择建议优先用 FormItem 校验处理单字段基础规则凡是只依赖当前字段自身就能判断的格式、范围、必填校验都应放在 FormItem 的validations中并开启validateOnChange让用户输入时立即得到反馈。跨字段联动用 Form 的rules规则当校验需要同时参考多个字段如日期区间、大小值比较时使用 Form 级rules表达式统一处理避免在多个 FormItem 中重复编写相互依赖的逻辑。唯一性、权限等安全敏感校验必须结合服务端前端校验只能提升体验不能作为安全边界。涉及唯一性、权限、数据一致性等关键规则必须通过validateApi或提交接口在服务端二次校验前端校验仅作为辅助提示。总结前端校验FormItem Form负责“体验”服务端校验负责“安全”。实际项目中应以前端校验为主、服务端校验兜底两者结合才能构建既流畅又可靠的表单系统。