DSL 领域模型架构设计:消灭 CRUD 重复工作

一、什么是 DSL 设计

DSL 架构是专为解决特定领域问题而设计的语言或配置规范,DSL 体现为一套可配置的菜单与页面描述协议------通过 JSON 配置描述菜单结构、页面类型、表格列定义、搜索表单、按钮行为等,前端根据配置自动渲染出完整的页面。

二、为什么要这样设计

2.1 解决的核心问题

问题 传统方式 DSL 方式
新增 CRUD 页面 写 Vue/React 模板 + 表格 + 搜索 + 按钮 写一份 JSON 配置
多项目/多租户 每个项目一套代码 同一套代码,不同配置
页面结构统一性 依赖开发者规范 架构层面保证一致

2.2 设计目的

  1. 配置驱动渲染:将页面结构从代码中抽离为配置,实现「改配置即改页面」
  2. 多项目复用:同一套前端引擎,通过不同 model 配置支撑多个业务系统
  3. 降低开发成本:标准 CRUD 页面零重复代码,减少80%的重复性工作
  4. 统一交互规范:所有页面的表格、搜索、按钮行为由框架统一控制

三、设计思路

3.1 整体架构分层

js 复制代码
┌─────────────────────────────────────────────────────┐
│                    DSL 配置层                        │
│   model/buiness/model.js  model/course/model.js     │
│   (描述菜单结构、页面类型、字段定义、按钮行为)        │
└──────────────────────┬──────────────────────────────┘
                       │ API 返回
┌──────────────────────▼──────────────────────────────┐
│                   状态管理层                         │
│   menuStore(菜单树)    projectStore(项目列表)      │
│   (全局状态存储 + 菜单查找方法)                      │
└──────────────────────┬──────────────────────────────┘
                       │ 驱动
┌──────────────────────▼──────────────────────────────┐
│                   路由引擎层                         │
│   entry.dashboard.js                                │
│   (根据 moduleType 匹配路由 → 对应视图组件)          │
└──────────────────────┬──────────────────────────────┘
                       │ 渲染
┌──────────────────────▼──────────────────────────────┐
│                   视图组件层                         │
│   header-view   sider-view   schema-view            │
│   iframe-view   todo-view                           │
│   (通用视图组件,根据 DSL 配置动态渲染内容)          │
└──────────────────────┬──────────────────────────────┘
                       │ 组合
┌──────────────────────▼──────────────────────────────┐
│                   通用组件层                         │
│   schema-table    schema-search-bar                 │
│   header-container  sider-container                 │
│   (纯 UI 组件,接收 schema 配置渲染表格/搜索/布局)   │
└─────────────────────────────────────────────────────┘

3.2 菜单模型设计

菜单是整个系统的骨架,采用树形结构 + 类型分发的设计:

js 复制代码
MenuItem
├── key          // 唯一标识
├── name         // 显示名称
├── menuType     // group(分组目录)/ module(功能模块)
│
├── [menuType=group]
│   └── subMenu: MenuItem[]    // 递归子菜单
│
└── [menuType=module]
    └── moduleType             // 决定渲染哪种视图
        ├── schema  → schemaConfig   → 数据表格页
        ├── iframe  → iframeConfig   → 内嵌页面
        ├── sider   → siderConfig    → 侧边栏布局
        └── custom  → customConfig   → 自定义组件

关键设计决策:

  • menuType 区分「目录」和「模块」,目录只用于分组,模块才是实际页面
  • moduleType 决定渲染引擎,每种类型对应一个视图组件
  • 配置按需挂载:schema 类型才有 schemaConfigiframe 类型才有 iframeConfig

3.3 Schema 协议设计

Schema 是 DSL 中最复杂的部分,采用一份字段定义,多视图复用的设计:

js 复制代码
properties: {
    product_name: {
        type: 'string',           // 字段类型
        label: '商品名称',         // 字段标签(表格列头 + 搜索标签共用)
        
        tableOptions: { ... },    // 表格列专属配置
        searchOptions: { ... },   // 搜索表单专属配置
    }
}

核心思路:

  1. 字段定义统一 :一个字段只定义一次,labeltype 等基础属性被表格和搜索共用
  2. 视图配置隔离 :通过 tableOptions / searchOptions 后缀区分不同视图的配置
  3. 按需提取buildDtoSchema 方法根据 comName 参数提取对应后缀的配置
js 复制代码
原始 schema
    ↓ buildDtoSchema(schema, 'table')
tableSchema(只含 tableOptions 的字段)
    ↓ buildDtoSchema(schema, 'search')
searchSchema(只含 searchOptions 的字段)

3.4 路由分发设计

路由采用类型映射而非硬编码:

js 复制代码
// dashboard.vue --- 顶部菜单点击
const pathMap = {
    sider:  '/sider',
    iframe: '/iframe',
    schema: '/schema',
    custom: customConfig?.path,
}
router.push({
    path: `/view/dashboard${pathMap[moduleType]}`,
    query: { key, proj_key }
})

