Mint 自研框架设计与实现:从重复开发走向配置驱动(五)

模板页运行时设计与实现

前面的章节已经完成了三项基础工作:

  • BFF 层负责提供接口并托管页面资源;
  • DSL 负责描述站点、菜单、字段和交互行为;
  • 前端工程化负责将 Vue 页面构建为浏览器可以访问的静态资源。

接下来需要将这些能力串联起来,将 DSL 真正转换为可交互的中后台页面。

模板页运行时连接了配置层与视图层,是 Mint 的核心模块。它负责加载项目 DSL、解析菜单和页面配置、选择页面模板,并协调搜索、表格、表单及动态组件之间的交互。

一次完整的页面渲染流程如下:

text 复制代码
浏览器访问页面
      ↓
BFF 返回 Dashboard HTML
      ↓
Dashboard SPA 启动
      ↓
根据 project_key 请求项目 DSL
      ↓
将菜单配置保存到 Pinia
      ↓
根据 URL 匹配菜单和页面类型
      ↓
进入 Schema、Sider、Iframe 或 Custom 页面
      ↓
解析当前菜单项的具体配置
      ↓
渲染页面并建立交互

BFF 接口开发和构建产物落盘已在前文介绍,本章从 Dashboard SPA 启动开始,重点说明前端运行时的解析与渲染过程。

Dashboard 的定位

Dashboard 是 Mint 内置的中后台页面模板,本质上是一个独立的 Vue SPA。它负责读取 DSL 配置,并将站点信息、菜单结构、页面类型和交互行为转换为可运行的前端界面。

Dashboard 本身不实现具体业务,而是充当整个运行时的入口和页面容器。其核心职责包括:

  • 根据路由参数加载对应的项目 DSL;
  • 保存并管理菜单等全局数据;
  • 渲染站点头部和公共布局;
  • 根据 moduleType 进入不同的页面模板;
  • 为 Schema 页面提供配置解析和事件调度能力。

Dashboard 路由设计

DSL 中的 moduleType 用于描述菜单对应的页面类型。Dashboard 需要将其映射到具体的 Vue Router 路径:

moduleType Vue Router 路径 页面用途
schema /view/dashboard/schema 渲染框架内置的标准管理页面
custom /view/dashboard/custom 渲染业务侧自行开发的页面
sider /view/dashboard/sider 渲染包含左侧菜单的复合布局
iframe /view/dashboard/iframe 嵌入第三方页面

Dashboard 的基础路由配置如下:

js 复制代码
// boot.js
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import store from "$mStore/index";
import "ant-design-vue/dist/reset.css";

/**
 * 启动器
 * @param {*} App 根组件
 * @param {object} options 配置项
 * @param {array} options.routes 路由配置项
 * @param {function(app)} callback 可通用callback的vue实例app自行挂载
 */
export default (App, options = {}, callback) => {
  const { routes = [] } = options;
  const app = createApp(App);
  // 挂载store
  app.use(store);
  // 对外提供自定义挂载拓展
  callback && callback(app);
  // 挂载路由
  if (routes && routes.length) {
    const router = createRouter({
      history: createWebHistory(),
      routes,
    });
    app.use(router);
    router.isReady().then(() => {
      // 挂载完成
      app.mount("#app");
    });
  } else {
    app.mount("#app");
  }
};
js 复制代码
import App from "./index";
import boot from "../boot";

const siderRoutes = [
  {
    path: "iframe",
    component: () => import("$mPage/dashboard/complex-view/" + "iframe-view/index.vue"),
  },
  {
    path: "schema",
    component: () => import("$mPage/dashboard/complex-view/" + "schema-view/index.vue"),
  },
];

const routes = [
  {
    path: "/view/dashboard/iframe",
    component: () => import("$mPage/dashboard/complex-view/" + "iframe-view/index.vue"),
  },
  {
    path: "/view/dashboard/schema",
    component: () => import("$mPage/dashboard/complex-view/" + "schema-view/index.vue"),
  },
  {
    path: "/view/dashboard/sider",
    component: () => import("$mPage/dashboard/complex-view/" + "sider-view/index.vue"),
    children: siderRoutes,
  },
];

boot(App, { routes });

其中,sider 页面通过子路由继续承载 schemaiframe 页面,从而实现"左侧菜单布局 + 具体内容页"的嵌套结构。

Dashboard 如何加载和解析 DSL

Dashboard 根组件不承载具体页面逻辑,主要完成以下初始化工作:

  1. 从路由中读取 project_key
  2. 向 BFF 请求对应项目的 DSL;
  3. 将 DSL 中的 menu 数据保存到 Pinia;
  4. 使用 HeaderView 渲染公共布局;
  5. 通过 <router-view /> 承载当前路由对应的具体页面。

