实现一个配置分离的复杂表单
示例使用
uniApp
开发,示例传送门:访问地址(h5)
主要功能
- 表单通过单独文件配置,按规定格式配置即可自动生成表单输入项。
- 可控制表单输入类型:文本输入(可限制输入类型如
number
、text
)、单选、颜色选择、checkbox
(布尔类型控制)。 - 支持配置默认值、
placeholder
、字段提示文字(弹窗显示) - 可单独控制是否显示,也可设置联动显示(根据某一字段的值控制是否显示)
- 精准追查到每一项的改动。
!!!请不要用到生产环境中,只是一个小Demo,功能上会有不完整,不会涵盖到所有的输入类型,输入校验也没有。
先上示例图:
![1655193530531](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a621643e52540da916cc1b4d07b78ed~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)
生成的结果
导出结果的数据结构如下:
部分表单配置项
考虑到当表单项太多情况下放到一起会显得太用臃肿,采用分组的形式。
分组可单独控制是否显示, key
是最后生成结果的对象键,对应上面结果的 nameOpt
分组中每一项的配置以数组形式保存在nameOpt.configOpt
中
- label: 显示的名称
- isShow: 控制当前项是否显示
- name: 生成配置的键
- value: 当前值 为
null
时 最后的结果取default
的值 - default: 默认值
- type: 表单类型(目前定义有 Boolean, Input, Color, Radio)
- root:导出在
root
的对象下,加入这个可以使当前项显示在当前分组下,最后导出结果时却在其他的对象下(跨分组甚至独立出来) - fieldInfo: 字段提示文字(设置了这个表单右侧会有个问号,点击会弹窗显示这个里面的东东)
- fieldType:
type = Input
时生效,指定输入框的类型 - placeholder:
placeholder
不解释 - radioList:
type = Radio
时生效,可选择的列表 - showWhen:
Array<Object>
: 联动控制是否显示(根据其他项的值控制是否显示本项,可 n 对 一, 可跨分组控制)- 以下示例配置中
showWhen
中的每一项:optKey
:控制项的分组key
,root
:控制项的root
,name
:控制项的name
,value
:控制项中值在value
中存在则显示。
- 以下示例配置中
以下是一个分组的配置。
const nameOpt = {
title: '标题配置',
isShow: true,
key: 'nameOpt',
configOpt: [
{ label: '是否展示', isShow: true, name: 'isShow', value: true, default: true, type: 'Boolean', root: 'nameOpt', fieldInfo: '是否展示标题'},
{ label: '标题文字', isShow: true, name: 'title', value: null, default: '', root: 'nameOpt', type: 'Input', fieldType: 'text', placeholder: '标题文字' },
{ label: '是否加粗', isShow: true, name: 'bold', value: true, default: true, root: 'nameOpt',type: 'Boolean' },
{ label: '文字颜色', isShow: true, name: 'color', value: null, default: '#ffffff', root: 'nameOpt',type: 'Color', fieldType: 'text',placeholder:'#ffffff' },
{
label: "对齐方式",
isShow: true,
name: 'align',
value: "center",
type: "Radio",
default: 'center',
root: 'nameOpt',
placeholder: '',
radioList: [
{ label: '左', value: 'left', }, { label: '居中', value: 'center' }, { label: '右', value: 'right' }],
showWhen: [{
optKey: 'tableColumnOpt',
root: 'tableColumnOpt',
name: 'columnFmt',
value: ['number','currency', 'percentage']
}
],
}
]
}
// ...
复制代码
实现思路
初始化过程
初始配置数据:
- 拿到配置的数据,遍历分组转换成数组的形式,同时遍历分组中的
configOpt
,将存在showWhen
的缓存到this.autoShowItems
数组中(方便以后某一项改变时触发是否显示会用到,先缓存起来)。
最终得到的列表数据(editList
)与需要根据其他项控制是否显示的数组(autoShowItems):
。
2. 根据 autoShowItems
处理对应项是否显示状态(避免初始化状态错乱)。
3. 根据列表数据(editList
)生成结果集
// 初始化
initConfigList(tableConfig){
// 配置数据
console.log(' =====> tableConfig', tableConfig);
this.autoShowItems = this.autoShowItems.splice(0)
let list = []
// 将配置对象装换成数组,并保存所有autoShowItem 对象信息
Object.keys(tableConfig).forEach(tKey => {
list.push(tableConfig[tKey])
tableConfig[tKey].configOpt.forEach(cItem => {
if(cItem.showWhen) this.autoShowItems.push({...cItem, selfOptKey: tableConfig[tKey].key})
})
})
// 处理自动显示/隐藏的项
this.autoShowItems.forEach(item => {
let changeItem = list.find(cItem => cItem.key === item.selfOptKey)
if(!changeItem) return
let changeOptItem = changeItem.configOpt.find(cItem => cItem.root == item.root && cItem.name == item.name)
if(!changeOptItem) return
let isShow = false
item.showWhen.forEach(sItem => {
let { optKey, root, name, value} = sItem
const decisionItem = list.find(lItem => lItem.key === optKey)
let decCitem = decisionItem.configOpt.find(cItem => cItem.root == root && cItem.name == name)
if(value.includes(decCitem.value)) isShow = true
})
changeOptItem.isShow = isShow
})
console.log(' =====> list', list);
console.log(' =====> this.autoShowItems', this.autoShowItems);
this.editList = [...list]
},
/**
* 初始化,根据配置列表生成配置对象
* @param {Object}
*/
initResultObj(editList) {
this.resultObj = editList.reduce((obj, item) => {
item.configOpt.forEach(it => {
if (!obj[it.root]) obj[it.root] = {}
obj[it.root][it.name] = (it.value === null || it.value === undefined) ?
it.default :
it.value;
})
return obj
}, {})
console.log(' =====> this.resultObj', this.resultObj);
},
复制代码
改变表单项
监听每个表单项改变的事件,更新结果集对应的项,并且检查是否在autoShowItems
数组中,动态改变对应项的isShow
onFormItemChange(e) {
this.updateConfigObj(e.item)
this.checkShowWhen(e.item)
},
// 更新某一项
updateConfigObj(item) {
const { root, value, name } = item
this.resultObj[root][name] = value
console.log(' 数据已更新 =====> this.resultObj', this.resultObj);
console.log(` 当前更新项 =====> ${root}[${name}],更新值为:${value}`);
this.checkShowWhen(item)
},
// 检查当前更新项是否会影响到 自动更新的项
checkShowWhen(item) {
if (!this.autoShowItems || this.autoShowItems.length === 0) return
this.autoShowItems.forEach(it => {
// 找到当前元素
let actOpt = this.editList.find(iit => iit.key === it.selfOptKey)
if(!actOpt) return
let changedItem = actOpt.configOpt.find(cItem => cItem.root == it.root && cItem.name == it.name)
if(!changedItem) return
let isShow = false
it.showWhen.forEach(sItem => {
let { optKey, root, name, value} = sItem
const decisionItem = this.editList.find(lItem => lItem.key === optKey)
let decCitem = decisionItem.configOpt.find(cItem => cItem.root == root && cItem.name == name)
if(value.includes(decCitem.value)) isShow = true
})
changedItem.isShow = isShow
})
}
复制代码
源码已发布到 gitCode