** 领域模型架构建设(低代码平台)**
一、概述
基于 vue3,使用 DSL 驱动的低代码平台,通过配置化方式可以实现业务系统的快速搭建,减少crud工作量,聚焦于定制化页面的开发。
1.企业级后台痛点
- 重复开发:每个业务模块(商品管理、订单管理、用户管理)都需要从零搭建
- 维护困难:菜单、路由、表格列配置分散在各处代码中
- 扩展性差:新增一个项目需要复制大量代码
- 多租户困境:同一套业务逻辑,不同客户需要不同的定制
2.DSL领域模型解决思路
用声明式的数据结构来描述业务领域,系统根据这些描述自动生成功能。
传统方式:代码驱动 → 手写每个页面和功能 领域模型:配置驱动 → 定义模型 → 自动生成页面和功能
二、结构设计
1. DSL 的定义
在 elips 项目中,我们设计了一套 仪表盘领域特定语言(Dashboard DSL) ,用 JavaScript 对象来描述整个后台站点的结构。
核心 DSL 定义位于 dashboard-model.js:
module.exports = {
mode: "dashboard", // 模板类型:不同模板对应不同的渲染规则
name: "", // 站点名称
desc: "", // 站点描述
icon: "", // 站点图标
homePage: "", // 首页路径
// 核心:菜单结构(驱动整个站点的版块划分)
menu: [
{
key: "", // 菜单唯一标识
name: "", // 菜单名称
menuType: "", // 枚举:group(分组)/ module(模块)
// ... 详细配置
},
],
};
2. 菜单类型系统
DSL 设计的关键在于 菜单类型(menuType) 和 模块类型(moduleType) 的组合:
| menuType | 菜单类型 |
|---|---|
| group | 分组菜单,包含 subMenu 子菜单(支持递归嵌套 |
| module | 功能模块,根据 moduleType 决定渲染方式 |
| moduleType | 模块类型 |
|---|---|
| sider | 带侧边栏的复合布局,内部可嵌套其他模块 |
| iframe | 嵌入外部页面(适合集成第三方系统) |
| custom | 自定义路由页面(开发者手写组件) |
| schema | Schema 驱动的 CRUD 页面(零代码) |
3. 模块配置详解
每种模块类型都有对应的配置块:
{
// 侧边栏配置:支持嵌套菜单结构
siderConfig: {
menu: [/* 递归菜单结构 */]
},
// iframe 配置:嵌入外部页面
iframeConfig: {
path: 'https://example.com'
},
// 自定义路由配置
customConfig: {
path: '/todo'
},
// Schema 配置:数据驱动的 CRUD
schemaConfig: {
api: '/api/resource', // RESTFUL API 地址
schema: {
type: 'object',
properties: {
fieldName: {
type: 'string',
label: '字段名称',
tableOptions: {
visible: true
}
}
}
}
}
}
三、Model-Project 继承体系
1. 双层架构设计
elips 采用了 Model(模型)→ Project(项目) 的两层继承架构:
┌─────────────────┐
│ Model 模型 │
│ (领域模板) │
└────────┬────────┘
│ 继承
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Project A │ │ Project B │ │ Project C │
│ (京东电商) │ │ (拼多多) │ │ (淘宝) │
└───────────────┘ └───────────────┘ └───────────────┘
2. 实际示例
基础模型定义
module.exports = {
model: "dashboard",
name: "电商系统",
menu: [
{
key: "product",
name: "商品管理",
menuType: "module",
moduleType: "custom",
customConfig: { path: "todo" },
},
{
key: "order",
name: "订单管理",
// ...
},
{
key: "client",
name: "客户管理",
// ...
},
],
};
项目扩展配置
module.exports = {
name: "拼多多",
desc: "拼多多电商",
homePage: "/todo?proj_key=pdd&key=product",
menu: [
// 重载:修改继承的菜单项
{
key: "product",
name: "商品管理(PDD)", // 覆盖名称
},
// 重载:增强客户管理功能
{
key: "client",
name: "客户管理(PDD)",
moduleType: "schema", // 升级为 Schema 驱动
schemaConfig: {
api: "/api/pdd/client",
schema: {},
},
},
// 新增:项目特有的功能模块
{
key: "data",
name: "数据分析",
menuType: "module",
moduleType: "sider",
siderConfig: {
menu: [
/* 侧边栏菜单 */
],
},
},
],
};
3. 继承合并算法
模型继承的核心逻辑位于
const projectExtendModel = (model, project) => {
return _.mergeWith({}, model, project, (modelValue, projValue) => {
// 数组合并的特殊处理
if (Array.isArray(modelValue) && Array.isArray(projValue)) {
let res = [];
// 规则 1:model 有的,project 也有 → 递归合并(重载)
// 规则 2:model 有的,project 没有 → 保留(继承)
for (let i = 0; i < modelValue.length; i++) {
const modelItem = modelValue[i];
const projItem = projValue.find((p) => p.key === modelItem.key);
res.push(
projItem ? projectExtendModel(modelItem, projItem) : modelItem
);
}
// 规则 3:project 有的,model 没有 → 添加(扩展)
for (let i = 0; i < projValue.length; i++) {
const projItem = projValue[i];
const modelItem = modelValue.find((m) => m.key === projItem.key);
if (!modelItem) res.push(projItem);
}
return res;
}
});
};
继承规则总结:
| 场景 | 行为 | 说明 |
|---|---|---|
| Model 有,Project 也有 | 重载 | Project 的配置覆盖 Model |
| Model 有,Project 没有 | 继承 | 直接使用 Model 的配置 |
| Model 没有,Project 有 | 扩展 | 新增 Project 特有功能 |
四、映射机制
1. 后端:模型加载与 API 暴露
服务层
const modelList = require("../../model/index.js")(app);
class ProjectService {
// 根据项目 key 获取完整配置
get(projKey) {
let projConfig;
modelList.forEach((modelItem) => {
if (modelItem.project[projKey]) {
projConfig = modelItem.project[projKey];
}
});
return projConfig;
}
}
控制器层
// GET /api/project?proj_key=pdd
get(ctx) {
const { proj_key: projKey } = ctx.request.query
const projConfig = projectService.get(projKey)
this.success(ctx, projConfig)
}
2. 前端:动态路由与组件映射
入口路由配置
const routes = [];
const basePath = "/view/dashboard";
const pathOption = [{
pathName: 'iframe',
comPath: () => import('./complex-view/iframe-view/iframe-view.vue')
},
{
pathName: 'schema',
comPath: () => import('./complex-view/schema-view/schema-view.vue')
},
{
pathName: 'todo',
comPath: () => import('./todo/todo.vue')
}]
const routeList = []
const siderRouteList = []
pathOption.forEach(item => {
const component = item.comPath
const routeItem = {
path: `${basePath}/${item.pathName}`,
component,
}
const siderRouteItem = {
path: item.pathName,
component,
}
routeList.push(routeItem)
siderRouteList.push(siderRouteItem)
})
routes.push(...routeList);
// 侧边栏菜单路由
routes.push({
path: `${basePath}/sider`,
component: () => import("./complex-view/sider-view/sider-view.vue"),
children: siderRouteList,
});
菜单选择处理
const onMenuSelect = (menuItem) => {
const { moduleType, key, customConfig } = menuItem;
// moduleType 到路由的映射表
const pathMap = {
sider: "/sider",
iframe: "/iframe",
schema: "/schema",
custom: customConfig?.path, // 自定义路径
};
router.push({
path: pathMap[moduleType],
query: { key, proj_key: route.query.proj_key },
});
};