侧边栏嵌套路由:

js 复制代码
/view/dashboard/sider              → sider-view(侧边栏容器)
  ├── /sider/iframe                → iframe-view(子路由)
  ├── /sider/schema                → schema-view(子路由)
  └── /sider/todo                  → todo(子路由)

侧边栏通过 sider_key query 参数区分左侧菜单选中项和右侧内容。

3.5 数据流向设计

js 复制代码
model 配置
    ↓ GET /api/getProject?proj_key=xxx
menuStore.setMenuList(menu)
    ↓ 
header-view 渲染顶部菜单
    ↓ 用户点击菜单项
router.push → 路由变化
    ↓
对应视图组件(schema-view / iframe-view / sider-view)
    ↓ 
useSchema() hook 从 menuStore 查找当前菜单配置
    ↓ buildDtoSchema() 解析
provide('schemaViewData') 注入子组件
    ↓
schema-table / schema-search-bar 渲染
    ↓ 用户操作(搜索/翻页/按钮点击)
emit 事件 → 父组件处理或透传
  • 通用事件(如 remove)在组件内部处理
  • 业务事件通过 emit 逐层上报,由最终使用方处理

3.6 路由参数设计(proj_key / sider_key)

在 DSL 流转过程中,路由 query 参数承担着上下文传递的关键职责:

proj_key --- 项目标识

作用: 确定当前访问的是哪个业务项目(如电商系统、课程系统),决定加载哪套菜单配置。

流转路径:

js 复制代码
项目列表页(project-list.vue)
  用户点击「进入」按钮
    ↓ 携带 proj_key 跳转
    router.push({ path: '/view/dashboard/schema', query: { proj_key: 'pdd', key: 'product' } })
    ↓
dashboard.vue 初始化
    ↓ 读取 route.query.proj_key
    GET /api/project/list?proj_key=pdd     → projectStore(项目数据)
    GET /api/getProject?proj_key=pdd       → menuStore(该项目的菜单树)
    ↓
header-view 渲染顶部菜单
    ↓ 用户点击菜单项
    router.push({ query: { key, proj_key: route.query.proj_key } })  ← proj_key 持续透传
    ↓
schema-view / iframe-view / sider-view
    ↓ 通过 route.query.proj_key 保持项目上下文

核心要点: proj_key 在整个应用生命周期中始终通过路由 query 透传,确保每次菜单切换都保持同一项目上下文。

sider_key --- 侧边栏菜单标识

作用: 在侧边栏布局(moduleType: 'sider')中,标识当前选中的左侧子菜单项。

流转路径:

js 复制代码
用户点击顶部菜单(moduleType === 'sider')
    ↓ router.push
    /view/dashboard/sider?key=data&proj_key=pdd
    ↓
sider-view.vue 加载
    ↓ 通过 route.query.key 查找菜单项
    menuStore.findMenuItem({ key: 'key', value: 'data' })
    ↓ 获取 siderConfig.menu
    渲染左侧菜单列表
    ↓ 首次加载自动选中第一个子菜单
    router.push({ path: '/view/dashboard/sider/schema', query: { key: 'data', sider_key: 'analysis', proj_key: 'pdd' } })
    ↓
sider-view 内部 router-view(子路由)
    ↓ schema-view / iframe-view 通过 route.query.sider_key ?? route.query.key 查找菜单
    渲染右侧内容
    ↓ 用户点击左侧菜单
    router.push({ query: { key: 'data', sider_key: 'sider-search', proj_key: 'pdd' } })

核心要点: sider_key 只在侧边栏布局中存在,用于区分左侧菜单选中项。内容组件通过 sider_key ?? key 优先使用 sider_key 定位菜单。

两个 key 的协作关系
js 复制代码
URL: /view/dashboard/sider/schema?key=data&sider_key=analysis&proj_key=pdd
                                       ↑           ↑                    ↑
                                    顶部菜单    侧边栏菜单            项目标识
                                    
路由层级:
/view/dashboard/sider              → sider-view(通过 key 定位顶部菜单 + 获取侧边栏配置)
  └── /sider/schema                → schema-view(通过 sider_key 定位侧边栏菜单 + 获取 schema 配置)
参数 作用域 决定什么 谁消费
proj_key 全局 加载哪个项目的菜单配置 dashboard.vuemenuStore
key 顶部菜单 当前激活的顶部菜单项 header-viewsider-viewschema-viewiframe-view
sider_key 侧边栏 当前激活的侧边栏子菜单项 sider-view、子路由中的视图组件

查找优先级: sider_key ?? key --- 有侧边栏 key 时用侧边栏的,否则用顶部菜单的。

情况 有 sider_key? 用哪个 原因
普通页面 key 只有顶部菜单,key 就是最终页面
侧边栏页面 sider_key key 只是容器,sider_key 才是实际页面

四、设计优势

