楔子
大概 4 年前,我做了一个简单的动态表单功能,开发人员通过 UI 界面配置表单(其实就是添加常用的控件,如文本框、下拉框等)就能向用户提供数据查询,反响不错,尤其是偏后端开发的小伙伴。
时至今日,上述功能存在以下问题:
- 不够灵活,无法满足不同场景的定制化需求
- 设计界面简陋,功能有限
- 组件有限,不支持扩展
- 不支持自定义代码(回调函数)
想要什么
- 简单
灵活易用(用户仅需要极低的学习成本) - 支持
VUE3(团队前端技术栈以 vue 为主) -
可视化表单设计(所见即所得) - 支持
移动端渲染(团队使用 Vant4,PC端渲染支持 Naive UI、Element Plus) - 支持
回调函数(事件监听) - 数据
联动(某个值变动后影响其他表单项) - 能够
自定义组件(满足定制化需求)
开源产品调研
目前书面上已经有不少优秀开源的同类产品,这里列出可二次开发的, 同时具备表单渲染、表单设计的工具(截止至 2022年底)
| 方案 | 框架 | UI库 | 备注 |
|---|---|---|---|
| formilyjs | React、Vue | AntD、Element、Vant等主流 | 校验、事件交互阿里巴巴开源的表单设计工具体系,能做到一份表单设计多端适配;但是对 vue3 支持不完备(设计器得自己做) |
| FormMaking | VUE | AntD、Element | 校验、事件交互操作良好,需要高级版本才支持 vue3 |
| form-generator | VUE | Element | 校验 操作良好,预览不友好(不够直接爽快),目前不支持vue3 |
| form-create | VUE | iView、AntD、Element、Naive UI | 校验操作良好,支持多个 UI 框架,对 vue 2/3 均支持,无设计器 |
| VForm | VUE | Element | 校验、事件交互 操作良好,开源版不支持数据源、子表单 |
再造个轮子吧
同类型的开源产品各有千秋,适合不同的应用场景,然而跟我想要的还不够契合。权衡后,还是觉得自己弄一个😄。技术选型为 vue3 + naive UI,使用 pnpm 进行包管理(monorepo结构)。
不同于同类型产品的组件拖拽,我采用栅栏布局来堆积组件(实现起来简单,省事😁,暂不支持容器嵌套、子表单),通过设置组件占据的格子数可以使其独占一行,故取名 GRID-FORM(栅栏表单),源码详见 Github。
表单设计器
得益于 VUE 的响应式,设计器所见即所得显得尤为丝滑,不然得自己手撸监听配置项变动事件及界面重绘。
编辑器分为左中右三个区域(这是业内约定俗成的标准设计),有别于兄弟产品,我把左区域用作表单整体的参数编辑。为方便用户自定义组件,设计器对外暴露组件库参数,并封装了常用的组件(诸如输入框、单选/多选框、日期选择)。
组件分为
数据型(对应上图中的输入组件、选择组件)及展示型(上图的展示组件)两类,后者不参与表单提交。
渲染器
组件渲染
每个组件有唯一编号,渲染函数为一个Object(key 即为组件编号),需要扩展组件时添加对应的渲染函数即可。渲染时属性分为基本信息(名称我用 _ 开头加以区分)及组件层面两类,分别对应了组件渲染函数的两个参数:attrs、props。
渲染引擎处理完属性后,调用 Render 函数(不同 UI 库各自实现,使用者可根据业务需要自行覆写)得到组件实例。此处以文本输入框 INPUT为例:
js复制代码import { h } from ''vue'' import { NInput } from ''naive-ui'' const RenderFuncs = { /** * @param {*} props 组件/控件属性 * @param {*} attrs 基本信息 * @returns */ "INPUT" : (props, attrs)=>{ if(props.rows > 1) props.type = "textarea" return h(NInput, props, buildSlotWithPrefixAndSuffix(props)) } }
默认值
表单项默认值可以填写常量或占位符(在初始化时被模板引擎赋值),占位符格式为${code},用户可自行扩展处理函数。
校验
此处校验分为
非空、内容格式两种
当表单项勾选是否必填,则在提交前渲染器会对该值进行非空检测;若设置了校验正则,则对非空值进行正则表达式校验。
事件&钩子函数
| 事件ID | 名称 | 回调参数 | 说明 |
|---|---|---|---|
| onLoad | 加载完成 | (form) | 在表单初始化后触发 |
| onSubmit | 表单提交前 | (form,items) | 当用户点击提交按钮后触发 |
| onChange | 数据值变动 | (form,agent,items) | 详见下一节数据联动 |
| afterSubmit | 表单提交后 | (form) | 注意该事件需由使用方手动触发(因为渲染器无法感知表单是否已正确提交) |
参数说明
| ID | 名称 | 类型 | 说明 |
|---|---|---|---|
| form | 表单数据 | Object | 当前全部表单项的数据对象(支持响应式) |
| items | 表单项清单 | Array | 来自设计器导出的表单项数组(支持响应式) |
| agent | 变动内容 | Object | 包含三个属性:key(表单项ID)、from(旧值)、to(新值) |
数据联动
常规的做法是输入类表单项增加事件(如 onChange、onBlur、onFocus 等),但是这样操(实)作(现)繁(困)琐(难)😂😂,我的做法只需要填写一处代码(直观简单)
要启用联动需要满足以下条件:
- 填写表单的
onChange事件代码 - 至少一个表单项勾选了
监听值变动
渲染器初始化后,会对勾选监听值变动的表单项开启监听(没错,是每个表单值有独立的监听)从而获取到新旧值。注意,若在回调函数中对form改动会重新触发onChange事件。
示例
js复制代码/** * 通过修改 items 下元素的 disabled(为 true 时禁用表单项)、_hide(为 true 时隐藏) * 可实现表单项的禁用与隐藏 */ if(agent.key==''nature''){ if(agent.to==''个体工商户''){ items.filter(v=>v._uuid==''scale'')[0].disabled=true form.scale = 1 } else{ items.filter(v=>v._uuid==''scale'')[0].disabled=false } }
适配更多 UI 库
目前已实现
Naive UI、Vant4的渲染器
我封装了渲染器的基础框架(组合式 API), 帮助使用者根据需要快速适配心仪的 UI 库。
结语
因个人能力有限,此工具在设计、实现上存在诸多不足,仅作学习交流🙂。