HeaderView 的职责包括:

  • 渲染站点 Logo 和标题;
  • 从 Pinia 中读取并渲染菜单;
  • 渲染头部右侧的用户操作区域;
  • <router-view /> 提供下方的内容布局区域。

通过这种拆分,Dashboard 根组件只负责应用初始化和公共布局,具体页面逻辑则由对应的路由组件处理。

不同页面类型的处理方式

Dashboard 根据当前 URL 进入不同的页面组件:

  • /view/dashboard/custom:进入业务侧注册的自定义页面;
  • /view/dashboard/iframe:进入 Iframe 页面,并加载配置指定的第三方地址;
  • /view/dashboard/sider:进入带左侧菜单的复合布局;
  • /view/dashboard/schema:进入框架内置的 Schema 标准页面。

Sider 页面

进入 Sider 页面后,组件会先根据路由参数 key 从菜单数据中找到当前顶部菜单,再根据 sider_key 从其左侧菜单配置中定位当前子菜单。

如果 URL 中不存在 sider_key,系统会默认选择第一个有效菜单项,并跳转到对应页面。这样可以保证用户首次进入 Sider 页面时,左侧菜单能够正确高亮,同时展示默认内容。

Schema 页面

Schema 页面是配置驱动渲染的核心。进入该页面后,顶层 SchemaView 会解析当前菜单项中的 DSL,并得到以下运行时数据:

  • api:列表及相关操作使用的接口地址;
  • tableSchema:表格字段定义;
  • tableConfig:表格按钮及整体配置;
  • searchSchema:搜索字段定义;
  • searchConfig:搜索区域整体配置;
  • componentConfig:弹窗、抽屉等动态组件配置。

SchemaView 通过 Vue 的 provide 将这些响应式数据注入后代组件,供搜索面板、表格面板和动态组件共同使用。

Schema 页面的组件分层

Mint 内置了三个可复用的 Schema 通用组件:

  • SchemaSearchBar:根据 Schema 动态生成搜索表单;
  • SchemaTable:根据 Schema 动态生成表格;
  • SchemaForm:根据 Schema 和数据模型动态生成业务表单。

这些组件只负责通用 UI 和交互,不应直接依赖 Dashboard 的上下文数据。为此,Schema 页面在通用组件与页面运行时之间增加了桥接层:

text 复制代码
SchemaView
├── SearchPanel
│   └── SchemaSearchBar
├── TablePanel
│   └── SchemaTable
└── Dynamic Components
    └── SchemaForm

各层职责如下:

  • SchemaView:解析 DSL、提供共享数据,并负责页面级事件调度;
  • SearchPanel:获取搜索配置并传递给 SchemaSearchBar,同时统一转发搜索事件;
  • TablePanel:获取表格配置并传递给 SchemaTable,同时处理表格级操作;
  • 动态组件:根据 componentConfig 渲染新增、编辑、详情等业务组件。

这种分层使通用组件与具体的数据来源解耦。通用组件可以在框架其他业务场景中复用,而 Dashboard 相关的数据获取和事件编排仍由页面层负责。

搜索面板的渲染与事件处理

SearchPanel 通过 inject 获取 searchSchemasearchConfig,并将其传递给 SchemaSearchBar

SchemaSearchBar 内置查询和重置按钮,并统一暴露 getValuevalidate 方法。组件拿到 Schema 后,会遍历字段配置,根据每个字段的 comType,从 search-item-config.js 注册表中查找对应组件并动态渲染。

目前搜索组件注册表可以包含以下映射:

text 复制代码
input            → 普通输入框
select           → 静态下拉框
dynamicSelect    → 动态下拉框
dateRangePicker  → 日期范围选择器

SchemaSearchBar 抛出的 searchresetload 事件先由 SearchPanel 接收。SearchPanel 对事件进行必要处理后,再以统一的搜索事件通知 SchemaView,由顶层更新搜索参数并触发表格刷新。

表格面板的渲染与事件处理

TablePanel 通过 inject 获取 tableSchematableConfigapi 和搜索参数,再将这些数据传递给 SchemaTable

SchemaTable 根据 tableSchema 动态生成表格列,并根据 api 请求列表数据。表格按钮被点击后,组件会将按钮配置、当前行数据和 eventKey 一并抛给 TablePanel

TablePanel 优先处理与表格本身强相关的事件,例如删除或刷新。如果当前事件不属于表格层的职责,则继续向上抛给 SchemaView,由顶层执行页面级操作,例如打开动态组件。

