领域模型DSL实现动态组件渲染

11 阅读5分钟

前言

在现代前端开发中,如何通过一份配置驱动整个站点的生成,是提升开发效率的关键。本文将深入探讨如何利用领域模型DSL(Domain Specific Language)实现动态组件的渲染,完成从 模板→模型→项目→内容→应用 的完整链路。

回顾:领域模型架构建设


一、整体架构设计

1.1 核心理念

整个架构分为三层:

DSL 配置层Schema 解析层视图渲染层

  • DSL 配置层:定义模板、模型、项目、应用的配置
  • Schema 解析层:解析配置生成 tableSchema、searchSchema、components
  • 视图渲染层:根据解析结果渲染 Table、SearchBar、CreateForm、EditForm 等组件

1.2 设计目标

目标说明
配置化通过JSON配置描述页面结构和行为
组件化动态渲染不同类型的组件
可扩展支持自定义组件和配置扩展
低代码减少重复代码,提升开发效率

二、Dashboard DSL 配置结构

2.1 顶层配置

{
  model: "dashboard",     // 模板类型
  name: '电商系统',        // 项目名称
  menu: [...]             // 菜单配置
}

2.2 菜单类型说明

菜单支持两种类型:

menuType: "group" - 菜单分组,包含 subMenu 子菜单(可递归)

menuType: "module" - 功能模块,支持以下 moduleType:

  • sider - 带侧边栏的布局,支持嵌套菜单
  • iframe - 嵌入外部页面,通过 path 指定 URL
  • custom - 自定义路由,跳转到指定的 Vue 组件
  • schema - ⭐ 核心能力:通过 Schema 配置动态渲染页面

三、Schema 配置详解

3.1 schemaConfig 整体结构

schemaConfig: {
  api: '/api/proj/product',       // RESTful API 地址
  schema: {...},                  // 数据模型定义
  tableConfig: {...},             // 表格全局配置
  componentConfig: {...}          // 动态组件配置
}

3.2 Schema 数据模型示例

schema: {
  type: 'object',
  properties: {
    product_id: {
      type: 'string',
      label: '商品ID',
      tableOption: { width: 300, "show-overflow-tooltip": true },
      editFormOption: { comType: 'input', disabled: true },
      detailPanelOption: {}
    },
    product_name: {
      type: 'string',
      label: '商品名称',
      maxLength: 10,
      minLength: 3,
      tableOption: { width: 200 },
      searchOption: { 
        comType: 'dynamicSelect', 
        api: '/api/proj/product_enum/list' 
      },
      createFormOption: { comType: 'input', default: '抖音哲玄' },
      editFormOption: { comType: 'input', visible: false },
      detailPanelOption: {}
    },
    price: {
      type: 'number',
      label: '价格',
      maximum: 1000,
      minimum: 30,
      tableOption: { width: 200 },
      searchOption: {
        comType: 'select',
        enumList: [
          { label: '全部', value: -1 },
          { label: '¥39.9', value: '39.9' },
          { label: '¥199', value: '199' },
          { label: '¥899', value: '899' }
        ]
      },
      createFormOption: { comType: 'inputNumber' },
      editFormOption: { comType: 'inputNumber' },
      detailPanelOption: {}
    }
  },
  required: ['product_name']
}

3.3 字段配置映射关系

每个字段可以配置以下 Option,分别对应不同组件的渲染规则:

Option对应组件说明
tableOptionTable Column表格列渲染配置
searchOptionSearchBar搜索栏控件渲染配置
createFormOptionCreateForm新建表单项配置
editFormOptionEditForm编辑表单项配置
detailPanelOptionDetailPanel详情面板展示配置

四、各 Option 配置详解

4.1 tableOption - 表格列配置

tableOption: {
  width: 200,                       // 列宽
  "show-overflow-tooltip": true,    // 内容过长时显示 tooltip
}

4.2 searchOption - 搜索栏配置

普通下拉选择:

searchOption: {
  comType: 'select',
  enumList: [
    { label: '全部', value: -1 },
    { label: '¥39.9', value: '39.9' }
  ]
}

动态下拉选择:

searchOption: {
  comType: 'dynamicSelect',
  api: '/api/proj/product_enum/list'
}

输入框:

searchOption: {
  comType: 'input'
}

日期范围:

searchOption: {
  comType: 'dateRange'
}

4.3 createFormOption - 新建表单配置

// 输入框带默认值
createFormOption: {
  comType: 'input',
  default: '抖音哲玄'
}

