结构决定展示-开发易维护表单

31 阅读6分钟

配置数据构建复杂form表单

一、为何需要 fieldConfig

前端开发人员是否经常遇到以下问题?

  • 💡后端同学告诉你请求字段为 null,导致访问时报错

  • 💡表单项过多,每个字段都写一堆 if 判空,重复又容易漏

  • 💡数据展示、下拉枚举、编辑权限、默认值、字段联动... 各种逻辑写死在业务DOM里?

  • 💡复杂业务表单涉及模块较多导致本地变量过多,新人接手成本很高

  • 😅**谁写的功能,谁负责后期维护,如何减少后期维护成本,或者提高维护效率?**😅

  • 🦛我们可以通过特定的数据结构配置规避掉这些问题 于是,fieldConfig思维应运而生。

二、什么是 fieldConfig?

  • fieldConfig 指的是前端使用一份集中数据模型配置,来描述特定业务对象例如:
export class OrderBtn {
  /** 按钮标识 */
  key: string | null;
  /** 按钮文字 */
  title: string | null;
  /** 按钮优先度 */
  priority: number | null;
  /** 按钮是否显示 */
  show: boolean | null;
  /** 按钮是否禁用 */
  disabled: boolean | null;
  /** css的类 */
  class: string | null;
  /** 按钮是否加载中 */
  loading: boolean | null;
  /** 样式 */
  style: string | null;
}

export class PropertyConfig {
  /** 属性名称(必须配置) */
  name?: string | null;
  /** 属性字段(必须配置) */
  field: string;
  /** 默认值(必须配置,如果没有配置,默认为'') */
  defaultValue?: IDefaultValue | null;
  /** 元素展示类型(必须配置) */
  elementType?: FE_ROOM_LAYOUT_ELEMENT_TYPE | null;
  /** 是否独占一行(默认否) */
  aloneLine?: boolean | null;
  /** 属性校验规则(可选) */
  rule?: IDynamicChangeField[] | null;
  /** 提示信息(可选) */
  placeholder?: string | null;
  /** 列数 */
  column?: number | null;
  /** 下拉选项(可选) */
  options?: ISelectOption[] | null;
  /** 是否有提示图标 */
  hasTipsIcon?: boolean | null;
  /** 提示语 */
  tipsWords?: ISelectOption[] | null;
  /** 表单项附加的备注信息 */
  fromItemRemark?: string | null;
  /** 额外的字段(上传的参数,url等) */
  extraFiled?: IDynamicChangeField | null;
  /** 是否禁用 */
  disable?: boolean | null;
  /** 是否影藏 */
  hidden?: boolean | null;
}
  • 具体的表单配置项,可以参考以下代码,这里就不一一列举了。