这一过程形成了清晰的分层事件链:

text 复制代码
SchemaTable 触发操作
        ↓
TablePanel 尝试处理表格级事件
        ↓ 无对应处理器
SchemaView 处理页面级事件

通用表单的动态渲染

SchemaForm 接收 schemamodel 两项数据:

  • schema 描述需要展示的字段、控件类型和校验规则;
  • model 提供表单的初始数据。

组件内部遍历 Schema 字段,根据 comTypeform-item-config.js 注册表中查找对应控件并动态渲染。例如:

text 复制代码
input        → 普通输入框
select       → 下拉选择框
inputNumber  → 数字输入框

各字段组件遵循统一接口,并向 SchemaForm 暴露取值和校验能力。SchemaForm 汇总所有字段组件后,再统一向业务组件提供 getValuevalidate 方法。

相关目录结构

Schema 页面编排层与通用组件层分别位于 Dashboard 和 Widgets 目录中,主要结构如下:

text 复制代码
app/pages
├── dashboard
│   ├── entry.dashboard.js
│   ├── index.vue
│   └── complex-view
│       ├── header-view
│       ├── iframe-view
│       ├── sider-view
│       └── schema-view
│           ├── index.vue
│           ├── hook
│           │   └── use-schema.js
│           ├── complex-view
│           │   ├── search-panel
│           │   └── table-panel
│           └── components
│               ├── component-config.js
│               ├── create-form
│               ├── detail-form
│               └── edit-form
└── widgets
    ├── schema-search-bar
    │   ├── index.vue
    │   ├── search-item-config.js
    │   └── component
    ├── schema-table
    │   └── index.vue
    └── schema-form
        ├── index.vue
        ├── form-item-config.js
        └── components

其中:

  • dashboard/complex-view/schema-view 负责 Schema 页面的解析、编排和事件调度;
  • widgets/schema-* 负责可复用的搜索、表格和表单能力;
  • component-config.js 维护动态组件名称与 Vue 组件实现之间的映射;
  • search-item-config.jsform-item-config.js 分别维护搜索控件与表单控件的注册关系。

动态组件的渲染

SchemaView 从 DSL 中解析出 componentConfig 后,会遍历其中的组件配置,并根据组件名称从 component-config.js 注册表中找到对应的 Vue 组件。

随后,运行时将当前组件配置作为参数传入组件实例,完成新增表单、编辑表单或详情表单等动态组件的渲染。

DSL 只负责声明"使用哪个组件"和"组件需要哪些参数",并不直接依赖组件文件路径。组件注册表负责建立稳定名称与具体实现之间的映射,从而实现配置与代码解耦。

本章小结

模板页运行时将 BFF、DSL 和前端工程化能力连接成完整链路:

  1. Dashboard 根据 project_key 加载项目 DSL;
  2. 根组件将菜单保存到 Pinia,并渲染公共布局;
  3. Vue Router 根据 moduleType 进入对应的页面模板;
  4. Schema 页面将 DSL 解析为搜索、表格和动态组件配置;
  5. 桥接层负责向通用组件传递数据并分发事件;
  6. 通用组件根据组件注册表完成动态渲染;
  7. SchemaView 统一协调页面级状态和交互。

通过这种设计,DSL 负责描述页面,通用组件负责实现基础能力,页面编排层负责连接数据与交互。三者各自保持清晰的职责边界,使框架既能覆盖常见中后台场景,也能为业务定制保留扩展空间。

相关推荐
景同学1 小时前
把 AI 用到线上运维:可行、有效,前提是喂足信息——一次 Full GC 排障实录
java·人工智能·后端
寒水馨1 小时前
Windows下载、安装 Tailwind CSS-v4.3.3(附安装包tailwindcss-windows-x64.exe)
前端·css·前端开发·tailwind css·utility-first·css 框架·独立 cli
西楼_1 小时前
一文读懂React19究竟更新了什么
前端
heimeiyingwang1 小时前
【架构实战】Helm Chart 进阶:依赖管理、测试与 CI/CD 集成
ci/cd·架构
四眼肥鱼1 小时前
【Nextjs】macos 系统运行报错:Error: Cannot find module '../lightningcss.darwin-x64.node'
前端·架构·前端框架
hoLzwEge1 小时前
Unplugin Turbo Console:让你的 `console.log` 脱胎换骨
前端·前端框架
dota李天王1 小时前
简单的 socket 实现
前端·javascript·websocket
盏灯2 小时前
nvm 无法拉取最新办法解决办法
前端