【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等
接上篇,上一篇介绍了 Interface 以及 controller 的一部分,这一篇继续介绍后续功能。
- 双列的表单
自动创建 model
既然有了meta,那么还需要手动创建 model 吗?其实在低代码里面,model都是运行时创建的吧,否则怎么“低”呢?
不过呢,我们还是应该两手准备,既可以支持低代码的方式,又可以支持常规开发的方式,因为只有这样,可方便切换到常规模式,否则还得自己弄一个 model,多麻烦。
获取名称和类型
meta 里面用字段名称,拿出来作为key即可,那么类型呢?需要在meta里面设置一个类型的描述吗?我觉得不用。因为我们可以使用控件类型来确定。
我们先来做个字典:
const dicType = {
// 文本
100: '',
101: '',
102: '',
103: '',
104: '',
105: '',
106: '',
107: '',
108: '',
// 数字
110: 0,
111: 0,
112: 0,
// 日期
120: '',
121: '',
122: '',
123: '',
124: '',
// 日期范围
125: [],
126: [],
127: [],
128: [],
// 时间
130: '',
131: [],
132: '',
133: [],
// 上传
140: '',
141: '',
142: '',
// 选择
150: false,// 勾选
151: false, // 开关
152: [],// 多选组
153: '',// 单选组
// 下拉
160: '',// 下拉单选
161: [],// 下拉多选
162: '',// 分组下拉单选
163: [],// 分组下拉多选
164: [],// 下拉联动
165: '',// 树状下拉
166: []// 树状多选
}
这样就可以把控件的类型和model的属性类型联系起来。日期控件,可以使用 string,也可以使用Date,还可以使用时间戳,其实还可以使用 null。不过初始化的时候,我们设置为 ‘’ 即可。
/**
* 创建表单的 model,传入 input 这类的 meta,依据其中的字段名称和控件类型,字段创建。
* @param meta 表单子控件的 meta
* @param colOrder 字段ID,数组。需要哪些字段,以及顺序
*/
export default function createModel<T>(
meta: IFormChildPropsList,
colOrder: Array<number | string>
): T {
// 定义一个 model
const formModel = {}
// 依据 meta,创建 model
colOrder.forEach(key => {
const _meta: IFormChildMeta = meta[key].meta
if (_meta.controlType < 200) {
// 表单内置组件
formModel[_meta.colName] = dicType[_meta.controlType]
} else {
formModel[_meta.colName] = ''
}
// 看看有没有设置默认值
if (typeof _meta.defValue !== 'undefined' ) {
switch (_meta.defValue) {
case '':
break
case '{}':
formModel[_meta.colName] = {}
break
case '[]':
formModel[_meta.colName] = []
break
case '{{now}}':
formModel[_meta.colName] = new Date()
break
default:
formModel[_meta.colName] = _meta.defValue
break
}
}
if (Array.isArray(formModel[_meta.colName] )){
// 数组类型,有可能需要对应多个字段
moreColName(_meta , formModel)
}
})
const re = reactive(formModel)
return re as T
}
先定义一个对象,然后遍历 meta,把字段名作为key加到对象里面,然后设置类型和默认值,最后套上 reactive,强制转换成泛型 T。
当然,这种方式比较简单粗暴,无法保证自动创建的 model 可以复合泛型 T 的类型。只是,我只能想到这么多了。
json 里面有字段名称还有字段类型和默认值,那么我们可以自动创建一个model。
创建 model 的位置
一开始,在内部和外部都可以创建,还可以兼容,但是后来想想,何必为难自己呢,然后使用者还各种懵逼,所以,干脆简单点。
提供一个创建 model 的工具,可以用泛型设置类型,在外部创建 model,可以用这个工具,也可以用其他方式,反正提供一个对象即可。
联动筛选
当一个组件的值发生变化的时候,需要显示(隐藏)哪些字段的联动。我们还是先定义一个 Interface:
定义 Interface
// 显示控件的联动设置
export interface ILinkageMeta {
[key: string | number]: {
[id: string | number]: Array<number>
}
}
记录一下联动情况,当一个字段的值发生变化时,需要显示哪些字段:
- key:控件的ID作为key,每个控件值对应一个数组,数组里面是需要显示的控件ID。
- id:控件的值作为key,后面的数组里存放需要显示的控件ID
- 选择这个值之后需要显示的字段ID,数组
json 文件
在json 文件里面设置对应的信息:
"linkageMeta": {
"90": {
"1": [90, 101, 100, 102, 103, 104, 105, 106, 107, 108],
"2": [90, 110, 111, 112],
"3": [90, 120, 121, 122, 123, 124, 125, 126, 127, 128],
"4": [90, 130, 131, 132],
"5": [90, 150, 151, 152, 153],
"6": [90, 160, 161, 162, 163, 165, 166, 164]
},
其他字段。。。
}
编号为 90 的字段,有 6 个值,每个值对应一组字段ID,当选择一个值的时候,对应的字段将会被显示,其他的会隐藏。
封装代码
/**
* 设置备选项和子控件的联动
* @param formMeta 表单控件的meta
* @param model 表单完整的 model
* @param partModel 表单部分 的 model
* @returns
*/
export default function setControlShow<T>(
formMeta: IFromMeta,
itemMeta: IFormChildMetaList,
model: T,
partModel: any
) {
// 解构需要的数据
const {
linkageMeta,
colOrder
} = formMeta
// 设置字段的是否可见
const showCol = reactive<ShowCol>({})
// 设置联动
const setFormColShow = () => {
// 数据变化,联动组件的显示
for (const key in linkageMeta) {
// 配置里面设置的主动组件。
const mainComp = linkageMeta[key]
// 主动组件的字段名称
const colName = itemMeta[key].colName
// 监听组件的值,有变化就重新设置局部 model
if (typeof model[colName] !== 'undefined') {
// 监听主动组件的值的变化
watch(() => model[colName], (modelValue, oldValue) => {
// 单选组的选项,先让字段都不可见,
Object.keys(itemMeta).forEach(key => {
showCol[key] = false
})
// 配置信息里对应的字段ID集合,设置为可见
mainComp[modelValue].forEach(id => {
showCol[id] = true
})
// 设置部分的 model
createPartModel<T>(model, partModel, itemMeta, showCol)
},
{ immediate: true })
}
}
// 监听完整model的值的变化,同步值
if (typeof partModel !== 'undefined') {
watch(model, () => {
for (const key in model) {
if (typeof(partModel[key]) !== 'undefined') {
partModel[key] = model[key]
}
}
})
}
}
return {
showCol,
setFormColShow
}
}
思路:
- 获取配置信息里面的 linkageMeta 信息
- 遍历里面的字段
- 监听对应的 model 的属性值的变化
- 当变化的时候,依据值对应的字段ID,设置字段是否显示。
- 依据显示的字段,设置一个新的 model,其中只包含显示的字段,没有隐藏的字段。
这样就实现了当一个字段的值发生变化的时候,其他相应的字段隐藏、显示的功能。
看看效果
选中不同的分类,可以显示对应的字段。
分栏表单的设置
一个表单里的字段如果过多的话,可以采用分栏的方式,具体可以分为:card、tab、step 等多种形式。我们先设定一个 Interface,存放 配置信息
定义一个 Interface
export interface ISubMeta {
type: ESubType,
cardColCount: number,
cols: Array<{
title: string,
colIds: Array<number>
}>
}
记录一下如何分栏:
- type 分栏的形式,card、tab、setp 等形式。
- cardColCount:card 模式有效,card 分几列
- cols:栏目信息
- title:分栏的名称
- colIds:栏里的字段ID集合,数组类型
json 文件内容
我们在json里面记录需要的信息:
"subMeta": {
"type": "tabs",
"cardColCount": 2, // card 模式,可以有多列
"cols": [
{
"title": "数字类",
"colIds": [ 110, 111, 112 ]
},
{
"title": "时间",
"colIds": [ 130, 131, 132 ]
},
// 其他栏目
]
}
这样一个分栏的信息就做好了,可能你见到又是魔数,就要xxxx,其实我看着也一样头疼,所以,我们可以做一个支撑平台,来管理这些魔数的,可视化带拖拽的哦。
代码的封装
主要体现在 template 里面:
<el-form
v-bind="$attrs"
:model="model"
>
<el-row :gutter="15">
<el-col :span="cardSpan" // 设置列数,24:一列;12:两列;8:三列
v-for="(item, index) in cardOrder" :key="index" // 遍历栏目
>
<el-card class="box-card" style="margin-bottom: 10px;">
<template #header>
<div class="card-header">
<span>{{item.title}}</span>
</div>
</template> // 一个栏目里的若干字段
<base-item
:colOrder="item.colIds" // 设置一个栏目里面需要的字段ID集合。
:model="model"
>
</base-item>
</el-card>
</el-col>
</el-row>
</el-form>
- 最外层是 el-form
- 里面用 el-row、el-col 给栏目分列,如果不需要多列的话,可以去掉
- 然后是 el-card 做的栏目。
- 里面是表单字段列表。
同理可以实现 tab 、setp 等功能。
看看效果
- 双列的 card 表单
- tab 的表单
源码
演示
转载自:https://juejin.cn/post/7242918609856102455