【学习】框架设计(DSL + 领域模型)(2)基于vue3完成动态组件库建设

前言

在里程碑 3 中,我们通过"DSL 配置 + 模板页 + 领域模型"实现了快速建站:一份 schema 配置就能驱动 schema-table 渲染出列表页。但一个中后台系统远不止列表------新增、编辑、详情、搜索,这些表单类功能同样是高频的重复劳动。

以商品管理为例,除了商品列表,还需要"新增商品"表单、"编辑商品"表单、"商品详情"面板。如果这些也逐个手写,建站框架的价值就打了折扣。这个里程碑的内容------动态组件扩展设计,就是解决这个问题:把表单、详情等组件也纳入 DSL 配置体系,通过配置动态注册和渲染,让生成的系统更具自由度和灵活度。

设计思路

先回顾 SchemaView 的结构:它维护一份 JSONSchema 配置,存放字段在不同组件中的配置信息(schema.properties),同时在 schemaConfig 中存放对应的组件配置。里程碑 3 只实现了其中 searchtable 两种形态,这个里程碑把能力扩展到"任意组件"。

动态组件设计的关键在于两个配置入口 + 一个解析器

  1. componentConfig(组件级配置) :在 schemaConfig 中声明系统用到的动态组件,如 createForm(新增表单)、editForm(编辑表单)、detailPanel(详情面板),并配置它们的标题、按钮文案、主键等信息;
  2. 字段级 xxxOption :在 schema.properties 的每个字段上,按组件名挂载对应的配置(如 createFormOptioneditFormOptiondetailPanelOption),用 comType 声明该字段在该组件中使用的控件类型;
  3. 组件解析器 :读取 componentConfig 动态加载组件,通过 showComponent() 按组件名渲染;组件内部再通过控件注册表把 comType 映射到具体的子组件。

整体流程如下图:

DSL 配置

componentConfig:组件级配置

schemaConfig 中新增 componentConfig,声明系统需要的动态组件。以商品管理为例,需要新增表单、编辑表单、详情面板三个组件:

js 复制代码
schemaConfig: {
  api: '/api/proj/product',
  schema: { /* 字段结构,见下文 */ },
  tableConfig: { /* 列表配置(里程碑3已实现) */ },
  componentConfig: {
    createForm: {
      title: '新增商品', // 表单标题
      saveBtnText: '新增商品', // 保存按钮文案
    },
    editForm: {
      mainKey: 'product_id', // 表单主键,唯一标识要修改的数据
      title: '修改商品',
      saveBtnText: '修改',
    },
    detailPanel: {
      mainKey: 'product_id',
      title: '商品详情',
    },
  },
},

组件级配置只描述"这个组件长什么样、操作什么数据",不关心具体字段------字段的呈现由字段级配置决定。

字段级 xxxOption:每个字段独立声明

schema.properties 的每个字段上,挂载它参与的各组件的配置。字段在哪个组件里显示、用什么控件、是否禁用、默认值是什么,都由对应的 xxxOption 决定:

js 复制代码
schema: {
  type: 'object',
  properties: {
    product_id: {
      type: 'string',
      label: '商品ID',
      tableOption: { width: 200, 'show-overflow-tooltip': true },
      editFormOption: {
        comType: 'input',
        disabled: true, // 主键在编辑时不可修改
      },
      detailPanelOption: {},
    },
    product_name: {
      type: 'string',
      label: '商品名称',
      minLength: 2,
      maxLength: 20,
      tableOption: { width: 240 },
      searchOption: {
        comType: 'dynamicSelect', // 搜索框用动态下拉
        api: '/api/proj/product_enum/list', // 通过接口加载枚举
      },
      createFormOption: {
        comType: 'input',
        default: '默认商品名称',
      },
      editFormOption: {
        comType: 'input',
      },
      detailPanelOption: {},
    },
    price: {
      type: 'number',
      label: '价格',
      minimum: 0,
      tableOption: { width: 160, toFixed: 2 },
      createFormOption: {
        comType: 'input-number',
      },
      editFormOption: {
        comType: 'input-number',
      },
      detailPanelOption: {},
    },
    stock: {
      type: 'number',
      label: '库存',
      tableOption: { width: 160 },
      createFormOption: {
        comType: 'input-number',
      },
      detailPanelOption: {},
    },
    create_time: {
      type: 'string',
      label: '创建时间',
      tableOption: {},
      searchOption: {
        comType: 'dateRange',
      },
      detailPanelOption: {},
    },
  },
  required: ['product_name', 'price'],
},

