likes
comments
collection
share

【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等

作者站长头像
站长
· 阅读数 81

上篇,上一篇介绍了 Interface 以及 controller 的一部分,这一篇继续介绍后续功能。

  • 双列的表单

【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等

自动创建 model

既然有了meta,那么还需要手动创建 model 吗?其实在低代码里面,model都是运行时创建的吧,否则怎么“低”呢?

不过呢,我们还是应该两手准备,既可以支持低代码的方式,又可以支持常规开发的方式,因为只有这样,可方便切换到常规模式,否则还得自己弄一个 model,多麻烦。

获取名称和类型

meta 里面用字段名称,拿出来作为key即可,那么类型呢?需要在meta里面设置一个类型的描述吗?我觉得不用。因为我们可以使用控件类型来确定。

我们先来做个字典:

const dicType = {
  // 文本
  100: '',
  101: '',
  102: '',
  103: '',
  104: '',
  105: '',
  106: '',
  107: '',
  108: '',
  // 数字
  110: 0,
  111: 0,
  112: 0,
  // 日期
  120: '',
  121: '',
  122: '',
  123: '',
  124: '',
  // 日期范围
  125: [],
  126: [],
  127: [],
  128: [],
  // 时间
  130: '',
  131: [],
  132: '',
  133: [],
  // 上传
  140: '',
  141: '',
  142: '',
  // 选择
  150: false,// 勾选
  151: false, // 开关
  152: [],// 多选组
  153: '',// 单选组
  // 下拉
  160: '',// 下拉单选
  161: [],// 下拉多选
  162: '',// 分组下拉单选
  163: [],// 分组下拉多选
  164: [],// 下拉联动
  165: '',// 树状下拉
  166: []// 树状多选
}

这样就可以把控件的类型和model的属性类型联系起来。日期控件,可以使用 string,也可以使用Date,还可以使用时间戳,其实还可以使用 null。不过初始化的时候,我们设置为 ‘’ 即可。

/**
 * 创建表单的 model,传入 input 这类的 meta,依据其中的字段名称和控件类型,字段创建。
 * @param meta 表单子控件的 meta
 * @param colOrder 字段ID,数组。需要哪些字段,以及顺序
 */
export default function createModel<T>(
    meta: IFormChildPropsList,
    colOrder: Array<number | string>
  ): T {
    // 定义一个 model
    const formModel = {}

    // 依据 meta,创建 model
    colOrder.forEach(key => {
      const _meta: IFormChildMeta = meta[key].meta
      if (_meta.controlType < 200) {
        // 表单内置组件
        formModel[_meta.colName] = dicType[_meta.controlType]
      } else {
        formModel[_meta.colName] = ''
      }

      // 看看有没有设置默认值
      if (typeof _meta.defValue !== 'undefined' ) {
        switch (_meta.defValue) {
          case '':
            break
          case '{}':
            formModel[_meta.colName] = {}
            break
          case '[]':
            formModel[_meta.colName] = []
            break
          case '{{now}}':
            formModel[_meta.colName] = new Date()
            break
          default:
            formModel[_meta.colName] = _meta.defValue
            break
        }
      }

      if (Array.isArray(formModel[_meta.colName] )){
        // 数组类型,有可能需要对应多个字段
        moreColName(_meta , formModel)
      }

    })

    const re = reactive(formModel)
    return re as T
  }

先定义一个对象,然后遍历 meta,把字段名作为key加到对象里面,然后设置类型和默认值,最后套上 reactive,强制转换成泛型 T。

当然,这种方式比较简单粗暴,无法保证自动创建的 model 可以复合泛型 T 的类型。只是,我只能想到这么多了。

json 里面有字段名称还有字段类型和默认值,那么我们可以自动创建一个model。

创建 model 的位置

一开始,在内部和外部都可以创建,还可以兼容,但是后来想想,何必为难自己呢,然后使用者还各种懵逼,所以,干脆简单点。

提供一个创建 model 的工具,可以用泛型设置类型,在外部创建 model,可以用这个工具,也可以用其他方式,反正提供一个对象即可。

联动筛选

当一个组件的值发生变化的时候,需要显示(隐藏)哪些字段的联动。我们还是先定义一个 Interface:

定义 Interface

// 显示控件的联动设置
export interface ILinkageMeta {
  [key: string | number]: {
    [id: string | number]: Array<number>
  }
}

记录一下联动情况,当一个字段的值发生变化时,需要显示哪些字段:

  • key:控件的ID作为key,每个控件值对应一个数组,数组里面是需要显示的控件ID。
    • id:控件的值作为key,后面的数组里存放需要显示的控件ID
    • 选择这个值之后需要显示的字段ID,数组

json 文件

在json 文件里面设置对应的信息:

   "linkageMeta": {
      "90": {
        "1": [90, 101, 100, 102, 103, 104, 105, 106, 107, 108],
        "2": [90, 110, 111, 112],
        "3": [90, 120, 121, 122, 123, 124, 125, 126, 127, 128],
        "4": [90, 130, 131, 132],
        "5": [90, 150, 151, 152, 153],
        "6": [90, 160, 161, 162, 163, 165, 166, 164]
      },
      其他字段。。。
   }

