前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化。

内容摘要

  • 需求分析。
  • 定义 interface。
  • 定义表单控件的 props。
  • 定义 json 文件。
  • 基于 el-form 封装,实现依赖 json 渲染。
  • 实现多列、验证、分栏等功能。
  • 使用 slot 实现自定义扩展。
  • 自定义子控件。(下篇介绍)
  • 表单子控件的设计与实现。(下篇介绍)
  • 做个工具维护 json 文件。(下下篇介绍)

需求分析

表单是很常见的需求,各种网页、平台、后台管理等,都需要表单,有简单的、也有复杂的,但是目的一致:收集用户的数据,然后提交给后端。

表单控件的基础需求:

  • 可以依赖 JSON 渲染。
  • 依赖 JSON 创建 model。
  • 便于用户输入数据。
  • 验证用户输入的数据。
  • 便于程序员实现功能。
  • 可以多列。
  • 可以分栏。
  • 可以自定义扩展。
  • 其他。

el-form 实现了数据验证、自定义扩展等功能(还有漂亮的UI),我们可以直接拿过来封装,然后再补充点代码,实现多列、分栏、依赖 JSON 渲染等功能。

设计 interface

首先把表单控件需要的属性分为两大类:el-form 的属性、低代码需要的数据。

表单控件需要的属性的分类

整理一下做个脑图:

表单控件的接口

我们转换为接口的形式,再做个脑图:

然后我们定义具体的 interface

IFromProps:表单控件的接口 (包含所有属性,对应 json 文件)

  1. /**
  2. * 表单控件的属性
  3. */
  4. export interface IFromProps {
  5. /**
  6. * 表单的 model,对象,包含多个字段。
  7. */
  8. model: any,
  9. /**
  10. * 根据选项过滤后的 model,any
  11. */
  12. partModel?: any,
  13. /**
  14. * 表单控件需要的 meta
  15. */
  16. formMeta: IFromMeta,
  17. /**
  18. * 表单子控件的属性,IFormItem
  19. */
  20. itemMeta: IFormItemList,
  21. /**
  22. * 标签的后缀,string
  23. */
  24. labelSuffix: string,
  25. /**
  26. * 标签的宽度,string
  27. */
  28. labelWidth: string,
  29. /**
  30. * 控件的规格,ESize
  31. */
  32. size: ESize,
  33. /**
  34. * 其他扩展属性
  35. */
  36. [propName: string]: any
  37. }
  • model:表单数据,可以依据 JSON 创建。
  • partModel:组件联动后,只保留可见组件对应的数据。
  • formMeta:低代码需要的属性集合。
  • itemMeta:表单子控件需要的属性集合。
  • 其他:el-table 组件需要的属性,可以使用 $attrs 进行扩展。

本来想用这个接口约束组件的 props,但是有点小问题:

  • 如果用 Option API 的话,不支持这种形式的接口。
  • 如果使用 Composition API 的话,虽然支持,但是只能在组件内部定义 interface,暂时不支持从外部文件引入。

接口文件应该可以在外部定义,然后引入组件。如果不能的话,那就尴尬了。

所以只好暂时放弃对组件的 props 进行整体约束。

IFromMeta:低代码需要的属性接口

  1. /**
  2. * 低代码的表单需要的 meta
  3. */
  4. export interface IFromMeta {
  5. /**
  6. * 模块编号,综合使用的时候需要
  7. */
  8. moduleId: number | string,
  9. /**
  10. * 表单编号,一个模块可以有多个表单
  11. */
  12. formId: number | string,
  13. /**
  14. * 表单字段的排序、显示依据,Array<number | string>,
  15. */
  16. colOrder: Array<number | string>,
  17. /**
  18. * 表单的列数,分为几列 number,
  19. */
  20. columnsNumber: number
  21. /**
  22. * 分栏的设置,ISubMeta
  23. */
  24. subMeta: ISubMeta,
  25. /**
  26. * 验证信息,IRuleMeta
  27. */
  28. ruleMeta: IRuleMeta,
  29. /**
  30. * 子控件的联动关系,ILinkageMeta
  31. */
  32. linkageMeta: ILinkageMeta
  33. }
  • moduleId 模块编号,以后使用
  • formId 表单编号,一个模块可以有多个表单
  • colOrder 数组形式,表单里包含哪些字段?字段的先后顺序如何确定?就用这个数组。
  • columnsNumber 表单控件的列数,表单只能单列?太单调,支持多列才是王道。

