最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    正文概述 掘金(金色海洋)   2021-02-22   788

    一个成熟的表单

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

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

    实现动态渲染

    把表单需要的属性,统统放入json里面,然后用require(方便) 或者aioxs(可以热更新)加载进来,这样就可以实现动态渲染了。 比如要实现公司信息的添加、修改,那么只需要加载公司信息需要的json即可。 想要实现员工信息的添加、修改,那么只需要加载员工信息需要的json。

    总之,加载需要的json即可,不需要再一遍一遍的手撸代码了。

    那么这个神奇的 json 是啥样子的呢?文件有点长,直接看截图,更清晰一些。 Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    另外还有几个附带功能:

    • 支持单行下的合并。

    在单行的情况下,一些短的控件会比较占空间,我们可以把多个小的合并到一行。

    • 支持多行下的扩展。

    多行的情况下,一些长的控件需要占更多的空间,我们可以设置它多占几个格子。

    • 自动创建表单需要的 model。

    不需要手动写 model了。

    实现多行多列的表单

    再次感谢 el-form,真的很强大,不仅好看,还提供了验证功能,还有很多其他的功能。 只是好像只能横着排,或者竖着排。那么能不能多行多列呢?似乎没有直接提供。

    我们知道 el-row、el-col 可以实现多行多列的功能,那么能不能结合一下呢?官网也不直说,害的我各种找,还好找到了。(好吧,其实折腾了一阵着的table)

    二者结合一下就可以了,这里有个小技巧,el-row 只需要一个就可以,el-col 可以有多个,这样一行排满后,会自动排到下一行。

        <el-form
          ref="form"
          :inline="false"
          class="demo-form-inline"
          :model="formModel"
          label-suffix=":"
          label-width="130px"
          size="mini"
        >
          <el-row>
            <!--不循环row,直接循环col,放不下会自动往下换行。-->
            <el-col
              v-for="(ctrId, index) in formColSort"
              :key="'form_'+index"
              :span="formColSpan[ctrId]"
            >
              <el-form-item :label="getCtrMeta(ctrId).label">
                <!--表单item组件,采用动态组件的方式-->
                <component
                  :is="ctlList[getCtrMeta(ctrId).controlType]"
                  v-model="formModel[getCtrMeta(ctrId).colName]"
                  :meta="getCtrMeta(ctrId)"
                  @myChange="mySubmit">
                </component>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
    
    • formColSort

    存放组件ID的数组,决定了显示哪些组件以及显示的先后顺序。

    • v-for

    遍历 formColSort 得到组件ID,然后获取ID对应的span(确定占位)以及组件需要的meta。

    • formColSpan

    存放组件占位的数组。依据el-col的span的24格设定。

    • getCtrMeta(ctrId)

    根据组件ID获取组件的meta。 为啥要写个函数呢?因为model的属性不允许中括号套娃,所以只好写个函数。 为啥不用计算属性呢?计算属性好像不能传递参数。

    • component :is="xxx"

    Vue提供的动态组件,用这个可以方便加载不同类型的子组件。

    • ctlList

    组件字典,把组件类型变成对应的组件标签。

    自动创建 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
      }
    

    可以根据类型和默认值,设置 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]
        }
      }
    

    这样就可以得到一个简洁的 model 了。

    多列的表单

    这个是最复杂的,分为两种情况:单列的挤一挤、多列的抢位置。

    单列

    Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    单列的表单有一个特点,一行比较宽松,那么有时候就需要两个组件在一行里显示,其他的还是一行一个组件,那么要如何调整呢?

    这里做一个设定:

    • 一个组件一行的,记做1
    • 两个组件挤一行的,记做-2
    • 三个组件挤一行的,记做-3

    以此类推,理论上最多支持 -24,当然实际上似乎没有这么宽的显示器。

    这样记录之后,我们就可以判断,≥1的记做span=24,负数的,用24去除,得到的就是span的数字。当然记得取整数。

    为啥用负数做标记呢?就是为了区分开多列的调整。

    多列

    Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    调多了之后发现一个问题,看起来和单列调整后似乎一样的。

    Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    多列的表单有一个特点,一个格子比较小,有些组件太长放不下,这个时候这个组件就要抢后面的格子来用。

    那么我们还是做一个设定:

    • 一个组件占一格的,还是记做1
    • 一个组件占两格的,记做2
    • 一个组件占三格的,记做3

    以此类推。

    这样记录之后,我们可以判断,≤1的,记做 24 / 列数,大于1的记做 24/ 列数 * n。 这样就可以了,可以兼容单列的设置,不用因为单列变多列而调整设置。 只是有个小麻烦,占得格子太多的话,就会提取挤到下一行,而本行会出现“空缺”。 这个暂时靠人工调整吧。 毕竟哪个字段在前面,还是需要人工设置的。

    一顿分析猛如虎,一看代码没几行。

      // 根据配置里面的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
              }
            }
          }
        }
      }
    

    最后看看效果,可以动态设置列数:
    www.zhihu.com/zvideo/1347…

    依据用户的选择,显示对应的组件

    这个也是一个急需的功能,否则的话,动态渲染的表单控件适应性就会受到限制。 其实想想也不难,就是改一下 formColSort 里面的组件ID就好了。 我们设置一个watch来监听组件值的变化,然后把需要的组件ID设置给 formColSort 就可以了。

      // 监听组件值的变化,调整组件的显示以及显示顺序
      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])
              // 设置部分的 model
              createPartModel(ctl[v1])
            }
          })
        }
      }
    
    

    因为需要监听的组件可能不只一个,所以做了个循环,这样就可以监听所有需要的组件了。

    看看效果 www.zhihu.com/zvideo/1347…

    完整代码

    上面的代码比较凌乱,这里整体介绍一下。

    • el-form-manage.js

    表单组件的管理类,单独拿出来,这样就可以支持其他UI库了,比如antdv

    import { reactive, watch } from 'vue'
    
    /**
     * 表单的管理类
     * * 创建v-model
     * * 调整列数
     * * 合并
     */
    const formManage = (props, context) => {
      // 定义 完整的 v-model
      const formModel = reactive({})
      // 定义局部的 model
      const formPartModel = reactive({})
    
      // 确定一个组件占用几个格子
      const formColSpan = reactive({})
      // 定义排序依据
      const formColSort = reactive([])
      // 获取表单meta
      const formMeta = props.meta
      console.log('formMeta', formMeta)
      // 表单元素meta
      const formItemMeta = formMeta.itemMeta
      // 表单验证meta,备用
      // const formRuleMeta = formMeta.ruleMeta
    
      // 根据表单元素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
      }
      // 先运行一次
      createModel()
    
      // 向父组件提交 model
      const mySubmit = (val, controlId, colName) => {
        context.emit('update:modelValue', formModel)
        // 同步到部分model
        if (typeof formPartModel[colName] !== 'undefined') {
          formPartModel[colName] = formModel[colName]
        }
        context.emit('update:partModel', formPartModel)
      }
    
      // 依据用户选项,创建对应的 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]
        }
      }
    
      // 根据配置里面的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()
    
      // 监听组件值的变化,调整组件的显示以及显示顺序
      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])
              // 设置部分的 model
              createPartModel(ctl[v1])
            }
          })
        }
      }
    
      return {
        // 对象
        formModel, // v-model createModel()
        formPartModel, // 用户选择的组件的 model
        formColSpan, // 确定组件占位
        formColSort, // 确定组件排序
        // 函数
        createModel, // 创建 v-model
        setFormColSpan, // 设置组件占位
        setFormColSort, // 设置组件排序
        mySubmit // 提交
      }
    }
    
    export default formManage
    
    
    • el-form-map.js

    动态组件需要的字典

    import { defineAsyncComponent } from 'vue'
    
    /**
     * 组件里面注册控件用
     * * 文本
     * ** eltext 单行文本、电话、邮件、搜索
     * ** elarea 多行文本
     * ** elurl
     * * 数字
     * ** elnumber 数字
     * ** elrange 滑块
     * * 日期
     * ** eldate 日期、年月、年周、年
     * ** eltime 时间
     * * 选择
     * ** elcheckbox 勾选
     * ** elswitch 开关
     * ** elcheckboxs 多选组
     * ** elradios 单选组
     * ** elselect 下拉选择
     */
    const formItemList = {
      // 文本类 defineComponent
      eltext: defineAsyncComponent(() => import('./t-text.vue')),
      elarea: defineAsyncComponent(() => import('./t-area.vue')),
      elurl: defineAsyncComponent(() => import('./t-url.vue')),
      // 数字
      elnumber: defineAsyncComponent(() => import('./n-number.vue')),
      elrange: defineAsyncComponent(() => import('./n-range.vue')),
      // 日期、时间
      eldate: defineAsyncComponent(() => import('./d-date.vue')),
      eltime: defineAsyncComponent(() => import('./d-time.vue')),
      // 选择、开关
      elcheckbox: defineAsyncComponent(() => import('./s-checkbox.vue')),
      elswitch: defineAsyncComponent(() => import('./s-switch.vue')),
      elcheckboxs: defineAsyncComponent(() => import('./s-checkboxs.vue')),
      elradios: defineAsyncComponent(() => import('./s-radios.vue')),
      elselect: defineAsyncComponent(() => import('./s-select.vue')),
      elselwrite: defineAsyncComponent(() => import('./s-selwrite.vue'))
    }
    
    /**
     * 动态组件的字典,便于v-for循环里面设置控件
     */
    const formItemListKey = {
      // 文本类
      100: formItemList.elarea, // 多行文本
      101: formItemList.eltext, // 单行文本
      102: formItemList.eltext, // 密码
      103: formItemList.eltext, // 电话
      104: formItemList.eltext, // 邮件
      105: formItemList.elurl, // url
      106: formItemList.eltext, // 搜索
      // 数字
      120: formItemList.elnumber, // 数组
      121: formItemList.elrange, // 滑块
      // 日期、时间
      110: formItemList.eldate, // 日期
      111: formItemList.eldate, // 日期 + 时间
      112: formItemList.eldate, // 年月
      113: formItemList.eldate, // 年周
      114: formItemList.eldate, // 年
      115: formItemList.eltime, // 任意时间
      116: formItemList.eltime, // 选择固定时间
      // 选择、开关
      150: formItemList.elcheckbox, // 勾选
      151: formItemList.elswitch, // 开关
      152: formItemList.elcheckboxs, // 多选组
      153: formItemList.elradios, // 单选组
      160: formItemList.elselect, // 下拉
      161: formItemList.elselwrite, // 下拉多选
      162: formItemList.elselect // 下拉联动
    
    }
    
    export default {
      formItemList,
      formItemListKey
    }
    
    
    • el-form-div.vue

    表单控件的代码 模板

      <div >
        <el-form
          ref="form"
          :inline="false"
          class="demo-form-inline"
          :model="formModel"
          label-suffix=":"
          label-width="130px"
          size="mini"
        >
          <el-row>
            <!--不循环row,直接循环col,放不下会自动往下换行。-->
            <el-col
              v-for="(ctrId, index) in formColSort"
              :key="'form_'+index"
              :span="formColSpan[ctrId]"
            >
              <el-form-item :label="getCtrMeta(ctrId).label">
                <!--表单item组件,采用动态组件的方式-->
                <component
                  :is="ctlList[getCtrMeta(ctrId).controlType]"
                  v-model="formModel[getCtrMeta(ctrId).colName]"
                  :meta="getCtrMeta(ctrId)"
                  @myChange="mySubmit">
                </component>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </div>
    

    js

    import { watch } from 'vue'
    import elFormConfig from '@/components/nf-el-form/el-form-map.js'
    import formManage from '@/components/nf-el-form/el-form-manage.js'
    
    export default {
      name: 'el-form-div',
      components: {
        ...elFormConfig.formItemList
      },
      props: {
        modelValue: Object,
        partModel: Object,
        meta: Object
      },
      setup (props, context) {
        // 控件字典
        const ctlList = elFormConfig.formItemListKey
    
        // 表单管理类
        const {
          formModel, // 依据meta,创建 Model
          formColSpan, // 依据meta,创建 span
          formColSort,
          setFormColSpan,
          setFormColSort, // 设置组件排序
          mySubmit
        } = formManage(props, context)
    
        // 监听列数的变化
        watch(() => props.meta.formColCount, (v1, v2) => {
          setFormColSpan()
        })
        // 监听reload
        watch(() => props.meta.reload, (v1, v2) => {
          setFormColSpan()
          setFormColSort()
        })
    
        // 监听组件值的变化,
        // 依据ID获取组件的meta,因为model不支持【】嵌套
        const getCtrMeta = (id) => {
          return props.meta.itemMeta[id] || {}
        }
    
        return {
          formModel,
          formColSpan,
          formColSort,
          ctlList,
          getCtrMeta,
          mySubmit
        }
      }
    }
    

    这里就简单多了,因为实现具体功能的js代码都分离出去了。要么做成子组件,要么组成独立的js文件。 这里主要就是负责重新渲染表单组件。

    表单验证

    这个使用 el-form 提供的验证功能。 目前暂时还没有归纳好 el-form 的验证,因为需要把这个验证用的数据写入到json里面,然后读取出来设置好即可。 所以肯定没难度,只是需要点时间。

    支持 扩展组件

    自带的组件肯定是不够的,因为用户的需求总是千变万化的,那么新组件如何加入到表单控件里面呢?可以按照接口定义封装成符合要求的组件,然后做一个map字典,就可以设置进去了。

    因为接口统一,所以可以适应表单控件的调用。

    简单的方法是,直接修改两个js文件。 如果不方便修改的话,也可以通过属性传递进来。目前暂时还没有想好细节,不过似乎不是太难。

    源码

    github.com/naturefwvue…


    起源地下载网 » Vue3组件(九)Vue3+element+json实现一个动态渲染的表单控件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元