编号为 90 的字段,有 6 个值,每个值对应一组字段ID,当选择一个值的时候,对应的字段将会被显示,其他的会隐藏。

封装代码

/**
 * 设置备选项和子控件的联动
 * @param formMeta 表单控件的meta
 * @param model 表单完整的 model
 * @param partModel 表单部分 的 model
 * @returns 
 */
export default function setControlShow<T>(
  formMeta: IFromMeta,
  itemMeta: IFormChildMetaList,
  model: T,
  partModel: any
) {
  // 解构需要的数据
  const {
    linkageMeta,
    colOrder
  } = formMeta
  
  // 设置字段的是否可见
  const showCol = reactive<ShowCol>({})

  // 设置联动
  const setFormColShow = () => {
    // 数据变化,联动组件的显示
      for (const key in linkageMeta) {
        // 配置里面设置的主动组件。
        const mainComp = linkageMeta[key]
        // 主动组件的字段名称
        const colName = itemMeta[key].colName
        // 监听组件的值,有变化就重新设置局部 model
        if (typeof model[colName] !== 'undefined') {
          // 监听主动组件的值的变化
          watch(() => model[colName], (modelValue, oldValue) => {
 
           // 单选组的选项,先让字段都不可见,
            Object.keys(itemMeta).forEach(key => {
              showCol[key] = false
            })
            // 配置信息里对应的字段ID集合,设置为可见
            mainComp[modelValue].forEach(id => {
              showCol[id] = true
             })
               
            // 设置部分的 model
            createPartModel<T>(model, partModel, itemMeta, showCol)
          },
          { immediate: true })
        }
      }
      // 监听完整model的值的变化,同步值
      if (typeof partModel !== 'undefined') {
        watch(model, () => {
          for (const key in model) {
            if (typeof(partModel[key]) !== 'undefined') {
              partModel[key] = model[key]
            }
          }
        })
      }
  }
  
  return {
    showCol,
    setFormColShow
  }
}

思路:

  • 获取配置信息里面的 linkageMeta 信息
  • 遍历里面的字段
  • 监听对应的 model 的属性值的变化
  • 当变化的时候,依据值对应的字段ID,设置字段是否显示。
  • 依据显示的字段,设置一个新的 model,其中只包含显示的字段,没有隐藏的字段。

这样就实现了当一个字段的值发生变化的时候,其他相应的字段隐藏、显示的功能。

看看效果

【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等

选中不同的分类,可以显示对应的字段。

分栏表单的设置

一个表单里的字段如果过多的话,可以采用分栏的方式,具体可以分为:card、tab、step 等多种形式。我们先设定一个 Interface,存放 配置信息

定义一个 Interface

export interface ISubMeta {
  type: ESubType, 
  cardColCount: number, 
  cols: Array<{ 
    title: string, 
    colIds:  Array<number> 
  }>
}

记录一下如何分栏:

  • type 分栏的形式,card、tab、setp 等形式。
  • cardColCount:card 模式有效,card 分几列
  • cols:栏目信息
    • title:分栏的名称
    • colIds:栏里的字段ID集合,数组类型

json 文件内容

我们在json里面记录需要的信息:

    "subMeta": {
      "type": "tabs",
      "cardColCount": 2, // card 模式,可以有多列
      "cols": [
        {
          "title": "数字类",
          "colIds": [ 110, 111, 112 ]
        },
        {
          "title": "时间",
          "colIds": [ 130, 131, 132 ]
        },
        // 其他栏目
      ]
    }

这样一个分栏的信息就做好了,可能你见到又是魔数,就要xxxx,其实我看着也一样头疼,所以,我们可以做一个支撑平台,来管理这些魔数的,可视化带拖拽的哦。

代码的封装

主要体现在 template 里面:

  <el-form
    v-bind="$attrs"
    :model="model"
  >
      <el-row :gutter="15">
        <el-col :span="cardSpan"  // 设置列数24一列12两列8三列
          v-for="(item, index) in cardOrder" :key="index" // 遍历栏目
        > 
          <el-card class="box-card" style="margin-bottom: 10px;">
            <template #header>
              <div class="card-header">
                <span>{{item.title}}</span>
              </div>
            </template> // 一个栏目里的若干字段
            <base-item
              :colOrder="item.colIds" // 设置一个栏目里面需要的字段ID集合
              :model="model"
            >
            </base-item>
          </el-card>    
        </el-col>
      </el-row>
  </el-form>
  • 最外层是 el-form
  • 里面用 el-row、el-col 给栏目分列,如果不需要多列的话,可以去掉
  • 然后是 el-card 做的栏目。
  • 里面是表单字段列表。

同理可以实现 tab 、setp 等功能。

看看效果

  • 双列的 card 表单

【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等

  • tab 的表单

【手撸低代码工具】二次封装UI库(五)继续封装表单:支持自动创建model、联动筛选、分栏等

源码

gitee.com/naturefw-co…

演示

naturefw-code.gitee.io/nf-rollup-u…

转载自:https://juejin.cn/post/7242918609856102455
评论
请登录