【手撸低代码工具】二次封装UI库(三)表单与表单内各组件的那点事

1,117 阅读4分钟

表单

表单里面有很多 dom,比如 input、textarea、select、CheckBox 等,但是他们并没有一个统称,看标题“表单里的各种组件”,我只好用这种称呼来描述。

elementPlus 的划分是:Form 表单组件的下面有各种组件,包括 el-form 在内。帮助文档的菜单可以这么做,因为看一下就知道了,但是。。。

比如我说,我要封装一下“表单组件”,你想到的一定是 el-form 这类的,不会想到 input 吧。

我要是说,我要封装一下表单子组件,你可能想到的是 el-form-item,那么有没有可能想到的是 input 呢?

是的,在接口命名上出现了冲突。

之前给 input 这类的起个统称:IFromItem,还自我感觉良好。
但是当封装到 el-form-item 的时候就懵了!咋办?只在组件内部定义 Interface?这不是糊弄人吗?改个名字就叫“FromSon”?似乎不好吧。

三个层次

其实表单可以分为三个层次:

  • el-form:对应整体 的 table,一个大表单,需要自己的 props 和 meta。
  • el-form-item:对应一个 tr 或者 td,表单里的一行,也需要自己的 props,对应自己的 meta 和 input 的meta,还有表单的验证。
  • input 等:目前没有一个统称。。。需要 props 和 meta。

看着是不是有点晕,对的,很晕。名字搞错了,所以要改名,要改一些代码。

所以,接口想改名就要趁早,越早影响越小,以后就没法改了。

接口名称

想了一个统称:FormChild 。我也想不到更好的了,先凑合用吧,至少不会混淆。

  • el-form:一个大表格
    • props IFromProps :el-form 需要的各种属性,比如labelSuffix、labelWidth等
      • formMeta IFromMeta:表单级别的 meta,实现各种辅助功能需要的属性
        • moduleId 等:模块、表单的标识
        • colOrder:数组,字段的显示顺序
        • columnsNumber:列数
        • subMeta:对象,分栏等
        • ruleMeta:对象,验证需要的
        • linkageMeta:对象,联动用的
        • customerControl:扩展用的
      • childPropsList IFormChildPropsList:这是一个综合,照顾 json 里面存放的数据。
      • model T:表单的对象数据。
  • el-form-item:一行,或者一个字段
    • props IFormItemMeta:综合的,表单里的验证,child 的 meta等
      • colOrder:字段排序依据,也是显示依据。
      • childProps:input 的 props 集合, 含 meta
      • childMeta:input 等的纯 meta 集合
      • ruleMeta:验证规则
      • showCol:联动的时候,是否显示的依据。
      • formColSpan:el-col 的 span 属性,设置列数的
  • form-child:input 这样的 props 的集合。

先确定总体范围,然后再细化调整。这样至少名称可以先弄好了。

枚举和魔数

上次遇到判断组件类型的时候,我直接用了魔数,后来发现可以用枚举(enum)实现,效果更好。于是改进了一下。可读性更好了,只是使用的时候代码有点长。

/**
 * 控件类型的枚举
 */
export const enum EControlType {
  // 文本
  text = 101,
  textarea = 100,
  password = 102,
  email = 103,
  tel = 104,
  url = 105,
  search = 106,
  autocomplete = 107,
  color = 108,
  // 数字
  number = 110,
  range = 111,
  rate = 112,
  // 日期
  date = 120,
  datetime = 121,
  month = 122,
  week = 123,
  year = 124,
  daterange = 125,
  datetimerange = 126,
  monthrange = 127,
  dates = 128,
  
  // 时间
  timepicker = 130,
  timepickerrange = 131,
  timeselect = 132,
  timeselectrange = 133,
  // 上传
  file = 140,
  picture = 141,
  video = 142,
  // 选择等
  checkbox = 150,
  switch = 151,
  checkboxs = 152,
  radios = 153,
  // 下拉
  select = 160, // 单选下拉
  selects = 161, // 多选下拉
  selectGroup = 162, // 分组下拉单选
  selectGroups = 163, // 分组下拉多选
  selectCascader = 164, // 联动下拉
  selectTree = 165, // 树状下拉
  selectTrees = 166 // 树状多选
}

应该可以扩展,我打算把200+的数字都留个扩展用,比如你可以定义 200 号是一个 你喜欢的组件,然后可以把这个组件加入到表单里面。

这样就可以支持各种各样的需求了。而且也不用去修改表单组件内部的代码。

合并组件

以前分的比较细致,导致出现了一大堆 xxxx.vue 文件,改起来就会比较费劲,比如这次修改 Interface 的名称。

所以干脆,我把若干比较相近的组件合并为一个,虽然内部代码有点繁琐,但是应该算是一个折中的方案吧。

比如 把 switch 和 el-checkbox 合并在一起:

<script setup lang="ts" generic="T extends object">
  // 引入组件需要的属性 引入表单子控件的管理类
  import { itemController, EControlType } from '../map'
  import type { IFormChildProps } from '../map'
  
  defineOptions({
    name: 'nf-el-from-item-checkbox',
    inheritAttrs: false,
  })

  // 定义 props
  const props = withDefaults(defineProps<IFormChildProps<T> & {
    title?: string 
  }>(), {
    clearable: true,
    title: ''
  })

  const { value } = itemController(props)

</script>

引入 Interface 定义 props 设置默认值,然后调用 controller 创建 value,最后到 template 上面去绑定即可,抽象之后,代码可以变得更简洁。

<template>
  <!--开关 -->
  <el-switch
    v-if="meta.controlType === EControlType.switch"
    v-model="value"
    v-bind="$attrs"
    :clearable="clearable"
  >
  </el-switch>
  <!--勾选框-->
  <el-checkbox
    v-else
    v-model="value"
    v-bind="$attrs"
    :clearable="clearable"
  >
    {{title}}
  </el-checkbox>
</template>

v-if 依据 meta.controlType 判断显示哪个组件,然后按照需求绑定属性即可。

这样合并之后,组件数量由三十多个缩减到 十六个。不能再少了,内部代码会乱。