可以看到,同一个字段在不同组件中可以有完全不同的呈现:product_name 在新增表单里是输入框(带默认值)、在搜索里是动态下拉、在详情里只读展示。comType 是控件类型声明,常用取值如下:

comType 控件 说明
input 文本输入框 最通用的控件
select 下拉选择 配合 enumList 配置静态枚举
input-number 数字输入 配合 minimum / maximum 做范围校验
dateRange 日期范围选择 常用于搜索条件
dynamicSelect 动态下拉 配置 api,由接口返回枚举数据

组件解析器

动态组件能生效,靠的是组件解析器。它的工作分为两个层面:

1. 动态组件层面 :模板页的按钮触发 showComponent 事件,携带 comName(组件名),解析器从 componentConfig 中取出对应配置并渲染该组件。前面里程碑 3 中表格按钮的配置在这里被真正用上:

js 复制代码
tableConfig: {
  rowButtons: [{
    label: '查看详情',
    eventKey: 'showComponent',
    eventOption: { comName: 'detailPanel' },
  }, {
    label: '编辑',
    eventKey: 'showComponent',
    eventOption: { comName: 'editForm' },
  }, {
    label: '删除',
    eventKey: 'remove',
    eventOption: {
      params: { product_id: 'schema::product_id' },
    },
  }],
},

2. 表单字段层面 :动态组件内部(如 editForm)渲染时,遍历 schema.properties,读取每个字段对应的 xxxOption,根据 comType 从控件注册表 FormItemConfig 中取出真正的子组件,通过 Vue 的 <component :is> 动态渲染:

js 复制代码
// components-config:控件注册表
import EditInput from './controls/edit-input.vue';
import EditSelect from './controls/edit-select.vue';
import EditInputNumber from './controls/edit-input-number.vue';
import EditDateRange from './controls/edit-date-range.vue';
import EditDynamicSelect from './controls/edit-dynamic-select.vue';

export const FormItemConfig = {
  input: { component: EditInput },
  select: { component: EditSelect },
  'input-number': { component: EditInputNumber },
  dateRange: { component: EditDateRange },
  dynamicSelect: { component: EditDynamicSelect },
};

表单组件内部的渲染核心(以 editForm 为例,省略样式与校验细节):

vue 复制代码
<template>
  <el-form v-if="schema?.properties" :model="model" :rules="rules" label-width="100px">
    <el-form-item
      v-for="(field, key) in schema.properties"
      v-show="field[editFormOption]?.visible !== false"
      :key="key"
      :label="field.label"
      :prop="key"
    >
      <component
        :is="FormItemConfig[field[editFormOption]?.comType]?.component"
        v-model="model[key]"
        v-bind="field[editFormOption]"
      />
    </el-form-item>
  </el-form>
</template>

<script setup>
import { computed } from 'vue';
import { FormItemConfig } from '@/components-config';

const props = defineProps({
  schema: Object, // 字段结构
  model: Object,  // 表单数据(由外层按 mainKey 注入)
});

// 当前组件对应的字段级配置 key(editForm 时读取 editFormOption)
const editFormOption = 'editFormOption';

// 根据字段配置生成校验规则(json-schema 规则 → el-form rules)
const rules = computed(() => {
  const result = {};
  for (const [key, field] of Object.entries(props.schema.properties || {})) {
    const opt = field[editFormOption] || {};
    if (opt.required !== false && props.schema.required?.includes(key)) {
      result[key] = [{ required: true, message: `${field.label}不能为空` }];
    }
  }
  return result;
});
</script>

两个关键点:

  • 字段配置与渲染解耦:组件不关心业务字段,只负责"遍历 properties,按 comType 找控件渲染",新增字段只是加一段配置;
  • 配置天然支持校验schema.properties 里的 requiredminLengthmaximum 等 json-schema 规则,可以直接映射成 el-form 的校验规则,前端校验和后端参数校验共用一份字段定义(呼应里程碑 3 的服务端能力)。

新增一个动态组件的完整流程

框架内置了 createFormeditFormdetailPanel 三种动态组件,但实际业务中往往需要更多。好在扩展一个动态组件只需要四步,完全不碰框架核心代码:

第一步:DSL 配置

componentConfig 中声明新组件,并在字段上挂载对应的 xxxOption

