一个成熟的表单
表单表单,你已经长大了,你要学会:
- 动态渲染
- 支持单列、双列、多列
- 支持调整布局
- 支持表单验证
- 支持调整排列(显示)顺序
- 依据组件值显示需要的组件
- 支持 item 扩展组件
- 可以自动创建 model
这个表单控件是基于 element-plus 的 el-form 做的二次封装,所以首先感谢 element-plus 提供了这么强大的UI库,以前用jQuery做过类似的,但是非常麻烦,既不好看,可维护性、扩展性也差,好多想法都实现不了(技术有限)。
现在好了,站在巨人的肩膀上,以前的想法都可以实现了。
大图预警:这里有好几张大 gif 图片,流量不够的话慎开。。。
调整排列(显示)顺序以及调整布局
既然是动态渲染,那么组件的显示顺序也应该可以灵活调整。
所以设计了一个显示用的数组,把组件ID存放进去,v-for的时候,遍历这个数组就可以实现调整显示顺序的需求了。
单列的调整顺序,以及调整布局
导出MP4才400多k,导出gif居然2M多。真的好无语。
可以插入,可以交换,可以把几个短的挤到一行。
多列的调整顺序,以及调整布局
掐了一半没播,因为gif太大了。
还是插入、交换,可以让一个组件独占一行。
多列也是一样的操作。
依据组件值显示需要的组件
有的时候表单里的组件并不是都需要用户填写的,比如注册域名的时候,公司注册需要填写公司名称等信息,而个人注册显然没有公司信息可以填,那么这时候表单就需要根据用户的选择(公司、个人)来显示不同的字段来让用户填写。
这时候就需要一个运行时调整的功能。
其实现原理,还是通过调整那个排序用的数组来实现的。
先看一下效果:
还是这么大。。。
下拉列表里面选择不同的选项,会显示对应的组件。这种对应并不是写死的表单控件里面,而是可以在外部配置出来。
监听组件的值,然后修改排序用的数组
- json文件:
"formColShow": {
"103": {
"101" : [101, 102, 103, 104],
"102" : [101, 102, 103, 106, 107, 104, 105],
"103" : [101, 102, 103, 104, 105]
}
},
依据组件的编号,指定每个组件值对应需要显示的组件ID。
似乎有点太抽象了,其实就是应为抽象出来共同的特点,才可以实现这样的功能呀。
// 设置组件的显示顺序
const setFormColSort = (array = formMeta.colOrder) => {
formColSort.length = 0
formColSort.push(...array)
}
// 监听组件值的变化,调整组件的显示以及显示顺序
if (typeof formMeta.formColShow !== 'undefined') {
for (const key in formMeta.formColShow) {
const ctl = formMeta.formColShow[key]
const colName = formItemMeta[key].colName
watch(() => formModel[colName], (v1, v2) => {
if (typeof ctl[v1] === 'undefined') {
// 没有设定,显示默认组件
setFormColSort()
} else {
// 按照设定显示组件
setFormColSort(ctl[v1])
}
})
}
}
遍历 formColShow ,监听里面的组件的值,有变化了就按照值去加载对应的组件ID,如果没有就加载默认组件。
自动创建 model
我比较懒,不想写model的定义代码,太麻烦。
尤其当需求变更的时候,又要改代码。
那么能不能自动创建呢?于是写了个函数。
完整的model
表单里面有多少组件,就创建多少个属性。
// 根据表单元素meta,创建 v-model
const createModel = () => {
// 依据meta,创建module
for (const key in formItemMeta) {
const m = formItemMeta[key]
// 根据控件类型设置属性值
switch (m.controlType) {
case 100: // 文本类
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
case 130:
case 131:
formModel[m.colName] = ''
break
case 110: // 日期
case 111: // 日期时间
case 112: // 年月
case 114: // 年
case 113: // 年周
formModel[m.colName] = null
break
case 115: // 任意时间
formModel[m.colName] = '00:00:00'
break
case 116: // 选择时间
formModel[m.colName] = '00:00'
break
case 120: // 数字
case 121:
formModel[m.colName] = 0
break
case 150: // 勾选
case 151: // 开关
formModel[m.colName] = false
break
case 153: // 单选组
case 160: // 下拉单选
case 162: // 下拉联动
formModel[m.colName] = null
break
case 152: // 多选组
case 161: // 下拉多选
formModel[m.colName] = []
break
}
// 看看有没有设置默认值
if (typeof m.defaultValue !== 'undefined') {
switch (m.defaultValue) {
case '':
break
case '{}':
formModel[m.colName] = {}
break
case '[]':
formModel[m.colName] = []
break
case 'date':
formModel[m.colName] = new Date()
break
default:
formModel[m.colName] = m.defaultValue
break
}
}
}
// 同步父组件的v-model
context.emit('update:modelValue', formModel)
return formModel
}
虽然有点长,但都是简单的case判断。这样就省去了手撸 model 的麻烦。
依据选项创建 model
因为支持依据用户的选择而现实不同的组件,那么创建的model是否也需要调整呢?所以又写了个函数。两种 model 同时支持,这样需要哪个就可以用哪个。
// 依据用户选项,创建对应的 model
const createPartModel = (array) => {
// 先删除属性
for (const key in formPartModel) {
delete formPartModel[key]
}
// 建立新属性
for (let i = 0; i < array.length; i++) {
const colName = formItemMeta[array[i]].colName
formPartModel[colName] = formModel[colName]
}
}
先把原有的属性删掉,然后再加上新的属性。
支持 扩展组件
自带的组件肯定是不够的,因为用户的需求总是千变万化的,那么新组件如何加入到表单控件里面呢?可以按照接口定义封装成符合要求的组件,然后做一个map字典,就可以设置进去了。
因为接口统一,所以可以适应表单控件的调用。
简单的方法是,直接修改两个js文件。
如果不方便修改的话,也可以通过属性传递进来。目前暂时还没有想好细节,不过似乎不是太难。
动态渲染
引入json,然后作为属性传入表单控件,然后就可以了。
这样我们再实现添加、修改的功能的时候,就不是写代码,而是写json了,另外剧透一下,这个json当然是不需要手撸的,我这么懒。
单列、双列、多列以及调整布局
这个其实就是个 v-for 的功能,使用el-cow、el-col 设置好span就可以实现。
// 根据配置里面的colCount,设置 formColSpan
const setFormColSpan = () => {
const formColCount = formMeta.formColCount // 列数
const moreColSpan = 24 / formColCount // 一个格子占多少份
if (formColCount === 1) {
// 一列的情况
for (const key in formItemMeta) {
const m = formItemMeta[key]
if (typeof m.colCount === 'undefined') {
formColSpan[m.controlId] = moreColSpan
} else {
if (m.colCount >= 1) {
// 单列,多占的也只有24格
formColSpan[m.controlId] = moreColSpan
} else if (m.colCount < 0) {
// 挤一挤的情况, 24 除以 占的份数
formColSpan[m.controlId] = moreColSpan / (0 - m.colCount)
}
}
}
} else {
// 多列的情况
for (const key in formItemMeta) {
const m = formItemMeta[key]
if (typeof m.colCount === 'undefined') {
formColSpan[m.controlId] = moreColSpan
} else {
if (m.colCount < 0 || m.colCount === 1) {
// 多列,挤一挤的占一份
formColSpan[m.controlId] = moreColSpan
} else if (m.colCount > 1) {
// 多列,占的格子数 * 份数
formColSpan[m.controlId] = moreColSpan * m.colCount
}
}
}
}
}
// 先运行一次
setFormColSpan()
// 设置组件的显示顺序
const setFormColSort = (array = formMeta.colOrder) => {
formColSort.length = 0
formColSort.push(...array)
}
// 先运行一下
setFormColSort()
表单验证
这个使用el-form提供的验证功能。
目前暂时还没有归纳好el-form的验证,因为需要把这个验证用的数据写入到json里面,然后读取出来设置好即可。
所以肯定没难度,只是需要点时间。
源码
https://github.com/naturefwvue/nf-vue-element