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

你可能感兴趣的:(Vue3组件(十)封装一个成熟的表单(下))