基于 vue3 完成领域模型架构建设

前言

在做后台业务开发的时候,你是否也跟我一样遇到这样的痛点:

  • 每个页面都要重复的编写表格,搜索表单,按钮事件啊这一系列的工作。
  • 相同的功能可能就因为表格哪些东西显示的不一样,或者新增表单一些不一样又重新开发。
  • 一但产品有需求变化的时候就可能好多个页面的组件都要改。
  • 新来的同事接到项目的时候需要耗费很多时间理解大量看似页面展示的内容不同,而代码一大堆的重复。

今天我来分享一套基于JSON Schema规范配置驱动的架构,通过一处配置,多处生效的设计理念,将减轻许多重复的业务开发,让你的业务开发效率得到一大部分提升。

一、设计理念:schema 驱动配置模式

传统的开发模式 VS schema 驱动配置模式。

传统模式:每个页面都要写组件、样式、逻辑

vue 复制代码
<template>
  <div>
    <el-table :data="tableData">
      <el-table-column prop="product_id" label="商品ID" width="300" />
      <el-table-column prop="product_name" label="商品名称" width="200" />
    </el-table>
    <el-form>
      <el-form-item label="商品名称">
        <el-select v-model="searchForm.product_name">
        </el-select>
      </el-form-item>
    </el-form>
  </div>
</template>

schema 去驱动配置模式:只需要遵从项目中docs目录下dashboard-model.js文件的规范结构

注:dashboard-model.js 文件是自己自定义的一套数据,并且遵循 JSON Schema 规范。

dashboardModel.js 复制代码
module.exports = {
  menu: [{
    key: "product",
    name: "商品管理",
    moduleType: "schema",
    schemaConfig: {
      api: "/api/proj/product",
      schema: {
        type: "object",
        properties: {
          product_id: {
            type: "string",
            label: "商品ID",
            tableOption: { width: 300, "show-overflow-tooltip": true },
            searchOption: { comType: "input", placeholder: "请输入商品ID" }
          },
          product_name: {
            type: "string", 
            label: "商品名称",
            tableOption: { width: 200 },
            searchOption: { 
              comType: "dynamicSelect",
              api: "/api/proj/product_enum/list"
            }
          }
        }
      }
    }
  }]
}

二、架构设计

架构图

三、实现细节:关键代码解析

一、DSL 语法定义阶段

1. 完整 DSL 语法规范(docs/dashboard-model.js)

javascript 复制代码
module.exports = {
  mode: "dashbord",           // DSL 根节点类型
  name: "",                   // 业务名称
  menu: [                     // 菜单 DSL 语法
    {
      key: "",                // 唯一标识符
      name: "",               // 显示名称
      menuType: "",           // 枚举:group/module
      moduleType: "",         // 枚举:sider/iframe/custom/schema
      
      // 当 moduleType === sider 时
      siderConfig: {
        menu: [/* 递归 menuItem */]
      },
      
      // 当 moduleType === iframe 时  
      iframeConfig: {
        path: ""              // iframe 路径
      },
      
      // 当 moduleType === custom 时
      customConfig: {
        path: ""              // 自定义路由路径
      },
      
      // 当 moduleType === schema 时
      schemaConfig: {
        api: "",              // 数据源 API
        schema: {             // JSON Schema 扩展语法
          type: "object",
          properties: {
            field: {
              type: "",       // 标准 JSON Schema
              label: "",      // DSL 扩展:显示标签
              tableOption: {}, // DSL 扩展:表格配置
              searchOption: {} // DSL 扩展:搜索配置
            }
          }
        },
        tableConfig: {},      // 表格配置
        searchConfig: {}      // 搜索配置
      }
    }
  ]
};

二、DSL 实例编写阶段

1. 基础模型 DSL 实例

javascript 复制代码
// model/buiness/model.js
module.exports = {
  model: "dashbord",
  name: "电商系统",
  menu: [
    {
      key: "product",
      name: "商品管理",
      menuType: "module",
      moduleType: "schema",    // Schema DSL
      schemaConfig: { /* ... */ }
    },
    {
      key: "order", 
      name: "订单管理",
      menuType: "module",
      moduleType: "custom",    // Custom DSL
      customConfig: {
        path: "/todo"
      }
    },
    {
      key: "data",
      name: "数据分析", 
      menuType: "module",
      moduleType: "sider",     // Sider DSL
      siderConfig: {
        menu: [
          {
            key: "analysis",
            name: "电商罗盘",
            menuType: "module",
            moduleType: "custom",
            customConfig: { path: "/todo" }
          },
          {
            key: "search",
            name: "信息查询",
            menuType: "module", 
            moduleType: "iframe", // Iframe DSL
            iframeConfig: {
              path: "https://www.pinduoduo.com/"
            }
          }
        ]
      }
    }
  ]
};

三、DSL 编译阶段

1. 词法分析(文件扫描与类型识别)

javascript 复制代码
// model/index.js - DSL 词法分析器
const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));