export const doorOne = [
  {
    elementType: FE_ROOM_LAYOUT_ELEMENT_TYPE.Select,
    name: '材料基材',
    field: 'surfaceTreatment',
    placeholder: '请选择',
    options: [
      { text: '平板', value: '平板' },
      { text: '造型', value: '造型' },
      { text: '其他', value: '其他' },
      { text: '无', value: '无' },
    ],
    rule: [
      {
        required: true,
        trigger: 'change',
      },
    ],
    disable: false,
    hidden: false,
  },
  • 通过抽象model层面的模型,你可以让「特殊逻辑、展示、编辑、默认值、联动、」全部脱离 if 判断,变成结构化驱动的系统。

三、合理的模块划,多端共用代码节省开发成本

✅ 1. 模块划分

  • 将表单拆分成多个模块,每个模块负责一部分表单的渲染,这样不仅可以减少页面的DOM绘制,还可以提高组件的复用性。最终将多个模块统一给Form组件进行管理和渲染。

✅ 2. 作用域插槽构建模块骨架

  • 实现展开折叠等模块动画效果,以及图标,模块标题,模块内容,模块操作按钮等功能。
  • 可以在不同的表单中复用这些组件,减少代码的重复性。

四、fieldConfig 应用思路

✅ 1. 字段枚举展示(enumMap)

  • 使用关键枚举替代每个字段包裹form-item:
// signed  logistic   afterSale   againBuy
OrderBtn.key
//   Check = 'el-checkbox',
//   Radio = 'el-radio',
//   InputNumber = 'uni-easyinput-number',
//   Upload = 'el-upload',
//   OnlyShow = 'el-text-area',
PropertyConfig.elementType
  • 逻辑判断:
<el-Input
    v-if="config.elementType === FE_ROOM_LAYOUT_ELEMENT_TYPE.Input"
    :placeholder="config.placeholder"
    :disabled="true"
    v-model="localFormModel[config.field]"
/>

✅ 2. 表单默认值(defaultValue)

  • 用于页面初始化规避掉给后端传入null值:
form.defaultValue = PropertyConfig.defaultValue;

✅ 3. 表单校验(rules)

  • 用于表单提交校验:
form.rules = PropertyConfig.rules;

✅ 4. 动态 label / placeholder / tooltip

config[field]?.label
config[field]?.description
config[field]?.placeholder
config[field]?.disable

✅ 5. 表单隐藏(hidden)

  • 替代 if-else用于表单隐藏:
form.hidden = PropertyConfig.hidden;

✅ 6. 表单禁用(disable)

  • 用于表单禁用:
form.disable = PropertyConfig.disable;
  • 用 fieldConfig配置数据,统一提取表单label、规则、字段:
  • 统一管理所有表单校验信息,避免重复编写
  protected generateFormModelAndRules(formFields:Array): any {
    const rules: any = {};
    const formModel: any = {};

    formFields.forEach((config: any) => {
      // 生成ruleForm
      formModel[config.field] = config.defaultValue || '';

      // 生成rules,统一管理form校验规则
      if (config?.rule) {
        const ruleValue = {
          rules: [
            {
              required: true,
              trigger: 'change',
              errorMessage: `${config.placeholder || '请输入'}${config.name || ''}  `,
            },
          ],
        };
        rules[config.field] = ruleValue;
      }
    });

    return {
      rules,
      formModel,
    };
  }

✅ 7. 控制字段行为权限

form.hidden = PropertyConfig.hidden;
form.edit = PropertyConfig.edit;
  • 你可以统一控制哪些状态下字段是可编辑的、只读的、禁用的。

✅ 8. 字段联动 / 特殊判断

  • 以“酒店是否双包”为例:是和否,会影响,“安装”,"酒店协作方”等字段的显示。
  • 传统写法都有类似 v-if="serviceCode === 'turnkeyProject'" 的判断这样的写法法值写死在业务代码中,不易读,也难以维护。
  • 使用配置数据,利用函数可以统一管理关联的字段管理,并且可以动态控制显示和隐藏。
  // 是否为双包
  @Watch('localFormModel.turnkeyProject', { deep: true, immediate: false })
  turnkeyProjectsChange(newVal: string): void {
    this.projectRequirements = ProjectDetail.judgeFormItemStatus({
      hiddenCondition: newVal === 'N',
      relativeFields: ['installName', 'projectCoordination'],
      formVariable: this.projectRequirements,
      originFormVariable: this.originProjectRequirements,
    });
  }
  • 具体函数很简单就利用配置数据中的hidden和filter函数,判断是否显示。然后统一封装判断逻辑
  /** 判断状态 */
  static judgeFormItemStatus(options: {
    hiddenCondition: boolean;
    relativeFields: string[];
    formVariable: IPropertyConfig[];
    originFormVariable: IPropertyConfig[];
  }): IPropertyConfig[] {
    options.relativeFields.forEach((field) => {
      const propertyConfig = options.originFormVariable.find((item) =>
        item.field.startsWith(field),
      ) as IPropertyConfig;

      if (propertyConfig) {
        propertyConfig.hidden = options.hiddenCondition;
      }
    });

    options.formVariable = options.originFormVariable.filter((item) => !item.hidden);
    return options.formVariable;
  }
  • 表单数据全程监控自动更新,无需手动更新,每一项数据更改都会及时触达提交表单的配置数据,从而实现表单配置数据与表单数据的同步。
  // 监听localFormModel变化
  @Watch('localFormModel', { deep: true, immediate: true })
  localFormChange() {
    this.latestFormData = this.generateFormModelAndRules().formConfigs;
  }

表单数据检验和错误定位

  • 不再是只弹框提示用户,必填项未填,而是直接在表单上标红,并滑动到必填项为止提示用户未填项。
(this.$refs.combineForm as any)
    .validate()
    .then((res: string) => {
    console.log('表单数据:', res, this.localFormModel);
    })
    .catch((err: { errorMessage: string }[]) => {
    const message = err[0]?.errorMessage || '';
    const errorElement = document.querySelector(`.msg--active`);

    if (message) {
        uni.showToast({
        title: message,
        duration: 2000,
        icon: 'none',
        });

        if (errorElement) {
        errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
    }
    });

思考:为什么这是更好的做法?

  • 把行为语义化:所见即所得,表单配置数据就是表单行为
  • 将字段→行为的映射集中管理
  • 一套配置数据管理多个客户端(PC,小程序,企微h5)
  • 未来新增表单项(如 isKinlong,isComfort),只需配置数据新增一个字段,无需修改页面代码
  • 未来新增表单模块(舒适家定制),新增一个模块数组,无需修改页面代码

这是一种「结构即页面」的思维方式。

五、关键步骤

  1. 抽象操作对象,先从枚举字段入手定义不同表单项
  2. 减少多个本地变量共同维护表单特定字段交给Watch
  3. 逐步建立 judgeFormItemStatus 等通用方法,统一赋值行为;
  4. 对所有字段建立行为描述统一表(fieldConfig) ,再生成表单;
  5. 推荐封装组件如 <form-item />,由 fieldConfig 决定类型、枚举、只读,显示等行为。

六、总结:fieldConfig 是你组件解耦、逻辑统一的利器

  • 让字段自己“说”该怎么被使用

  • 让 if else 变成结构化配置

  • 摆脱人为判断去兜底,通过结构设计让DOM保持清爽

  • 让代码更像配置,配置更像代码

  • 一旦用上,你会发现整个页面“安静了”,逻辑清晰了,页面神清气爽。