由于公司经常需要做一些表单页,每次都是重复开发,所以决定搞一下自定义表单,可以大大的减少工作量,说干就干,冲冲冲!
由于是公司内部项目,所以本文只记录实现的思路,代码是不可能放出来的,毕竟我还年轻,我不想进去...
通用组件配置
首先得是定义一些常用的组件,后续可根据业务需求新增
比如:【微信名(小程序或公众号网页版本)】、【定位】、【录音】、【视频】和【手写签名】等组件
[{
"type": "text",
"name": "单行文本"
}, {
"type": "multiline",
"name": "多行文本"
}, {
"type": "number",
"name": "数字"
}, {
"type": "date",
"name": "日期"
}, {
"type": "time",
"name": "时间"
}, {
"type": "radio",
"name": "单选项"
}, {
"type": "checkbox",
"name": "多选项"
}, {
"type": "cascader",
"name": "级联选择"
}, {
"type": "address",
"name": "地址"
}, {
"type": "image",
"name": "图片"
}, {
"type": "formList",
"name": "表单组"
}]
表单配置
然后就是各表单项的配置,后续可以根据业务拓展
{
id: "xxx", // 前端随机生成
name: "表单项名称", // 单选、多选、单行文本等
type: "表单项类型", // 与后端定义相同规则,如text,radio,checkbox,file,date,time,datetime等
title: "表单项标题",
options: [{ // 单选、多选独有配置
title: "选项名称",
id: "选项id,前端随机生成"
}],
other: [], // 其他配置,如是否必填、是否记录上次填写的内容、是否默认使用当前日期或时间等
conditions: [], // 表单渲染条件,后面会介绍
[其他字段]: xxx // 针对不同组件增加相应字段。如表单组增加children字段描述表单组内的表单项;级联选择增加placeholder描述各级标题,options变为[{lable: 'xxx', value: 'xxx'}]等。也可以用统一的字段名去描述,这个看个人喜好,能描述清楚就行
}
一些难点
页面交互
这块主要是多借鉴(chao)参考(xi)各个大厂的实现,如腾讯问卷、番茄问卷、草料二维码等,这里就不详细介绍了。
数据转换
将antd Form组件收集到的源数据转换成上述【表单配置】格式,并在表单编辑和复制时转换回来便于回填(当然,也可以直接存一份源数据到数据库,就只需要转换一遍。优点是减少代码量,缺点是前后端传输成本增加,毕竟一个表单的源数据转换成json也是挺长的)
这一步也是些常见的数据处理手段
逻辑设置
表单必备的功能,根据单选或多选所选项来控制接下来的表单项是否展示。
- 配置
上几个截图
逻辑配置的数据格式,这个数据结构用来描述逻辑规则应该是没问题的
这个格式是哪个表单项配置了就会增加一条规则
[
{
item: 'xxx', // 单选或多选选项id
rules: [
{
option: 'xxx', // 单选或多选选中项id
displayItems: ['xxx', 'xxx', ...], // 选中指定选项时页面展示的表单项id
}
]
}
]
- 表单渲染
本来渲染也是想借鉴番茄表单用css控制表单项显示隐藏的,但是由于antd Form组件的校验规则(css隐藏的表单项也会校验),所以将上述规则处理成下面的格式,比较方便渲染
这个规则是哪个表单项需要被控制
conditions就会多一条规则
{
id: "xxx", // 前端随机生成
...[上面介绍过],
conditions: [
{
id: 'xxx', // 控制该表单项展示的表单项id
value: 'xxx', // 控制该表单项展示的表单项选中的值
isParent: Boolean, // 该规则是本身的规则还是父表单项的规则
}
], // 表单渲染条件
}
为什么需要 isParent 字段呢?因为逻辑规则可能会有嵌套,比如下面的情况:
选中性别为男 => 选中喜欢女生的类型 => 根据不同类型展示不同的输入框
渲染的时候遍历 conditions 中的规则,如果是本身的规则,则满足其中一项即可渲染,使用some;如果是父表单项的规则,则需全部满足才能渲染,使用every
这一步格式转换说实话有点恶心,最后还是在老大的帮助下写出来的,但总感觉实现方式不是很优雅,等后面有空了再看看能不能优化下。
还要提一下,在删除表单项或者单选多选的选项时,记得将规则也同步删除,不然渲染会有问题。
也不知道我有没有讲清楚,好气哦...
表单组组件
需求就是类似这种效果
可以添加很多组,然后超过两组时可以删除
这个我看那些现有的都好像没有这个组件,它们用的都是【矩阵】组件,没得借鉴了,自己干吧
分三步走:打开编辑器 => 编写代码 => 保存代码并关闭编辑器
皮一下很开心,emmmm,说正经的...
- 首先要把通用组件(除表单组之外的组件)配置抽离,方便表单组配置时复用代码。然后将表单组的配置全部丢到
children中,渲染的时候根据type区分,type为formList的渲染它的children,否则直接渲染。 - 思考需不需要无限嵌套,表单组中能不能再包含表单组,按道理是可以的,但是业务不需要所以就没做,只做了一个层级的。
这个组件考虑比较多的其实是表单组里的逻辑设置,关于配置方面我是实现了,但是由于 antd Form组件的 Form.List 目前有点问题,就是会自动收集页面未渲染的表单项的值,但是可以跳过校验,所以暂时先把这一块砍掉了。
关于
Form.List的 issue
级联选择组件
刚开始想的比较简单,以为这个应该不难,直接像下面这样配置就行了
但是由于要做数据统计,如果编辑的话生成的 id 肯定会变,然后又开始了借鉴之路。看了下腾讯问卷的实现方式,哇,大厂就是不一样,各种情况都考虑到了,但是要我这个菜鸡实现的话还是有点难度的(看着这么复杂的交互有点懒不想动),所以新增我还是使用原来的方式,编辑的话我分别提供了单项编辑和新增,后面如果过需要删除的话也可以加上去。实现效果如下:
这样就不会影响历史数据了,虽然好像不太优雅,但好歹实现了。
这个组件耗时比较久的应该是将 textarea 的值转换成级联选择所需的数据格式了,本来用递归应该是比较好的实现方式,但是我没写出来。。。后面用的一堆 if 加循环实现的
所以大佬们如果有空的话可以实现下,然后评论区讨论下
const str = '省份/城市/区县/学校\n广东省/深圳市/宝安区/学校1\n广东省/深圳市/宝安区/学校2\n广东省/深圳市/南山区/学校1\n广东省/深圳市/南山区/学校2\n广东省/深圳市/龙华区/学校1\n广东省/深圳市田区/学校1\n广东省/深圳市田区/学校2\n广东省/深圳市/龙岗区/学校1\n广东省/深圳市/龙岗区/学校2\n广东省/广州市/番禺区/学校1\n江西省/南昌市/青山湖区/学校1\n江西省/南昌市/西湖区/学校1\n江西省/赣州市/南康区/学校1'
const result = [
{
label: '江西省',
value: '江西省',
children: [
{
label: '南昌市',
value: '南昌市',
children: [
{
label: '青山湖区',
value: '青山湖区',
children: [
{
label: '学校1',
value: '学校1'
}
]
},
{
label: '西湖区',
value: '西湖区',
children: [
{
label: '学校1',
value: '学校1'
}
]
}
]
},
{
label: '赣州市',
value: '赣州市',
children: [
{
label: '南康区',
value: '南康区',
children: [
{
label: '学校1',
value: '学校1'
}
]
}
]
}
]
}
]
从 str 到 result,str 的层级不固定,最多 4 层,最少 1 层,示例是 4 层
表单数据查看及统计
数据统计是后端的活儿,我只负责展示,,,但是后端大哥返回给我的是 id 和值,然后一个 mapping 对象。我要根据 id 去查对应的标题,然后再组装成我要的格式。虽说难度不大,但是耐不住层级多(表单组中包含的单选多选等)啊,现在 ?. 的兼容性又不是很好,所以只能写一堆的三目运算符,真是太快(e)乐(xin)了
然后就是对于单选和多选做的可视化统计了,这个用的 antd-pro 的【图表】组件,不得不说,站在巨人的肩膀上还是舒服呀。
echarts也很香,都是大佬给我们小码农的福利
最后
这个项目总体还是让我比较兴奋的(除了中间代码写不出来的时候),因为还算是比较有挑战性(不像之前不同形式的增删改查),后面肯定还需要继续完善,架子搭好了后面各种奇奇怪怪的配置应该都可以实现。
这也算是跳出舒适圈的一种形式吧,继续前行吧
对了,还得感谢下各个大厂产品给的思路,这应该不算抄袭吧,不算吧,不算吧......我真的只是借鉴而已~