js 复制代码
componentConfig: {
  importPanel: {
    title: '批量导入',
    saveBtnText: '开始导入',
  },
},
// 字段上挂载
importPanelOption: {
  comType: 'upload', // 上传控件
},

第二步:新建组件文件并注册

components 目录下新建组件,然后在 components-config 中注册:

js 复制代码
// components/import-panel/import-panel.vue
// ... 组件实现

// components-config
import ImportPanel from './import-panel/import-panel.vue';

export const ComponentConfig = {
  createForm: { component: CreateForm },
  editForm: { component: EditForm },
  detailPanel: { component: DetailPanel },
  importPanel: { component: ImportPanel }, // 新组件注册
};

第三步:修改 model 配置

在对应的 model 文件中,把新组件的 componentConfig 和字段的 xxxOption 补上(即第一步的配置落到具体系统的配置里)。

第四步:补齐接口

router-schemaroutercontrollerservice 中新增对应的接口处理逻辑。这一步正好用上里程碑 2 的 elpis-core------它通过 loader 自动加载约定目录下的文件,新增接口不需要改框架,只需要按约定添加文件即可。

至此,一个新的动态组件就完整接入系统了。

灵活性与自由度

对比传统开发和配置驱动开发,差异很直观:

维度 传统开发 动态组件配置驱动
新增一个表单 新建页面 + 组件 + 接口 四步配置 + 组件文件
字段增减 改代码 改配置
控件类型变化 改代码 comType
新增业务系统 重新开发 继承领域模型 + 写配置

另外,动态组件内部并不是只能使用框架提供的基础控件:它可以使用我们提供的 schema 组件(如 SchemaForm),也可以嵌套其他自定义内容。也就是说,动态组件本身也是一个"容器",自由度可以逐层放大------配置化解决 80% 的标准场景,自定义能力兜住 20% 的特殊场景。

总结

这个里程碑补上了建站框架的重要一环:

  1. 从"列表页配置化"到"全组件配置化":schema-table 解决列表,动态组件把表单、详情、甚至任意自定义组件都纳入了 DSL 体系;
  2. 两级配置设计componentConfig 管组件级行为(标题、主键、按钮),字段级 xxxOption 管字段级呈现(控件、显隐、默认值),职责清晰、互不干扰;
  3. 解析器是核心showComponent + FormItemConfig 注册表 + <component :is>,让"组件按配置动态渲染"成为可能,扩展新组件不需要改框架;
  4. 前后端配置同源:字段的 json-schema 规则同时驱动前端表单校验和后端参数校验,一份定义多处使用。

回顾四个里程碑:工程化解决了构建问题,elpis-core 沉淀了后端接口流程,建站框架用 DSL + 领域模型生成了系统的骨架,动态组件让这个骨架上的每个"器官"也可以按需配置。到这一步,一个中后台系统从页面到表单,真正做到了"配置优先、代码兜底"。

注:抖音"哲玄前端",《大前端全栈实践课》

相关推荐
X54先生(人文科技)1 小时前
《元创力》纪实录 · 卷宗 3.5-C《协议的形状——ELR体系第一份商业合同的形成全记录》
人工智能·深度学习·架构·ai写作·开源协议
风哥2号1 小时前
数据库教程FGMT19‑Oracle多租户容器架构与新特性总结
数据库·oracle·架构
昇腾知识体系1 小时前
昇腾 Atlas 800I A5 服务器:机型定位与部署入口
服务器·人工智能·华为·架构·知识图谱
快乐非自愿1 小时前
2026企业低代码选型对比:制造、政务、供应链转型硬核评测
人工智能·低代码·架构·制造·政务
落木萧萧8251 小时前
Wrapper、Criteria、Specification:查询能不能写得好维护一点
数据库·后端·架构
海宇大数据1 小时前
零信任架构实战:基于海宇单人婚姻状态查询构建自动化线上房产交易合规网关
运维·人工智能·架构·自动化
X54先生(人文科技)2 小时前
《元创力》纪实录 · 桥段 3.5-D《第一次协议的遗址》
人工智能·深度学习·架构·开源·ai写作
天远数科2 小时前
零信任架构实战:基于天远企业四要素验证构建自动化高并发金融科技商户收单网关
人工智能·金融·架构·自动化
今天AI了吗2 小时前
2026年三大AI桌面智能体横评:Codex vs Hermes vs WorkBuddy
大数据·前端·css·人工智能·架构