ISubMeta:分栏的接口

  1. /**
  2. * 分栏表单的设置
  3. */
  4. export interface ISubMeta {
  5. type: ESubType, // 分栏类型:card、tab、step、"" (不分栏)
  6. cols: Array<{ // 栏目信息
  7. title: string, // 栏目名称
  8. colIds: Array<number> // 栏目里有哪些控件ID
  9. }>
  10. }

UI库提供了 el-card、el-tab、el-step等组件,我们可以使用这几个组件来实现多种分栏的形式。

IRule、IRuleMeta、:数据验证的接口

el-form 采用 async-validator 实现数据验证,所以我们可以去官网(https://github.com/yiminghe/async-validator)看看可以有哪些属性,针对这些属性指定一个接口(IRule),然后定义一个【字段编号-验证数组】的接口(IRuleMeta)


  1. /**
  2. * 一条验证规则,一个控件可以有多条验证规则
  3. */
  4. export interface IRule {
  5. /**
  6. * 验证时机:blur、change、click、keyup
  7. */
  8. trigger?: "blur" | "change" | "click" | "keyup",
  9. /**
  10. * 提示消息
  11. */
  12. message?: string,
  13. /**
  14. * 必填
  15. */
  16. required?: boolean,
  17. /**
  18. * 数据类型:any、date、url等
  19. */
  20. type?: string,
  21. /**
  22. * 长度
  23. */
  24. len?: number, // 长度
  25. /**
  26. * 最大值
  27. */
  28. max?: number,
  29. /**
  30. * 最小值
  31. */
  32. min?: number,
  33. /**
  34. * 正则
  35. */
  36. pattern?: string
  37. }
  38. /**
  39. * 表单的验证规则集合
  40. */
  41. export interface IRuleMeta {
  42. /**
  43. * 控件的ID作为key, 一个控件,可以有多条验证规则
  44. */
  45. [key: string | number]: Array<IRule>
  46. }

ILinkageMeta:组件联动的接口

有时候需要根据用户的选择显示对应的一组组件,那么如何实现呢?其实也比较简单,还是做一个key-value ,字段值作为key,需要显示的字段ID集合作为value。这样就可以了。

  1. /**
  2. * 显示控件的联动设置
  3. */
  4. export interface ILinkageMeta {
  5. /**
  6. * 控件的ID作为key,每个控件值对应一个数组,数组里面是需要显示的控件ID。
  7. */
  8. [key: string | number]: {
  9. /**
  10. * 控件的值作为key,后面的数组里存放需要显示的控件ID
  11. */
  12. [id: string | number]: Array<number>
  13. }
  14. }
  • 根据选项,显示对应的组件

定义表单控件的 props。

interface 都定义好了,我们来定义组件的 props(实现接口)。

这里采用 Option API 的方式,因为可以从外部文件引入接口,也就是说,可以实现复用。

  1. import type { PropType } from 'vue'
  2. import type {
  3. IFromMeta // 表单控件需要的 meta
  4. } from '../types/30-form'
  5. import type { IFormItem, IFormItemList } from '../types/20-form-item'
  6. import type { ESize } from '../types/enum'
  7. import { ESize as size } from '../types/enum'
  8. /**
  9. * 表单控件需要的属性
  10. */
  11. export const formProps = {
  12. /**
  13. * 表单的完整的 model
  14. */
  15. model: {
  16. type: Object as PropType<any>,
  17. required: true
  18. },
  19. /**
  20. * 根据选项过滤后的 model
  21. */
  22. partModel: {
  23. type: Object as PropType<any>,
  24. default: () => { return {}}
  25. },
  26. /**
  27. * 表单控件的 meta
  28. */
  29. formMeta: {
  30. type: Object as PropType<IFromMeta>,
  31. default: () => { return {}}
  32. },
  33. /**
  34. * 表单控件的子控件的 meta 集合
  35. */
  36. itemMeta: {
  37. type: Object as PropType<IFormItemList>,
  38. default: () => { return {}}
  39. },
  40. /**
  41. * 标签的后缀
  42. */
  43. labelSuffix: {
  44. type: String,
  45. default: ':'
  46. },
  47. /**
  48. * 标签的宽度
  49. */
  50. labelWidth: {
  51. type: String,
  52. default: '130px'
  53. },
  54. /**
  55. * 控件的规格
  56. */
  57. size: {
  58. type: Object as PropType<ESize>,
  59. default: size.small
  60. }
  61. }

在组件里的使用方式

那么如何使用呢?很简单,用 import 导入,然后解构即可。

  1. // 表单控件的属性
  2. import { formProps, formController } from '../map'
  3. export default defineComponent({
  4. name: 'nf-el-from-div',
  5. props: {
  6. ...formProps
  7. // 还可以设置其他属性
  8. },
  9. setup (props, context) {
  10. 略。。。
  11. }
  12. })

这样组件里的代码看起来也会很简洁。

定义 json 文件

我们做一个简单的 json 文件:

  1. {
  2. "formMeta": {
  3. "moduleId": 142,
  4. "formId": 14210,
  5. "columnsNumber": 2,
  6. "colOrder": [
  7. 90, 101, 100,
  8. 110, 111
  9. ],
  10. "linkageMeta": {
  11. "90": {
  12. "1": [90, 101, 100],
  13. "2": [90, 110, 111]
  14. }
  15. },
  16. "ruleMeta": {
  17. "101": [
  18. { "trigger": "blur", "message": "请输入活动名称", "required": true },
  19. { "trigger": "blur", "message": "长度在 3 到 5 个字符", "min": 3, "max": 5 }
  20. ]
  21. }
  22. },
  23. "itemMeta": {
  24. "90": {
  25. "columnId": 90,
  26. "colName": "kind",
  27. "label": "分类",
  28. "controlType": 153,
  29. "isClear": false,
  30. "defValue": 0,
  31. "extend": {
  32. "placeholder": "分类",
  33. "title": "编号"
  34. },
  35. "optionList": [
  36. {"value": 1, "label": "文本类"},
  37. {"value": 2, "label": "数字类"}
  38. ],
  39. "colCount": 2
  40. },
  41. "100": {
  42. "columnId": 100,
  43. "colName": "area",
  44. "label": "多行文本",
  45. "controlType": 100,
  46. "isClear": false,
  47. "defValue": 1000,
  48. "extend": {
  49. "placeholder": "多行文本",
  50. "title": "多行文本"
  51. },
  52. "colCount": 1
  53. },
  54. "101": {
  55. "columnId": 101,
  56. "colName": "text",
  57. "label": "文本",
  58. "controlType": 101,
  59. "isClear": false,
  60. "defValue": "",
  61. "extend": {
  62. "placeholder": "文本",
  63. "title": "文本"
  64. },
  65. "colCount": 1
  66. },
  67. "110": {
  68. "columnId": 110,
  69. "colName": "number1",
  70. "label": "数字",
  71. "controlType": 110,
  72. "isClear": false,
  73. "defValue": "",
  74. "extend": {
  75. "placeholder": "数字",
  76. "title": "数字"
  77. },
  78. "colCount": 1
  79. },
  80. "111": {
  81. "columnId": 111,
  82. "colName": "number2",
  83. "label": "滑块",
  84. "controlType": 111,
  85. "isClear": false,
  86. "defValue": "",
  87. "extend": {
  88. "placeholder": "滑块",
  89. "title": "滑块"
  90. },
  91. "colCount": 1
  92. }
  93. }
  94. }

温馨提示:JSON 文件不需要手撸哦。

基于 el-form 封装,实现依赖 json 渲染。

准备工作完毕,我们来二次封装 el-table 组件。

  1. <el-form
  2. :model="model"
  3. ref="formControl"
  4. :inline="false"
  5. class="demo-form-inline"
  6. :label-suffix="labelSuffix"
  7. :label-width="labelWidth"
  8. :size="size"
  9. v-bind="$attrs"
  10. >
  11. <el-row :gutter="15">
  12. <el-col
  13. v-for="(ctrId, index) in colOrder"
  14. :key="'form_' + ctrId + index"
  15. :span="formColSpan[ctrId]"
  16. v-show="showCol[ctrId]"
  17. ><!---->
  18. <transition name="el-zoom-in-top">
  19. <el-form-item
  20. :label="itemMeta[ctrId].label"
  21. :prop="itemMeta[ctrId].colName"
  22. :rules="ruleMeta[ctrId] ?? []"
  23. :label-width="itemMeta[ctrId].labelWidth??''"
  24. :size="size"
  25. v-show="showCol[ctrId]"
  26. >
  27. <component
  28. :is="formItemKey[itemMeta[ctrId].controlType]"
  29. :model="model"
  30. v-bind="itemMeta[ctrId]"
  31. >
  32. </component>
  33. </el-form-item>
  34. </transition>
  35. </el-col>
  36. </el-row>
  37. </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 写在单独文件里的原因。

  1. <el-form
  2. :model="model"
  3. ref="formControl"
  4. :inline="false"
  5. class="demo-form-inline"
  6. :label-suffix="labelSuffix"
  7. :label-width="labelWidth"
  8. :size="size"
  9. v-bind="$attrs"
  10. >
  11. <el-tabs
  12. v-model="tabIndex"
  13. type="border-card"
  14. >
  15. <el-tab-pane
  16. v-for="(item, index) in cardOrder"
  17. :key="'tabs_' + index"
  18. :label="item.title"
  19. :name="item.title"
  20. >
  21. <el-row :gutter="15">
  22. <el-col
  23. v-for="(ctrId, index) in item.colIds"
  24. :key="'form_' + ctrId + index"
  25. :span="formColSpan[ctrId]"
  26. v-show="showCol[ctrId]"
  27. >
  28. <transition name="el-zoom-in-top">
  29. <el-form-item
  30. :label="itemMeta[ctrId].label"
  31. :prop="itemMeta[ctrId].colName"
  32. v-show="showCol[ctrId]"
  33. >
  34. <component
  35. :is="formItemKey[itemMeta[ctrId].controlType]"
  36. :model="model"
  37. v-bind="itemMeta[ctrId]"
  38. >
  39. </component>
  40. </el-form-item>
  41. </transition>
  42. </el-col>
  43. </el-row>
  44. </el-tab-pane>
  45. </el-tabs>
  46. </el-form>
  • 分栏的表单(el-card)

  • 分标签的表单(el-tabs)

  • 分步骤的表单(el-steps)

使用 slot 实现自定义扩展。

虽然表单控件可以预设一些表单子控件,比如文本、数字、日期、选择等,但是客户的需求是千变万化的,固定的子控件肯定无法满足客户所有的需求,所以必须支持自定义扩展。

比较简单的扩展就是使用 slot 插槽,el-table 里面的 el-form-item 其实就是以 slot 的形式加入到 el-table 内部的。

所以我们也可以通过 slot 实现自定义的扩展:

  1. <nf-form
  2. v-form-drag="formMeta"
  3. :model="model"
  4. :partModel="model2"
  5. v-bind="formMeta"
  6. >
  7. <template v-slot:text>
  8. <h1>外部插槽 </h1>
  9. <input v-model="model.text"/>
  10. </template>
  11. </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库秒变低代码工具——表单篇(一)设计的更多相关文章

  1. 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件

    上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...

  2. 【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现

    内容摘要: 需求分析 定义 interface 定义 json 文件 定义列表控件的 props 基于 el-table 封装,实现依赖 json 渲染 实现内置功能:选择行(单选.多选),格式化.锁 ...

  3. 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具

    上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...

  4. Thief-Book 上班摸鱼神器

    Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...

  5. vscode插件(摸鱼神器-小霸王游戏机

    vscode插件(摸鱼神器-小霸王游戏机 步骤 vscode扩展搜索小霸王,点击下载即可. 使用 默认有一个demo小游戏,即超级玛丽. 本地仓库 可以通过local菜单上的添加按钮添加本地nes r ...

  6. QMUI UI库 控件 弹窗 列表 工具类 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  7. vue-cli3.0结合lib-flexible、px2rem实现移动端适配,完美解决第三方ui库样式变小问题

    公司最近做的一个移动端项目从搭框架到前端开发由我独立完成,以前做移动端适配用的媒体查询,这次想用点别的适配方案,然后就采用了vue-cli3.0结合lib-flexible.px2rem实现移动端适配 ...

  8. 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程

    下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...

  9. 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成

    一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...

随机推荐

  1. Java语言学习day39--8月14日

    今日内容介绍1.Map接口2.模拟斗地主洗牌发牌 ###01Map集合概述 A:Map集合概述: 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形 ...

  2. Python求解线性规划——PuLP使用教程

    简洁是智慧的灵魂,冗长是肤浅的藻饰.--莎士比亚<哈姆雷特> 1 PuLP 库的安装 如果您使用的是 Anaconda[1] 的话(事实上我也更推荐这样做),需要先激活你想要安装的虚拟环境 ...

  3. 论文解读(MERIT)《Multi-Scale Contrastive Siamese Networks for Self-Supervised Graph Representation Learning》

    论文信息 论文标题:Multi-Scale Contrastive Siamese Networks for Self-Supervised Graph Representation Learning ...

  4. keil工程当中实现printf重定向串口打印

    之前是完全不知道printf可以重定向设置 最近才发现还有这等好事,可以让printf直接实现串口打印 在网上找了很多资料,终于实现了我想要的效果 原理:printf是通过调用底部的fputc来实现打 ...

  5. centos 7.0 下安装FFmpeg软件 过程

    这几天由于需要编写一个语音识别功能,用到了百度语音识别接口,从web端或小程序端传上来的音频文件是aac或者mp3或者wav格式的,需要使用FFmpeg进行格式转换,以符合百度api的要求. 安装FF ...

  6. [笔记] 轮廓线 DP

    是状态 DP 的一种,主要是对于网格图状压,实现 \(O(1)\) 转移的一种处理方式. oooo---- ----x - 是状压了信息的位置,x 是当前更新的位置. 应用价值 可以一格一格考虑状态, ...

  7. 2.3 为什么建议使用虚拟机来安装Linux?

    笔者认为,通过虚拟机软件学习是初学者学习 Linux 的最佳方式. 在与部分读者的交流中,笔者发现,很多初学者都认为,学习 Linux 就必须将自己的电脑装成 Linux 系统或者必须要有真正的服务器 ...

  8. Asp.Net Core 7 preview 4 重磅新特性--限流中间件

    前言 限流是应对流量暴增或某些用户恶意攻击等场景的重要手段之一,然而微软官方从未支持这一重要特性,AspNetCoreRateLimit这一第三方库限流库一般作为首选使用,然而其配置参数过于繁多,对使 ...

  9. Event Loop 是什么?

    Event Loop 是什么? 本文写于 2020 年 12 月 6 日 广义上来说 Event Loop 并不是 JavaScript 独有的概念,他是一个计算机的通用概念. 狭义上来说,只有 No ...

  10. opencv学习之边缘检测

    边缘检测 是图像处理 过程中经常会涉及到的一个环节.而在计算机视觉 和 机器学习领域,边缘检测 用于 特征提取 和 特征检测 效果也是特别明显.而 openCV 中进行边缘检测的 算法 真是五花八门, ...