表单校验可以改善用户体验和减轻服务器的压力, 而动态配置表单校验能极大的提高动态表单的扩展性、灵活性, 满足多样性、差异化需求
目标
👌,首先我们简要说下要实现的目标功能:
- 具有基础的表单验证功能
- 提供一些内置验证规则
- 提供对外开放的能力
这样就能直接通过配置的形式无代码来表单校验
背景
开源库 vue-form-design基于 Vue3 的可视化表单设计器,拖拽式操作让你快速构建一个表单, 让表单开发简单而高效。
低代码表单设计器, 可以通过拖拽的形式生成 n 种不同类型的表单, 这样的表单如何校验数据的准确性?
就需要用到
element-plus
提供的表单校验相关规则, 并且该规则是一个对象, 那我们是不是可以配置
json
来进行校验?
在开发表单设计器初期, 确实是通过 json 配置的形式进行表单校验, 但是这种方法有几大缺陷
- 局限于 element-plus 提供出来的校验方式, 如必填, 数字校验, 长度校验等
- 保存动态表单配置时会转成 json 字符串, 如果使用 element-ui 自定义校验函数时,会转换成空 (就算能使用函数的 toString 方法转成字符串, 但校验函数的代码提示不好, 不能自定义参数等)
- 入门门槛高, 需了解 element-plus 校验方式, 需要一定的基础
- 流程复杂, 不能通过下拉框枚举出常见校验直接使用
- 动态表单, 顾名思义, 可以通过表单配置的形式来校验输入的准确性, 但
json
配置的方式不支持
从以上几大缺陷可知, 使用
json
校验方式的扩展性, 可维护性, 便捷性, 灵活性都没有, 所以才需要重新设计动态表单的校验方式
技术
组件库使用的是
element-plus
, 所以相关表单校验的设计要符合
element-plus
提供的规则
如以下规则:
{"rules":{"name":[{"required":true,"message":"请输入活动名称","trigger":"blur"},{"min":3,"max":5,"message":"长度在 3 到 5 个字符","trigger":"blur"}],"region":[{"required":true,"message":"请选择活动区域","trigger":"change"}]}}
自定义校验函数规则:
varvalidatePass=(rule, value, callback)=>{if(value ===""){callback(newError("请输入密码"));}else{if(this.ruleForm.checkPass !==""){this.$refs.ruleForm.validateField("checkPass");}callback();}};
为了提升扩展性, 可以通过编写自定义校验函数的形式进行校验, 涉及到代码开发, 所以需要一个可提示、可编写注释的开源编辑器
codemirror
CodeMirror
是基于 js 的源代码编辑器组件,它支持 javascript 等多种高级语言,tampermonkey 内置的代码编辑器就是基于它。它的按键组合方式兼容 vim,emacs 等,调用者还可自定义’自动完成’的列表窗口,自由度极高,相当成熟。
使用
import{ basicSetup }from"codemirror";import VueCodemirror from"vue-codemirror";import{ javascript }from"@codemirror/lang-javascript";// 全局注册
app.use(VueCodemirror,{autofocus:true,disabled:false,indentWithTab:true,tabSize:2,placeholder:"Code goes here...",extensions:[basicSetup,javascript()],});
以上是可以编写 javascript 相关代码, 如果我们要编写 css, json 呢? 正好在该库也使用到
import{ json }from"@codemirror/lang-json";exportdefault{setup(){const extensions =[json()];return{
extensions,};},};
template
<codemirrorv-model="code"placeholder="Code goes here..."mode="text/json":style="{ height: '400px' }":extensions="extensions":autofocus="true":indent-with-tab="true":tab-size="2"/>
通过以上代码, 就可以在项目中使用
codemirror
来编写 javascript 代码
架构设计
通过以上两种技术, 我相信大家都知道 element-plus 两种校验方式
那如何解决几大缺陷?
我是如何做的? 以下三种方式进行解决
枚举校验
把常见的、已知的校验规则枚举出来, 直接通过文件引用的方式遍历出校验列表进行选择(最好是维护到后台里面, 通过接口获取, 这样方便增删改查)
如:
// 数字校验规则const validateNumber =`(rule, value, callback) => {
console.log(rule);
if (value === "" || value == null) {
callback(new Error("请输入"));
} else if (!/^[0-9]*$/.test(value)) {
callback(new Error("必须为数字"));
}
callback();
}`;// 数字校验规则(小数点保留两位)const validateNumberD2 =`(rule, value, callback) => {
if (value === "" || value == null) {
callback(new Error("请输入"));
} else if (!/^([1-9]+[\d]*(.[0-9]{1,2})?)$/.test(value)) {
callback(new Error("必须为数字,且小数点最多两位"));
}
callback();
}`;const ruleList =[{label:"数字校验规则",validator: validateNumber,},{label:"数字校验规则(小数点保留两位)",validator: validateNumberD2,},];exportdefault ruleList;
这样就可以通过下拉框的形式选择需要使用的校验方式
表单自定义配置
把 element-plus 校验规则进行总结, 归纳出所有组合类型, 通过表单配置的形式选择
这样就可以把校验规则进行可视化配置, 直观配置需要的校验规则, 学习成本低
同时校验规则表单列表使用到了动态表单组件(上图), 即 vue-form-design 暴露出来的两个组件其一(渲染表单组件和配置表单组件)
自定义校验函数
上文可知, 自定义校验函数使用到了 CodeMirror, 这样的话就可以编写代码
上图可知, 包括两个配置
- 函数触发事件配置, blur 还是 change
- 校验函数配置
该配置只写函数体, 如果整个函数可配, 可能导致参数使用错误, 并且把参数位置写死, 并和 element-plus 保持一致,
这样后期好处理, 降低配置错误率.并且把每个参数的作用列出来, 方便开发. 同时在 element-plus 固有的参数基础上, 新增了第四个参数, 表示当前表单配置数据,
方便基于其他表单数据作为校验判断的基础
使用场景如:
假设有两个表单如图, 一个是开关表单, 一个是校验表单
文本表单要求开关表单配置为 true 才校验通过, 否则不通过
则配置自定义函数
这样就使用到了第四个参数, 即拿到当前表单列表页数据
{ceshi: true, verify: ""}
, 进行校验
通过以上方法, 极大的提高了表单校验的扩展性, 灵活性
使用
codemirror
返回的函数体为一个字符串, 这种数据结构就能方便存储和进行各种处理, 如下
{"rule":[{"type":"func","title":"自定义函数规则","value":{"trigger":"blur","func":"if(mainData.ceshi){callback()}else{\n callback(new Error(\"请变更为false\"));\n}\n"}}]}
通过以上三种方法就能解决使用 json 配置的所有缺陷
下面的 json 是 element-plus 的校验规则例子, 大家发现了什么规则? 是不是字段的校验规则的数据结构是数组, 代表可配置多个校验规则
{"rules":{"name":[{"required":true,"message":"请输入活动名称","trigger":"blur"},{"min":3,"max":5,"message":"长度在 3 到 5 个字符","trigger":"blur"}]}}
所以为了和 element-plus 保持一致, 我们可以使用以上三个方法动态遍历设置校验规则
运用
如图, 在 vue-form-design 中可动态配置校验方式(自定义枚举、自定义校验函数、高级模式), 并按对应校验方式配置校验规则
一个表单最后生成的数据结构如下:
[{"ControlType":"Text","nameCn":"文本框","id":"QGR69RscPmjnFqHW7JeMB","layout":false,"data":{"fieldName":"Text_N-A6Ft1FUd8NJyrWN3LL2","label":"标签名称","tip":"","placeholder":"","showRule":"{}","required":false,"rule":[{"type":"enum","title":"自定义枚举","value":"(rule, value, callback) => {\n if (value === \"\" || value == null) {\n callback(new Error(\"请输入\"));\n } else if (!/^1(?:3d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8d|9d)d{8}$/.test(value)) {\n callback(new Error(\"请输入正确的值\"));\n }\n callback();\n }"},{"type":"func","title":"自定义函数规则","value":{"trigger":"blur","func":"callback()"}},{"type":"high","title":"高级模式","value":{"message":"必填","trigger":"change","required":true,"ruleType":"1"}}],"default":"","csslist":[]}}]
最后渲染表单组件时再处理每个表单的校验规则, 拼接成 element-plus 要求的数据接口即可.
如上数据结构,最后转换为如下 校验规则 json:
{"rules":{"Text_N-A6Ft1FUd8NJyrWN3LL2":[// ...]}}
校验实现
上文可知, 三种校验方式(自定义枚举、自定义校验函数、高级模式), 目的是转换为 element-plus 要求的规则
只要我们把配置的规则进行转换就能实现校验
functiongetFormListRules(rules: any[]){constresult: any[]=[];if(Array.isArray(rules)&& rules && rules.length >0){
rules.forEach((item)=>{if(item.type =="enum"){// 自定义枚举 上文可知我们枚举出来的是函数字符串, 使用eval执行返回函数const func =eval(`(${item.value})`);
result.push({validator: func,trigger:"blur",});}elseif(item.type =="func"){// 自定义函数校验 配置的是函数体 所以我们需要自己拼接出函数, 第四个参数需要通过使用默认参数的形式进行传参(利用到了闭包), 否则element-plus底层获取不到当前表单数据const mainData = props.formResult;const func =eval(`((rule, value, callback, mainData = mainData) => {${item.value.func}})`);
result.push({validator: func,trigger:"blur",});}elseif(item.type =="high"){// 高级模式 是通过表单配置出element-plus支持的数据, 所以不做特殊处理, 因为数字校验min, max使用表单配置校验不成功, 所有兜底使用了函数校验if(item.value.ruleType ==5){
result.push({validator:eval(item.value.validor),trigger: item.value.trigger,});return;}
result.push(item.value);}});}return result;}const rules =ref({});functiongetRules(item){let rule =[];if(item.data.required){
rule.push({required:true,message:"请输入"+ item.data.label,trigger:"blur",});}if(typeof item.data.rule =="string"){
rule = rule.concat(proxy.$Flex.tryParseJson(item.data.rule));}else{
rule = rule.concat(getFormListRules(item.data.rule));}// 特殊的jsoneditor表单要单独处理if(item.data.json){
rule.push(...proxy.$Flex.getJsonValidate());}
rules.value[item.data.fieldName]= rule;}
template 上使用
<el-formref="ruleForm":model="formResult":rules="rules"></el-form>
最后就能转换出 element-plus 用到校验数据格式
总结
三种校验方案都是基于 element-plus 提供的规则, 合理利用和发掘, 对其进行封装设计, 能在程序上极大提升配置效率, 降低门槛
自定义函数校验还是有一定的缺陷, 如自定义代码有缺陷或者抛错, 外层未进行兜底等, 后期会进行解决
github 地址
预览
版权归原作者 haixin-fang 所有, 如有侵权,请联系我们删除。