Vue3组件(十)封装一个成熟的表单(下)

一个成熟的表单

表单表单,你已经长大了,你要学会:

  • 动态渲染
  • 支持单列、双列、多列
  • 支持调整布局
  • 支持表单验证
  • 支持调整排列(显示)顺序
  • 依据组件值显示需要的组件
  • 支持 item 扩展组件
  • 可以自动创建 model

这个表单控件是基于 element-plus 的 el-form 做的二次封装,所以首先感谢 element-plus 提供了这么强大的UI库,以前用jQuery做过类似的,但是非常麻烦,既不好看,可维护性、扩展性也差,好多想法都实现不了(技术有限)。
现在好了,站在巨人的肩膀上,以前的想法都可以实现了。

大图预警:这里有好几张大 gif 图片,流量不够的话慎开。。。

调整排列(显示)顺序以及调整布局

既然是动态渲染,那么组件的显示顺序也应该可以灵活调整。
所以设计了一个显示用的数组,把组件ID存放进去,v-for的时候,遍历这个数组就可以实现调整显示顺序的需求了。

单列的调整顺序,以及调整布局

动态表单003单列调整01.gif

导出MP4才400多k,导出gif居然2M多。真的好无语。
可以插入,可以交换,可以把几个短的挤到一行。

多列的调整顺序,以及调整布局

动态表单004双列调整2.gif

掐了一半没播,因为gif太大了。
还是插入、交换,可以让一个组件独占一行。

多列也是一样的操作。

依据组件值显示需要的组件

有的时候表单里的组件并不是都需要用户填写的,比如注册域名的时候,公司注册需要填写公司名称等信息,而个人注册显然没有公司信息可以填,那么这时候表单就需要根据用户的选择(公司、个人)来显示不同的字段来让用户填写。

这时候就需要一个运行时调整的功能。
其实现原理,还是通过调整那个排序用的数组来实现的。
先看一下效果:


动态表单005依据值显示其他组件.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当然是不需要手撸的,我这么懒。


005json数据.png

单列、双列、多列以及调整布局

这个其实就是个 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容