4.1 开发效率

  • 零代码 CRUD:新增一个数据管理页面只需写 model 配置,无需写 Vue 代码
  • 统一开发范式:所有开发者按同一套协议写配置,降低沟通成本
  • 快速原型:业务人员可直接修改配置验证需求

4.2 可维护性

  • 配置与逻辑分离:页面结构在 model 文件中,渲染逻辑在组件中,互不干扰
  • 组件高度复用schema-tableschema-search-bar 等组件可在任意 schema 页面复用
  • 易于统一升级:修改通用组件即可影响所有使用该组件的页面

4.3 可扩展性

  • 新模块类型 :新增 moduleType 枚举值 + 对应视图组件即可
  • 新搜索组件 :在 search.item.config.js 注册新组件即可
  • 新按钮事件 :在 EventHandlerMap 注册即可
  • 多项目支撑 :不同 proj_key 对应不同 model 配置,一套前端服务多个系统

4.4 架构清晰度

  • 分层明确:DSL 层 → 状态层 → 路由层 → 视图层 → 组件层
  • 数据流单向:配置 → Store → 组件 → 事件 → 组件
  • 职责单一:每个组件只负责一种渲染逻辑

五、可扩展方向

5.1 Schema 组件类型扩展

当前搜索支持 inputselectdynamicSelectdateRange等,可扩展:

组件类型 用途
cascader 级联选择(如省市区)
treeSelect 树形选择
switch 开关筛选
numberRange 数值范围
upload 文件上传搜索

5.2 表单 Schema(新增/编辑页面)

当前 Schema 只驱动「表格 + 搜索」,可扩展为驱动「新增/编辑表单」:

js 复制代码
properties: {
    product_name: {
        type: 'string',
        label: '商品名称',
        formOptions: {           // 表单配置
            comType: 'input',
            rules: [{ required: true, message: '请输入商品名称' }],
            placeholder: '请输入商品名称',
        },
    }
}

配合 eventKey: 'showComponent' 弹出表单弹窗,实现完整的 CRUD 闭环。

5.3 详情页 Schema

js 复制代码
detailOptions: {
    comType: 'text',     // 纯文本展示
    // comType: 'image', // 图片展示
    // comType: 'link',  // 链接跳转
}

5.4 权限控制

在 DSL 中增加权限字段:

js 复制代码
menu: [{
    key: 'product',
    name: '商品管理',
    permission: 'product:view',    // 菜单权限
    schemaConfig: {
        tableConfig: {
            rowButtons: [{
                label: '删除',
                permission: 'product:delete',  // 按钮权限
            }]
        }
    }
}]

六、总结

Elpis 前端领域模型架构的核心思想是 「配置即页面」------通过一套 DSL 协议将菜单结构、页面类型、字段定义、交互行为等描述为可配置的 JSON 数据,前端引擎根据配置自动渲染出完整的业务页面。

架构核心价值

维度 价值
开发效率 标准 CRUD 页面零前端代码,新增页面只需编写 model 配置
多项目复用 同一套前端引擎,通过 proj_key 切换不同业务系统的配置
统一规范 所有页面的表格、搜索、按钮行为由框架统一控制,交互体验一致
易于维护 配置与逻辑分离,修改页面结构无需改动 Vue 代码
可扩展 新增模块类型、搜索组件、按钮事件只需注册即可,无需改动核心引擎

设计哲学

这套架构将前端从「页面开发者 」转变为「引擎开发者」,业务逻辑通过配置表达,而非通过代码表达。当业务需求变化时,只需调整配置而非重写代码,这是低代码思想在中后台系统中的典型实践。

适用场景

  • 适合:中后台管理系统、多项目/多租户平台、标准化 CRUD 场景
  • 不适合:高度定制化的 C 端页面、复杂交互流程页面、需要极致性能优化的场景

一句话总结

用 DSL 描述业务结构,用引擎驱动页面渲染,用配置替代代码实现业务需求。

相关推荐
码事漫谈2 小时前
时序数据库2026盘点:国产数据库如何以“融合多模”走出差异化之路?
前端·后端
道友可好2 小时前
让 AI 自己验收,等于让学生自己批卷
前端·人工智能·后端
yingyima2 小时前
Go 语言正则表达式速查手册:30 分钟掌握核心语法与实战技巧
前端
大蝴蝶博努奇a2 小时前
使用ChatGPT 解决各类代码报错
前端
胡志辉3 小时前
深入浅出 call、apply、bind
前端·javascript·后端
iccb10133 小时前
5年,一个程序员是如何把私有化在线客服系统做到第一名的
前端·后端·github
假如让我当三天老蒯3 小时前
回归基本功:Map/Set 与 WeakMap/WeakSet 的区别
前端·面试
IT乐手3 小时前
48队都装不下你|国足第24次让全世界失望
前端
SoaringHeart4 小时前
Flutter最佳实践:IM聊天文字链接自动识别跳转
前端·flutter