fileList.forEach((file) => {
  // 识别 DSL 文件类型
  const type = filePath.indexOf(`${sep}project${sep}`) > -1 ? "project" : "model";
  
  if (type === "model") {
    // 解析基础模型 DSL
    const modelKey = extractModelKey(filePath);
    modelItem.model = require(filePath);
  }
  
  if (type === "project") {
    // 解析项目 DSL
    const { modelKey, projectKey } = extractKeys(filePath);
    modelItem.project[projectKey] = require(filePath);
  }
});

2. 语法分析(继承合并)

javascript 复制代码
// DSL 语法分析器 - 处理所有模块类型的继承
const projectExtendModel = (model, project) => {
  return _.mergeWith({}, model, project, (modelValue, projectValue) => {
    if (Array.isArray(modelValue) && Array.isArray(projectValue)) {
      // 处理菜单数组的合并
      const results = [];
      
      // 按 key 匹配进行语法分析
      for (let i = 0; i < modelValue.length; ++i) {
        const modelItem = modelValue[i];
        const projectItem = projectValue.find(
          item => item.key === modelItem.key
        );
        
        // 递归解析所有模块类型的 DSL 结构
        results.push(
          projectItem ? projectExtendModel(modelItem, projectItem) : modelItem
        );
      }
      
      return results;
    }
  });
};

四、DSL 执行阶段

1. 路由分发器(entry.dashboard.js)

javascript 复制代码
// 根据 moduleType 分发到不同的渲染器
const routes = [
  // Schema DSL 渲染器
  {
    path: "/view/dashboard/schema",
    component: () => import("./complex-view/schema-view/schema-view.vue"),
  },
  
  // Iframe DSL 渲染器
  {
    path: "/view/dashboard/iframe", 
    component: () => import("./complex-view/iframe-view/iframe-view.vue"),
  },
  
  // Custom DSL 渲染器
  {
    path: "/view/dashboard/todo",
    component: () => import("./todo/todo.vue"),
  },
  
  // Sider DSL 渲染器(支持嵌套)
  {
    path: "/view/dashboard/sider",
    component: () => import("./complex-view/sider-view/sider-view.vue"),
    children: [
      { path: "iframe", component: () => import("./complex-view/iframe-view/iframe-view.vue") },
      { path: "schema", component: () => import("./complex-view/schema-view/schema-view.vue") },
      { path: "todo", component: () => import("./todo/todo.vue") },
    ],
  },
];

2. 主控制器(dashboard.vue)

javascript 复制代码
// DSL 执行主控制器
const onMenuSelect = (menuItem) => {
  const { moduleType, key, customConfig } = menuItem;
  
  // 根据 moduleType 分发到不同的执行器
  const pathMap = {
    sider: '/sider',           // Sider DSL 执行器
    iframe: '/iframe',         // Iframe DSL 执行器  
    custom: customConfig?.path, // Custom DSL 执行器
    schema: '/schema',         // Schema DSL 执行器
  };

  router.push({
    path: `/view/dashboard${pathMap[moduleType]}`,
    query: { key, proj_key: route.query.proj_key }
  });
};

五、DSL 渲染阶段

1. Schema DSL 渲染器

javascript 复制代码
// complex-view/schema-view/hook/schema.js
export const useSchema = () => {
  // Schema DSL 执行逻辑
  const buildDtoSchema = (_schema, comName) => {
    // 将 Schema DSL 转换为组件可用的格式
  };
  
  return { api, tableSchema, tableConfig, searchSchema, searchConfig };
};

2. Iframe DSL 渲染器

javascript 复制代码
// complex-view/iframe-view/iframe-view.vue
const setPath = () => {
  const { key, sider_key: siderKey } = route.query;
  const menuItem = menuStore.findMenuItem({
    key: 'key',
    value: siderKey ?? key,
  });
  // 执行 Iframe DSL,获取 iframe 路径
  path.value = menuItem?.iframeConfig?.path ?? '';
};

3. Custom DSL 渲染器

javascript 复制代码
// 直接路由到自定义组件
// customConfig.path 指向具体的 Vue 组件

4. Sider DSL 渲染器

javascript 复制代码
// complex-view/sider-view/sider-view.vue
const setMenuList = () => {
  const menuItem = menuStore.findMenuItem({
    key: 'key',
    value: route.query.key,
  });
  // 执行 Sider DSL,获取子菜单列表
  if (menuItem && menuItem.siderConfig && menuItem.siderConfig.menu) {
    menuList.value = menuItem.siderConfig.menu;
  }
};

const handleMenuSelect = (menuKey) => {
  const menuItem = menuStore.findMenuItem({
    key: 'key',
    value: menuKey,
  });
  const { moduleType, key, customConfig } = menuItem;
  
  // 递归执行子菜单的 DSL
  const pathMap = {
    iframe: '/iframe',
    schema: '/schema', 
    custom: customConfig?.path,
  };
  
  router.push({
    path: `/view/dashboard/sider${pathMap[moduleType]}`,
    query: {
      key: route.query.key,
      sider_key: key,
      proj_key: route.query.proj_key,
    }
  });
};

六、完整 DSL 执行流程图

