开源表单设计器vue-form-design动态表单校验实现原理

表单校验可以改善用户体验和减轻服务器的压力, 而动态配置表单校验能极大的提高动态表单的扩展性、灵活性, 满足多样性、差异化需求

目标

👌,首先我们简要说下要实现的目标功能:

  • 具有基础的表单验证功能
  • 提供一些内置验证规则
  • 提供对外开放的能力

这样就能直接通过配置的形式无代码来表单校验

背景

开源库 vue-form-design基于 Vue3 的可视化表单设计器,拖拽式操作让你快速构建一个表单, 让表单开发简单而高效。

低代码表单设计器, 可以通过拖拽的形式生成 n 种不同类型的表单, 这样的表单如何校验数据的准确性?

就需要用到 element-plus 提供的表单校验相关规则, 并且该规则是一个对象, 那我们是不是可以配置 json 来进行校验?

在开发表单设计器初期, 确实是通过 json 配置的形式进行表单校验, 但是这种方法有几大缺陷

  • 局限于 element-plus 提供出来的校验方式, 如必填, 数字校验, 长度校验等
  • 保存动态表单配置时会转成 json 字符串, 如果使用 element-ui 自定义校验函数时,会转换成空 (就算能使用函数的 toString 方法转成字符串, 但校验函数的代码提示不好, 不能自定义参数等)
  • 入门门槛高, 需了解 element-plus 校验方式, 需要一定的基础
  • 流程复杂, 不能通过下拉框枚举出常见校验直接使用
  • 动态表单, 顾名思义, 可以通过表单配置的形式来校验输入的准确性, 但 json 配置的方式不支持

从以上几大缺陷可知, 使用 json 校验方式的扩展性, 可维护性, 便捷性, 灵活性都没有, 所以才需要重新设计动态表单的校验方式

技术

组件库使用的是element-plus, 所以相关表单校验的设计要符合element-plus提供的规则

如以下规则:

json 复制代码
{
  "rules": {
    "name": [
      { "required": true, "message": "请输入活动名称", "trigger": "blur" },
      { "min": 3, "max": 5, "message": "长度在 3 到 5 个字符", "trigger": "blur" }
    ],
    "region": [{ "required": true, "message": "请选择活动区域", "trigger": "change" }]
  }
}

自定义校验函数规则:

js 复制代码
var validatePass = (rule, value, callback) => {
  if (value === "") {
    callback(new Error("请输入密码"));
  } else {
    if (this.ruleForm.checkPass !== "") {
      this.$refs.ruleForm.validateField("checkPass");
    }
    callback();
  }
};

为了提升扩展性, 可以通过编写自定义校验函数的形式进行校验, 涉及到代码开发, 所以需要一个可提示、可编写注释的开源编辑器codemirror

CodeMirror 是基于 js 的源代码编辑器组件,它支持 javascript 等多种高级语言,tampermonkey 内置的代码编辑器就是基于它。它的按键组合方式兼容 vim,emacs 等,调用者还可自定义'自动完成'的列表窗口,自由度极高,相当成熟。

使用

js 复制代码
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 呢? 正好在该库也使用到

js 复制代码
import { json } from "@codemirror/lang-json";
export default {
  setup() {
    const extensions = [json()];
    return {
      extensions,
    };
  },
};

template

html 复制代码
<codemirror v-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 两种校验方式

那如何解决几大缺陷?

我是如何做的? 以下三种方式进行解决

枚举校验

把常见的、已知的校验规则枚举出来, 直接通过文件引用的方式遍历出校验列表进行选择(最好是维护到后台里面, 通过接口获取, 这样方便增删改查)

如:

js 复制代码
// 数字校验规则
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,
  },
];
export default ruleList;

这样就可以通过下拉框的形式选择需要使用的校验方式

表单自定义配置

把 element-plus 校验规则进行总结, 归纳出所有组合类型, 通过表单配置的形式选择

这样就可以把校验规则进行可视化配置, 直观配置需要的校验规则, 学习成本低

同时校验规则表单列表使用到了动态表单组件(上图), 即 vue-form-design 暴露出来的两个组件其一(渲染表单组件和配置表单组件)

自定义校验函数

上文可知, 自定义校验函数使用到了 CodeMirror, 这样的话就可以编写代码

上图可知, 包括两个配置

  • 函数触发事件配置, blur 还是 change
  • 校验函数配置

该配置只写函数体, 如果整个函数可配, 可能导致参数使用错误, 并且把参数位置写死, 并和 element-plus 保持一致, 这样后期好处理, 降低配置错误率.并且把每个参数的作用列出来, 方便开发. 同时在 element-plus 固有的参数基础上, 新增了第四个参数, 表示当前表单配置数据, 方便基于其他表单数据作为校验判断的基础

使用场景如:

假设有两个表单如图, 一个是开关表单, 一个是校验表单

文本表单要求开关表单配置为 true 才校验通过, 否则不通过

则配置自定义函数

这样就使用到了第四个参数, 即拿到当前表单列表页数据{ceshi: true, verify: ""}, 进行校验

通过以上方法, 极大的提高了表单校验的扩展性, 灵活性

使用 codemirror 返回的函数体为一个字符串, 这种数据结构就能方便存储和进行各种处理, 如下

json 复制代码
{
  "rule": [
    {
      "type": "func",
      "title": "自定义函数规则",
      "value": {
        "trigger": "blur",
        "func": "if(mainData.ceshi){callback()}else{\n  callback(new Error(\"请变更为false\"));\n}\n"
      }
    }
  ]
}

通过以上三种方法就能解决使用 json 配置的所有缺陷

下面的 json 是 element-plus 的校验规则例子, 大家发现了什么规则? 是不是字段的校验规则的数据结构是数组, 代表可配置多个校验规则

json 复制代码
{
  "rules": {
    "name": [
      { "required": true, "message": "请输入活动名称", "trigger": "blur" },
      { "min": 3, "max": 5, "message": "长度在 3 到 5 个字符", "trigger": "blur" }
    ]
  }
}

所以为了和 element-plus 保持一致, 我们可以使用以上三个方法动态遍历设置校验规则

运用

如图, 在 vue-form-design 中可动态配置校验方式(自定义枚举、自定义校验函数、高级模式), 并按对应校验方式配置校验规则

一个表单最后生成的数据结构如下:

json 复制代码
[
  {
    "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:

json 复制代码
{
  "rules": {
    "Text_N-A6Ft1FUd8NJyrWN3LL2": [
      // ...
    ]
  }
}

校验实现

上文可知, 三种校验方式(自定义枚举、自定义校验函数、高级模式), 目的是转换为 element-plus 要求的规则

只要我们把配置的规则进行转换就能实现校验

js 复制代码
function getFormListRules(rules: any[]) {
  const result: 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",
        });
      } else if (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",
        });
      } else if (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({});
function getRules(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 上使用

html 复制代码
<el-form ref="ruleForm" :model="formResult" :rules="rules"> </el-form>

最后就能转换出 element-plus 用到校验数据格式

总结

三种校验方案都是基于 element-plus 提供的规则, 合理利用和发掘, 对其进行封装设计, 能在程序上极大提升配置效率, 降低门槛

自定义函数校验还是有一定的缺陷, 如自定义代码有缺陷或者抛错, 外层未进行兜底等, 后期会进行解决

github 地址

预览

相关推荐
别拿曾经看以后~1 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