模板页运行时设计与实现
前面的章节已经完成了三项基础工作:
- 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 页面通过子路由继续承载 schema 和 iframe 页面,从而实现"左侧菜单布局 + 具体内容页"的嵌套结构。
Dashboard 如何加载和解析 DSL
Dashboard 根组件不承载具体页面逻辑,主要完成以下初始化工作:
- 从路由中读取
project_key; - 向 BFF 请求对应项目的 DSL;
- 将 DSL 中的
menu数据保存到 Pinia; - 使用
HeaderView渲染公共布局; - 通过
<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 获取 searchSchema 和 searchConfig,并将其传递给 SchemaSearchBar。
SchemaSearchBar 内置查询和重置按钮,并统一暴露 getValue 与 validate 方法。组件拿到 Schema 后,会遍历字段配置,根据每个字段的 comType,从 search-item-config.js 注册表中查找对应组件并动态渲染。
目前搜索组件注册表可以包含以下映射:
text
input → 普通输入框
select → 静态下拉框
dynamicSelect → 动态下拉框
dateRangePicker → 日期范围选择器
SchemaSearchBar 抛出的 search、reset 和 load 事件先由 SearchPanel 接收。SearchPanel 对事件进行必要处理后,再以统一的搜索事件通知 SchemaView,由顶层更新搜索参数并触发表格刷新。
表格面板的渲染与事件处理
TablePanel 通过 inject 获取 tableSchema、tableConfig、api 和搜索参数,再将这些数据传递给 SchemaTable。
SchemaTable 根据 tableSchema 动态生成表格列,并根据 api 请求列表数据。表格按钮被点击后,组件会将按钮配置、当前行数据和 eventKey 一并抛给 TablePanel。
TablePanel 优先处理与表格本身强相关的事件,例如删除或刷新。如果当前事件不属于表格层的职责,则继续向上抛给 SchemaView,由顶层执行页面级操作,例如打开动态组件。
这一过程形成了清晰的分层事件链:
text
SchemaTable 触发操作
↓
TablePanel 尝试处理表格级事件
↓ 无对应处理器
SchemaView 处理页面级事件
通用表单的动态渲染
SchemaForm 接收 schema 和 model 两项数据:
schema描述需要展示的字段、控件类型和校验规则;model提供表单的初始数据。
组件内部遍历 Schema 字段,根据 comType 从 form-item-config.js 注册表中查找对应控件并动态渲染。例如:
text
input → 普通输入框
select → 下拉选择框
inputNumber → 数字输入框
各字段组件遵循统一接口,并向 SchemaForm 暴露取值和校验能力。SchemaForm 汇总所有字段组件后,再统一向业务组件提供 getValue 和 validate 方法。
相关目录结构
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.js和form-item-config.js分别维护搜索控件与表单控件的注册关系。
动态组件的渲染
SchemaView 从 DSL 中解析出 componentConfig 后,会遍历其中的组件配置,并根据组件名称从 component-config.js 注册表中找到对应的 Vue 组件。
随后,运行时将当前组件配置作为参数传入组件实例,完成新增表单、编辑表单或详情表单等动态组件的渲染。
DSL 只负责声明"使用哪个组件"和"组件需要哪些参数",并不直接依赖组件文件路径。组件注册表负责建立稳定名称与具体实现之间的映射,从而实现配置与代码解耦。
本章小结
模板页运行时将 BFF、DSL 和前端工程化能力连接成完整链路:
- Dashboard 根据
project_key加载项目 DSL; - 根组件将菜单保存到 Pinia,并渲染公共布局;
- Vue Router 根据
moduleType进入对应的页面模板; - Schema 页面将 DSL 解析为搜索、表格和动态组件配置;
- 桥接层负责向通用组件传递数据并分发事件;
- 通用组件根据组件注册表完成动态渲染;
SchemaView统一协调页面级状态和交互。
通过这种设计,DSL 负责描述页面,通用组件负责实现基础能力,页面编排层负责连接数据与交互。三者各自保持清晰的职责边界,使框架既能覆盖常见中后台场景,也能为业务定制保留扩展空间。