// 数字输入框
createFormOption: {
  comType: 'inputNumber'
}

// 下拉选择
createFormOption: {
  comType: 'select',
  enumList: [
    { label: '100', value: 100 },
    { label: '1000', value: 1000 },
    { label: '10000', value: 10000 }
  ]
}

4.4 editFormOption - 编辑表单配置

// 禁用编辑
editFormOption: {
  comType: 'input',
  disabled: true
}

// 不显示该字段
editFormOption: {
  comType: 'input',
  visible: false
}

// 数字输入框
editFormOption: {
  comType: 'inputNumber'
}

4.5 detailPanelOption - 详情面板配置

detailPanelOption: {}   // 空对象表示使用默认展示

五、tableConfig 与 componentConfig

5.1 tableConfig - 表格全局配置

tableConfig: {
  // 表头按钮
  headerButtons: [
    {
      label: '新增商品',
      eventKey: 'showComponent',
      eventOption: {
        comName: 'createForm'
      },
      type: 'primary',
      plain: true
    }
  ],
  
  // 行操作按钮
  rowButtons: [
    {
      label: '查看详情',
      eventKey: 'showComponent',
      eventOption: {
        comName: 'detailPanel'
      },
      type: 'primary'
    },
    {
      label: '修改',
      eventKey: 'showComponent',
      eventOption: {
        comName: 'editForm'
      },
      type: 'warning'
    },
    {
      label: '删除',
      eventKey: 'remove',
      eventOption: {
        params: {
          product_id: 'schema::product_id'
        }
      },
      type: 'danger'
    }
  ]
}

5.2 componentConfig - 动态组件配置

componentConfig: {
  createForm: {
    title: '新增商品',
    saveBtnText: '新增商品'
  },
  editForm: {
    mainKey: 'product_id',
    title: '修改商品',
    saveBtnText: '修改商品'
  },
  detailPanel: {
    mainKey: 'product_id',
    title: '商品详情'
  }
}

六、useSchema Hook 核心实现

6.1 Hook 职责

useSchema 是连接 DSL 配置与视图渲染的桥梁:

输入:

  • route.query(路由参数)
  • menuStore(菜单配置)

处理:

  • 解析 schemaConfig
  • 构建各组件专属 schema
  • 过滤无关配置(降噪)

输出:

  • api(数据接口)
  • tableSchema + tableConfig
  • searchSchema + searchConfig
  • components(动态组件配置集合)

6.2 buildDtoSchema 降噪原理

原始 Schema 字段:

product_name: {
  type: 'string',
  label: '商品名称',
  maxLength: 10,
  minLength: 3,
  tableOption: { width: 200 },
  searchOption: { comType: '...' },
  createFormOption: { ... },
  editFormOption: { ... },
  detailPanelOption: {}
}

经过 buildDtoSchema(schema, 'table') 处理后:

product_name: {
  type: 'string',
  label: '商品名称',
  maxLength: 10,
  minLength: 3,
  option: { width: 200 }  // 只保留 table 相关配置
}

七、数据流转示意

整体数据流转过程:

  1. DSL 配置 - JSON 配置文件定义页面结构
  2. menuStore - Pinia 状态管理存储菜单配置
  3. useSchema - Composable Hook 解析配置
  4. 分发到各组件 - tableSchema、searchSchema、components
  5. SchemaView - 统一视图容器渲染最终页面

八、设计优势与总结

8.1 设计优势

优势说明
配置即代码通过 JSON 配置描述页面,无需编写重复的 CRUD 代码
统一规范所有页面遵循相同的配置规范,便于维护和理解
灵活扩展支持自定义组件和配置项,满足个性化需求
降低耦合Schema 与视图组件解耦,修改配置即可更新页面
提升效率新增页面只需添加配置,大幅减少开发时间

8.2 核心设计思想

  1. 单一数据源:一份 Schema 描述所有组件的渲染规则
  2. 配置驱动:通过 xxxOption 区分不同组件的配置
  3. 降噪处理:buildDtoSchema 过滤无关配置
  4. 响应式更新:watch 监听路由变化,自动重建配置
  5. 组件解耦:useSchema 作为中间层,连接配置与视图

结语

通过领域模型 DSL 的设计,我们实现了一份配置驱动整个页面的能力。useSchema Hook 作为核心桥梁,将复杂的 DSL 配置转换为各组件可消费的数据结构,大大提升了开发效率和代码可维护性。

域模