【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计
前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化。
内容摘要
- 需求分析。
- 定义 interface。
- 定义表单控件的 props。
- 定义 json 文件。
- 基于 el-form 封装,实现依赖 json 渲染。
- 实现多列、验证、分栏等功能。
- 使用 slot 实现自定义扩展。
- 自定义子控件。(下篇介绍)
- 表单子控件的设计与实现。(下篇介绍)
- 做个工具维护 json 文件。(下下篇介绍)
需求分析
表单是很常见的需求,各种网页、平台、后台管理等,都需要表单,有简单的、也有复杂的,但是目的一致:收集用户的数据,然后提交给后端。
表单控件的基础需求:
- 可以依赖 JSON 渲染。
- 依赖 JSON 创建 model。
- 便于用户输入数据。
- 验证用户输入的数据。
- 便于程序员实现功能。
- 可以多列。
- 可以分栏。
- 可以自定义扩展。
- 其他。
el-form 实现了数据验证、自定义扩展等功能(还有漂亮的UI),我们可以直接拿过来封装,然后再补充点代码,实现多列、分栏、依赖 JSON 渲染等功能。
设计 interface
首先把表单控件需要的属性分为两大类:el-form 的属性、低代码需要的数据。
表单控件需要的属性的分类
整理一下做个脑图:
表单控件的接口
我们转换为接口的形式,再做个脑图:
然后我们定义具体的 interface
IFromProps:表单控件的接口 (包含所有属性,对应 json 文件)
/**
* 表单控件的属性
*/
export interface IFromProps {
/**
* 表单的 model,对象,包含多个字段。
*/
model: any,
/**
* 根据选项过滤后的 model,any
*/
partModel?: any,
/**
* 表单控件需要的 meta
*/
formMeta: IFromMeta,
/**
* 表单子控件的属性,IFormItem
*/
itemMeta: IFormItemList,
/**
* 标签的后缀,string
*/
labelSuffix: string,
/**
* 标签的宽度,string
*/
labelWidth: string,
/**
* 控件的规格,ESize
*/
size: ESize,
/**
* 其他扩展属性
*/
[propName: string]: any
}
- model:表单数据,可以依据 JSON 创建。
- partModel:组件联动后,只保留可见组件对应的数据。
- formMeta:低代码需要的属性集合。
- itemMeta:表单子控件需要的属性集合。
- 其他:el-table 组件需要的属性,可以使用 $attrs 进行扩展。
本来想用这个接口约束组件的 props,但是有点小问题:
- 如果用 Option API 的话,不支持这种形式的接口。
- 如果使用 Composition API 的话,虽然支持,但是只能在组件内部定义 interface,暂时不支持从外部文件引入。
接口文件应该可以在外部定义,然后引入组件。如果不能的话,那就尴尬了。
所以只好暂时放弃对组件的 props 进行整体约束。
IFromMeta:低代码需要的属性接口
/**
* 低代码的表单需要的 meta
*/
export interface IFromMeta {
/**
* 模块编号,综合使用的时候需要
*/
moduleId: number | string,
/**
* 表单编号,一个模块可以有多个表单
*/
formId: number | string,
/**
* 表单字段的排序、显示依据,Array<number | string>,
*/
colOrder: Array<number | string>,
/**
* 表单的列数,分为几列 number,
*/
columnsNumber: number
/**
* 分栏的设置,ISubMeta
*/
subMeta: ISubMeta,
/**
* 验证信息,IRuleMeta
*/
ruleMeta: IRuleMeta,
/**
* 子控件的联动关系,ILinkageMeta
*/
linkageMeta: ILinkageMeta
}
- moduleId 模块编号,以后使用
- formId 表单编号,一个模块可以有多个表单
- colOrder 数组形式,表单里包含哪些字段?字段的先后顺序如何确定?就用这个数组。
- columnsNumber 表单控件的列数,表单只能单列?太单调,支持多列才是王道。
ISubMeta:分栏的接口
/**
* 分栏表单的设置
*/
export interface ISubMeta {
type: ESubType, // 分栏类型:card、tab、step、"" (不分栏)
cols: Array<{ // 栏目信息
title: string, // 栏目名称
colIds: Array<number> // 栏目里有哪些控件ID
}>
}
UI库提供了 el-card、el-tab、el-step等组件,我们可以使用这几个组件来实现多种分栏的形式。
IRule、IRuleMeta、:数据验证的接口
el-form 采用 async-validator
实现数据验证,所以我们可以去官网(https://github.com/yiminghe/async-validator)看看可以有哪些属性,针对这些属性指定一个接口(IRule),然后定义一个【字段编号-验证数组】的接口(IRuleMeta)
/**
* 一条验证规则,一个控件可以有多条验证规则
*/
export interface IRule {
/**
* 验证时机:blur、change、click、keyup
*/
trigger?: "blur" | "change" | "click" | "keyup",
/**
* 提示消息
*/
message?: string,
/**
* 必填
*/
required?: boolean,
/**
* 数据类型:any、date、url等
*/
type?: string,
/**
* 长度
*/
len?: number, // 长度
/**
* 最大值
*/
max?: number,
/**
* 最小值
*/
min?: number,
/**
* 正则
*/
pattern?: string
}
/**
* 表单的验证规则集合
*/
export interface IRuleMeta {
/**
* 控件的ID作为key, 一个控件,可以有多条验证规则
*/
[key: string | number]: Array<IRule>
}
ILinkageMeta:组件联动的接口
有时候需要根据用户的选择显示对应的一组组件,那么如何实现呢?其实也比较简单,还是做一个key-value ,字段值作为key,需要显示的字段ID集合作为value。这样就可以了。
/**
* 显示控件的联动设置
*/
export interface ILinkageMeta {
/**
* 控件的ID作为key,每个控件值对应一个数组,数组里面是需要显示的控件ID。
*/
[key: string | number]: {
/**
* 控件的值作为key,后面的数组里存放需要显示的控件ID
*/
[id: string | number]: Array<number>
}
}
- 根据选项,显示对应的组件
定义表单控件的 props。
interface 都定义好了,我们来定义组件的 props(实现接口)。
这里采用 Option API 的方式,因为可以从外部文件引入接口,也就是说,可以实现复用。
import type { PropType } from 'vue'
import type {
IFromMeta // 表单控件需要的 meta
} from '../types/30-form'
import type { IFormItem, IFormItemList } from '../types/20-form-item'
import type { ESize } from '../types/enum'
import { ESize as size } from '../types/enum'
/**
* 表单控件需要的属性
*/
export const formProps = {
/**
* 表单的完整的 model
*/
model: {
type: Object as PropType<any>,
required: true
},
/**
* 根据选项过滤后的 model
*/
partModel: {
type: Object as PropType<any>,
default: () => { return {}}
},
/**
* 表单控件的 meta
*/
formMeta: {
type: Object as PropType<IFromMeta>,
default: () => { return {}}
},
/**
* 表单控件的子控件的 meta 集合
*/
itemMeta: {
type: Object as PropType<IFormItemList>,
default: () => { return {}}
},
/**
* 标签的后缀
*/
labelSuffix: {
type: String,
default: ':'
},
/**
* 标签的宽度
*/
labelWidth: {
type: String,
default: '130px'
},
/**
* 控件的规格
*/
size: {
type: Object as PropType<ESize>,
default: size.small
}
}
在组件里的使用方式
那么如何使用呢?很简单,用 import 导入,然后解构即可。
// 表单控件的属性
import { formProps, formController } from '../map'
export default defineComponent({
name: 'nf-el-from-div',
props: {
...formProps
// 还可以设置其他属性
},
setup (props, context) {
略。。。
}
})
这样组件里的代码看起来也会很简洁。
定义 json 文件
我们做一个简单的 json 文件:
{
"formMeta": {
"moduleId": 142,
"formId": 14210,
"columnsNumber": 2,
"colOrder": [
90, 101, 100,
110, 111
],
"linkageMeta": {
"90": {
"1": [90, 101, 100],
"2": [90, 110, 111]
}
},
"ruleMeta": {
"101": [
{ "trigger": "blur", "message": "请输入活动名称", "required": true },
{ "trigger": "blur", "message": "长度在 3 到 5 个字符", "min": 3, "max": 5 }
]
}
},
"itemMeta": {
"90": {
"columnId": 90,
"colName": "kind",
"label": "分类",
"controlType": 153,
"isClear": false,
"defValue": 0,
"extend": {
"placeholder": "分类",
"title": "编号"
},
"optionList": [
{"value": 1, "label": "文本类"},
{"value": 2, "label": "数字类"}
],
"colCount": 2
},
"100": {
"columnId": 100,
"colName": "area",
"label": "多行文本",
"controlType": 100,
"isClear": false,
"defValue": 1000,
"extend": {
"placeholder": "多行文本",
"title": "多行文本"
},
"colCount": 1
},
"101": {
"columnId": 101,
"colName": "text",
"label": "文本",
"controlType": 101,
"isClear": false,
"defValue": "",
"extend": {
"placeholder": "文本",
"title": "文本"
},
"colCount": 1
},
"110": {
"columnId": 110,
"colName": "number1",
"label": "数字",
"controlType": 110,
"isClear": false,
"defValue": "",
"extend": {
"placeholder": "数字",
"title": "数字"
},
"colCount": 1
},
"111": {
"columnId": 111,
"colName": "number2",
"label": "滑块",
"controlType": 111,
"isClear": false,
"defValue": "",
"extend": {
"placeholder": "滑块",
"title": "滑块"
},
"colCount": 1
}
}
}
温馨提示:JSON 文件不需要手撸哦。
基于 el-form 封装,实现依赖 json 渲染。
准备工作完毕,我们来二次封装 el-table 组件。
<el-form
:model="model"
ref="formControl"
:inline="false"
class="demo-form-inline"
:label-suffix="labelSuffix"
:label-width="labelWidth"
:size="size"
v-bind="$attrs"
>
<el-row :gutter="15">
<el-col
v-for="(ctrId, index) in colOrder"
:key="'form_' + ctrId + index"
:span="formColSpan[ctrId]"
v-show="showCol[ctrId]"
><!---->
<transition name="el-zoom-in-top">
<el-form-item
:label="itemMeta[ctrId].label"
:prop="itemMeta[ctrId].colName"
:rules="ruleMeta[ctrId] ?? []"
:label-width="itemMeta[ctrId].labelWidth??''"
:size="size"
v-show="showCol[ctrId]"
>
<component
:is="formItemKey[itemMeta[ctrId].controlType]"
:model="model"
v-bind="itemMeta[ctrId]"
>
</component>
</el-form-item>
</transition>
</el-col>
</el-row>
</el-form>
通过 props 绑定 el-table 的属性
props 里面定义的属性,直接绑定即可,比如:label-suffix="labelSuffix"
。通过 $attrs 绑定 el-table 的属性
props 里面没有定义的属性,会保存在 $attrs 里面,可以通过v-bind="$attrs"
的方式绑定,既方便又支持扩展。使用动态组件(component)加载表单子组件。
实现数据验证,设置 rules 属性即可,
:rules="ruleMeta[ctrId] ?? []"
。
实现多列
使用 el-row、el-col 实现多列的效果。
el-col 分为了24个格子,通过一个字段占用多少个格子的方式实现多列,也就是说,最多支持 24列。当然肯定用不了这么多。
所以,我们通过各种参数计算好 span 即可。篇幅有限,具体代码不介绍了,感兴趣的话可以看源码。
- 单列表单
- 双列表单
- 三列表单
多列表单
因为 el-col 的 span 最大是 24,所以最多支持24列。支持调整布局
三列表单里面 URL组件就占用了一整行,这类的调整都是很方便实现的。
分栏
这里分为多个表单控件,以便于实现多种分栏方式,并不是在一个组件内部通过 v-if 来做各种判断,这也是我需要把 interface 写在单独文件里的原因。
<el-form
:model="model"
ref="formControl"
:inline="false"
class="demo-form-inline"
:label-suffix="labelSuffix"
:label-width="labelWidth"
:size="size"
v-bind="$attrs"
>
<el-tabs
v-model="tabIndex"
type="border-card"
>
<el-tab-pane
v-for="(item, index) in cardOrder"
:key="'tabs_' + index"
:label="item.title"
:name="item.title"
>
<el-row :gutter="15">
<el-col
v-for="(ctrId, index) in item.colIds"
:key="'form_' + ctrId + index"
:span="formColSpan[ctrId]"
v-show="showCol[ctrId]"
>
<transition name="el-zoom-in-top">
<el-form-item
:label="itemMeta[ctrId].label"
:prop="itemMeta[ctrId].colName"
v-show="showCol[ctrId]"
>
<component
:is="formItemKey[itemMeta[ctrId].controlType]"
:model="model"
v-bind="itemMeta[ctrId]"
>
</component>
</el-form-item>
</transition>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
- 分栏的表单(el-card)
- 分标签的表单(el-tabs)
- 分步骤的表单(el-steps)
使用 slot 实现自定义扩展。
虽然表单控件可以预设一些表单子控件,比如文本、数字、日期、选择等,但是客户的需求是千变万化的,固定的子控件肯定无法满足客户所有的需求,所以必须支持自定义扩展。
比较简单的扩展就是使用 slot 插槽,el-table 里面的 el-form-item 其实就是以 slot 的形式加入到 el-table 内部的。
所以我们也可以通过 slot 实现自定义的扩展:
<nf-form
v-form-drag="formMeta"
:model="model"
:partModel="model2"
v-bind="formMeta"
>
<template v-slot:text>
<h1>外部插槽 </h1>
<input v-model="model.text"/>
</template>
</nf-form>
nf-form 就是封装后的表单控件,设置属性和 model 后就可使用了,很方便。
如果想扩展的话,可以使用 <template v-slot:text>
的方式,里面的 【text】 是字段名称(model 的属性)。
也就是说,我们是依据字段名称来区分 slot 的。
实现 interface 扩展子组件
虽然使用 slot 可以扩展子组件,但是对于子组件的结构复杂的情况,每次都使用 slot 的话,明显不方便复用。
既然都定义 interface 了,那么为何不实现接口制作组件,然后变成新的表单子组件呢?
当然可以了,具体方法下次再介绍。
关于 TypeScript
为什么要定义 interface ?
定义 interface 可以比较清晰的表明结构和意图,然后实现接口即可。避免过段时间自己都忘记含义。JSON 文件导入后会自动解析为 js 的对象,那么还用 interface 做什么?
这就比较尴尬了,也是我一直没有采用 TS 的原因之一。
TS只能在编写代码、打包时做检查,但是在运行时就帮不上忙了,所以对于低代码的帮助有限。
源码和演示
core:https://gitee.com/naturefw-code/nf-rollup-ui-controller
二次封装: https://gitee.com/naturefw-code/nf-rollup-ui-element-plus
演示: https://naturefw-code.gitee.io/nf-rollup-ui-element-plus/
【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计的更多相关文章
- 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件
上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...
- 【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现
内容摘要: 需求分析 定义 interface 定义 json 文件 定义列表控件的 props 基于 el-table 封装,实现依赖 json 渲染 实现内置功能:选择行(单选.多选),格式化.锁 ...
- 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具
上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...
- Thief-Book 上班摸鱼神器
Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...
- vscode插件(摸鱼神器-小霸王游戏机
vscode插件(摸鱼神器-小霸王游戏机 步骤 vscode扩展搜索小霸王,点击下载即可. 使用 默认有一个demo小游戏,即超级玛丽. 本地仓库 可以通过local菜单上的添加按钮添加本地nes r ...
- QMUI UI库 控件 弹窗 列表 工具类 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- vue-cli3.0结合lib-flexible、px2rem实现移动端适配,完美解决第三方ui库样式变小问题
公司最近做的一个移动端项目从搭框架到前端开发由我独立完成,以前做移动端适配用的媒体查询,这次想用点别的适配方案,然后就采用了vue-cli3.0结合lib-flexible.px2rem实现移动端适配 ...
- 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程
下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...
- 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成
一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...
随机推荐
- babel 的介绍及其配置
vue/cli -- babel Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其 ...
- 史上最全log4j2远程命令执行漏洞汇总报告
已投稿信安之路公众号,文章链接
- 前端性能优化之js,css调用优化
规则1:减少HTTP请求 把多个JS请求合并为一个JS请求,把多个CSS请求合并为一个CSS请求.从而减少从客户端向服务器端的请求数. 规则3:添加Expires头 用http ...
- transition 动画过渡
1. html 结构 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- 你不知道的下划线属性-text-decoration
大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...
- linux脚本执行jar包运行
以下为linux下运行jar包的脚本(只需替换jar包名称): #!/bin/bash #这里可替换为你自己的执行程序,其他代码无需更改 APP_NAME=ruoyi-admin.jar cd `di ...
- Promql基础语法2
数据样本 直方图类型 delta函数 运算操作 数学运算 node_disk_info / 100 当瞬时向量与标量之间进行数学运算时,数学运算符会依次作用域瞬时向量中的每一个样本值,从而得到一组新的 ...
- 关于VR(虚拟现实)的探讨
从外部来看:一个完整的系统由输入和输出组成,人体也不例外.人的输入系统一般称为感官系统,主要由口耳眼鼻舌和皮肤组成,它们对应于味觉.听觉.视觉.嗅觉和触觉.生而为人,我们对于外部世界的感知主要来自于上 ...
- 北航内核操作系统-lab0
1.lab0环境介绍. 2.进入实验界面. 3.进入实战测试. 任务要求: 3.1编写斐波那契数列. 3.2编写Makefile脚本. Makefile介绍: make命令执行时,需要一个 Makef ...
- 《你不知道的JS》上