复杂中后台项目中的表单系统重构:从 schema 到可组合组件

272 阅读2分钟

一、背景:为什么表单最难维护?

在广告平台中,表单遍布各个业务模块:新建广告、编辑素材、审批流表单、充值申请、内容提交……

问题在于:

  • 字段多达几十个,表单嵌套层级深
  • 联动逻辑复杂,部分字段按角色/状态动态显示
  • 校验规则五花八门,重复性高

因此,我主导对表单系统进行全面重构。


二、目标:构建 schema 驱动、逻辑解耦、组件可插拔的表单系统

核心思路:数据驱动 UI + 逻辑分离 + 插槽灵活扩展


三、表单 schema 抽象设计

基础结构:

interface FormSchemaField {
  label: string
  field: string
  component: string
  props?: Record<string, any>
  rules?: Rule[]
  ifRender?: () => boolean
}

示例 schema:

const adPlanFormSchema: FormSchemaField[] = [
  {
    label: '广告名称',
    field: 'name',
    component: 'n-input',
    rules: [{ required: true, message: '请输入广告名称' }]
  },
  {
    label: '投放时间',
    field: 'range',
    component: 'n-date-picker',
    props: { type: 'daterange' }
  }
]

四、动态渲染组件逻辑

封装一个 SchemaForm.vue

<template>
  <n-form :model="formData" ref="formRef">
    <n-form-item
      v-for="field in schema"
      :label="field.label"
      :path="field.field"
      v-if="!field.ifRender || field.ifRender()"
    >
      <component :is="field.component" v-model:value="formData[field.field]" v-bind="field.props" />
    </n-form-item>
  </n-form>
</template>

五、插槽机制支持扩展需求

支持传入插槽覆盖字段渲染:

<template #custom-range>
  <n-date-picker v-model:value="formData.range" type="daterange" clearable />
</template>

在 Schema 中标记:

{
  label: '投放时间',
  field: 'range',
  component: 'custom-slot'
}

六、联动逻辑设计(useFormModel)

抽象状态联动逻辑到 hook:

export const useFormModel = () => {
  const formData = reactive({ name: '', range: [], type: 'CPC' })
  const schema = computed(() => {
    return [
      ...baseSchema,
      ...(formData.type === 'CPM' ? extraCPMSchema : [])
    ]
  })
  return { formData, schema }
}

七、表单校验封装与提交

在组件内部暴露 validate 方法供外部调用:

const validate = async () => {
  if (!formRef.value) return false
  return await formRef.value.validate()
}

defineExpose({ validate })

八、结果:高复用 + 高灵活 + 低耦合

  • 所有表单结构配置化,业务字段易于维护
  • 表单逻辑抽离后更易测试与复用
  • 插槽机制让灵活场景无痛嵌入

九、总结:表单是中后台的“根”,不重构注定混乱

前端架构的核心就是识别“高重复 + 高变形”的部分并优雅抽象。

表单系统就是其中最典型的代表。