graph TD A[DSL 语法定义] --> B[DSL 实例编写] B --> C[词法分析 - 文件扫描] C --> D[语法分析 - 继承合并] D --> E[语义分析 - 模块类型验证] E --> F[路由分发器] F --> G{模块类型判断} G -->|schema| H[Schema DSL 执行器] G -->|iframe| I[Iframe DSL 执行器] G -->|custom| J[Custom DSL 执行器] G -->|sider| K[Sider DSL 执行器] H --> L[Schema 渲染器] I --> M[Iframe 渲染器] J --> N[Custom 渲染器] K --> O[Sider 渲染器] O --> P{子菜单类型} P -->|schema| H P -->|iframe| I P -->|custom| J P -->|sider| K

七、DSL 目录结构

1. DSL 语法定义层

bash 复制代码
docs/
├── dashboard-model.js          # DSL 语法规范文档
├── api-schema.js              # API 参数 DSL 规范
└── component-schema.js        # 组件配置 DSL 规范

2. DSL 实例编写层

bash 复制代码
model/
├── index.js                   # DSL 解析器入口
├── buiness/                   # 电商业务域
│   ├── model.js              # 基础 DSL 实例
│   └── project/              # 项目 DSL 实例
│       ├── jd.js             # 京东 DSL 配置
│       ├── pdd.js            # 拼多多 DSL 配置
│       └── taobao.js         # 淘宝 DSL 配置
├── course/                    # 课程业务域
    ├── model.js              # 基础 DSL 实例
    └── project/
        ├── bilibili.js       # B站 DSL 配置
        └── dy.js             # 抖音 DSL 配置

3. DSL 编译器层

ruby 复制代码
elpis-core/
├── index.js                   # DSL 编译器入口
├── loader/                    # DSL 加载器
│   ├── config.js             # 配置加载器
│   ├── controller.js         # 控制器加载器
│   ├── middleware.js         # 中间件加载器
│   ├── router.js             # 路由加载器
│   ├── router-schema.js      # 路由 Schema 加载器
│   ├── service.js            # 服务加载器
│   └── extend.js             # 扩展加载器
└── env.js                    # 环境配置

4. DSL 运行时层

csharp 复制代码
app/
├── controller/                # DSL 执行控制器
│   ├── base.js
│   ├── business.js           # 业务 DSL 执行器
│   ├── project.js            # 项目 DSL 执行器
│   └── view.js               # 视图 DSL 执行器
├── middleware/               # DSL 中间件
│   ├── api-params-verify.js  # API 参数验证中间件
│   ├── api-sign-verify.js    # API 签名验证中间件
│   ├── error-handler.js      # 错误处理中间件
│   └── project-handler.js    # 项目处理中间件
├── router-schema/            # API Schema DSL
│   ├── business.js           # 业务 API Schema
│   └── project.js            # 项目 API Schema
├── service/                  # DSL 服务层
│   ├── base.js
│   └── project.js
└── pages/                    # DSL 渲染层
    ├── dashboard/            # 仪表板 DSL 渲染
    │   ├── complex-view/     # 视图 DSL 渲染组件目录
    │   │   ├── schema-view/  # Schema DSL 渲染组件
    │   │   │   ├── hook/
    │   │   │   │   └── schema.js  # Schema DSL 执行钩子
    │   │   │   └── schema-view.vue
    │   │   ├── iframe-view/  # Iframe DSL 渲染组件
    │   │   ├── sider-view/   # Sider DSL 渲染组件
    │   │   └── header-view/  # Header DSL 渲染组件
    │   └── dashboard.vue
    ├── project-list/         # 项目列表 DSL 渲染
    ├── store/                # DSL 状态管理
    │   ├── index.js
    │   ├── menu.js           # 菜单 DSL 状态
    │   └── project.js        # 项目 DSL 状态
    └── widgets/              # DSL 组件库
        ├── schema-search-bar/    # 搜索栏 DSL 组件
        ├── schema-table/         # 表格 DSL 组件
        ├── header-container/     # 头部 DSL 组件
        └── sider-container/      # 侧边栏 DSL 组件

四、未来规划与总结

一、未来规划

  1. TypeScript支持:提供类型安全的配置
  2. 可视化配置:提供配置管理界面

二、总结

这套 DSL 的架构设计,通过一套配置,实现了

  • 开发效率的替身
  • 维护成本的降低
  • 减少了重复代码的编写
  • 支持项目的定制
相关推荐
今禾2 小时前
Git完全指南(中篇):GitHub团队协作实战
前端·git·github
Tech_Lin2 小时前
前端工作实战:如何在vite中配置代理解决跨域问题
前端·后端
XiaoSong2 小时前
React Native 主题配置终极指南,一篇文章说透
前端·react native·react.js
NicolasCage2 小时前
Eslint v9 扁平化配置学习
前端·eslint
shayudiandian2 小时前
Chrome性能优化秘籍技术
前端·chrome·性能优化
嬉皮客3 小时前
TailwindCSS 初探
前端·css
林希_Rachel_傻希希3 小时前
Express 入门全指南:从 0 搭建你的第一个 Node Web 服务器
前端·后端·node.js