Elpis 架构演进:五个里程碑
本文总结 Elpis 框架从 0 到 1 的完整架构演进过程,涵盖服务端内核、前端工程化、领域模型、动态组件库、npm 包封装五个核心阶段。
一、Node.js 服务端内核引擎
第一阶段的核心任务是构建一个稳定、可扩展的服务端内核引擎 elpis-core,基于 Koa2 实现。核心设计理念是"框架优先,业务扩展"——通过 Loader 机制实现框架代码和业务代码的自动加载与整合。
Loader 自动加载机制:框架与业务的整合
每个 Loader 都遵循"先框架,后业务"的加载顺序:先扫描框架目录(如 elpis-core 下的 app/controller),再扫描业务项目目录(如业务项目根目录下的 app/controller),将两边的文件统一处理并挂载到同一个对象上(如 app.controller)。文件路径会转为驼峰命名,支持多级目录(如 custom-module/custom-controller.js 对应 app.controller.customModule.customController)。这种机制带来三个价值:框架和业务可以并存;业务同名文件会覆盖框架;业务可以扩展任意新文件,无需改框架。serviceLoader、routerLoader、middlewareLoader 等均采用同样的"框架+业务"整合模式。
业务项目的使用方式
业务项目只需在约定的目录结构下编写代码,框架会自动加载:
业务项目/
├── app/
│ ├── controller/
│ │ └── user.js # 自动加载到 app.controller.user
│ ├── service/
│ │ └── user.js # 自动加载到 app.service.user
│ ├── router/
│ │ └── user.js # 自动注册路由
│ └── router-schema/
│ └── user.js # 自动加载校验规则
业务开发者无需关心加载逻辑,框架在 start 时会自动扫描并加载这些文件。
router 和 router-schema 的解耦联动
Elpis 将路由映射(router)和参数校验(router-schema)拆开管理,再通过中间件串联。业务在 app/router/ 下定义「路径 → controller 方法」的映射,在 app/router-schema/ 下用 JSON Schema 声明各路径、各方法的参数规则(如 body、query 的 type、required 等)。router-schema 由 router-schemaLoader 加载后挂到 app.routerSchema,结构按「路径 → 方法 → schema」组织。请求进入时,api-params-verify 中间件根据 ctx.path 和 ctx.method 从 app.routerSchema 里取对应 schema,用 ajv 校验通过后再放行到 router 匹配到的 controller,校验不通过则直接返回 400。这样路由只负责「谁处理」,schema 只负责「参数长什么样」,职责分离;校验在中间件层统一完成,Controller 无需手写校验逻辑,且能信任已通过校验的入参。
通过 Loader 机制与 router-schema 解耦设计,Elpis 实现框架与业务解耦:业务在约定目录下编写 router、router-schema 等,框架负责加载、整合与关联。
二、Webpack5 工程化建设
第二阶段的核心任务是构建前端工程化体系,基于 Webpack5 实现多环境构建、热更新、业务扩展等能力。
2.1 Alias 机制:框架与业务的协同扩展
Alias 机制不仅仅是简化路径引用,更重要的是实现了框架与业务的协同扩展。
框架能力向业务开放
Elpis 通过 Webpack 的 resolve.alias 配置,将框架内置的组件、工具、配置暴露给业务项目。业务代码可以直接引用框架内部模块,而无需关心实际的物理路径:
// 业务代码中引用框架组件
import HeaderContainer from '$elpisHeaderContainer';
import $curl from '$elpis$curl';
这种设计的核心价值在于:框架升级时,只需调整 Webpack 配置,业务代码无需修改。框架的内部结构可以自由重构,对外暴露的别名保持稳定,确保了向下兼容性。
业务扩展配置的动态注入机制
框架通过检测业务项目中是否存在特定的配置文件,动态决定使用业务配置还是空白模块。
例如:
$businessDashboardRouterConfig:检测./app/pages/dashboard/router.js,允许业务自定义 dashboard 路由配置$businessFormItemConfig:检测./app/pages/widgets/schema-form/form-item-config.js,允许业务扩展自定义表单项组件
实现机制:空白文件替代法
核心代码示例(webpack.base.js:142-146):
const businessDashboardRouterConfig = path.resolve(
process.cwd(),
'./app/pages/dashboard/router.js'
);
aliasMap['$businessDashboardRouterConfig'] = fs.existsSync(
businessDashboardRouterConfig
)
? businessDashboardRouterConfig
: blankModulePath;
这种**"存在则扩展,不存在则忽略"**的机制:
- 使用
fs.existsSync()动态检测业务文件是否存在 - 存在时,alias 指向业务文件,框架使用业务的扩展配置
- 不存在时,alias 指向
blankModulePath(空白 JS 文件:module.exports = {}),Webpack 打包时不会报"模块未找到"错误
这种设计实现了渐进式扩展:业务项目可以从完全使用框架默认配置开始,逐步根据需要添加自定义配置文件,无需一开始就提供完整的配置。框架代码在导入这些别名时,会判断是否为有效配置,从而决定是否应用业务扩展。
包处理规则:require.resolve() 引用声明
在处理第三方依赖时,我们采用了动态解析策略。Webpack 配置中使用 require.resolve() 来声明包的引用路径:
resolve: {
alias: {
'vue': require.resolve('vue'),
'element-plus': require.resolve('element-plus')
}
}
这样做的好处是确保所有模块引用的是同一个包实例,避免了多版本共存导致的奇怪问题(特别是 Vue、React 这类状态管理框架)。require.resolve() 会根据 Node.js 的模块解析规则,找到 node_modules 中实际安装的包路径。
2.2 多环境差异构建
Elpis 通过独立的配置文件实现多环境构建,应对开发和生产环境的不同需求。
配置文件结构
Webpack 配置采用三层结构:
- webpack.base.js:基础配置,包含 entry、alias、loader、plugin 等所有环境共享的配置,以及 Code Splitting 策略(将代码分为 vendor、common、entry 三层,利用浏览器缓存)
- webpack.dev.js:开发环境配置,使用
webpack-merge合并 base 配置 - webpack.prod.js:生产环境配置,使用
webpack-merge合并 base 配置
开发环境特点
开发环境优先考虑开发效率和调试便利性:
// webpack.dev.js
mode: 'development',
devtool: 'eval-cheap-module-source-map', // 快速的 source-map
output: {
publicPath: 'http://127.0.0.1:9002/public/dist/dev/' // 指向开发服务器
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 热更新插件
]
关键配置:
- 开启详细的 source-map,方便定位代码问题
- 配置热更新插件(HotModuleReplacementPlugin)
- Entry 注入 HMR 客户端代码,建立 WebSocket 通信
- 输出路径指向开发服务器的端口
生产环境特点
生产环境优先考虑性能和产物优化:
// webpack.prod.js
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin({
terserOptions: {
compress: { drop_console: true } // 移除 console.log
}
})
]
},
plugins: [
new CleanWebpackPlugin(), // 清理旧文件
new MiniCssExtractPlugin(), // CSS 提取
new CssMinimizerPlugin() // CSS 压缩
]
关键配置:
- 使用 TerserPlugin 压缩 JS,移除 console.log
- 使用 thread-loader 并行处理 JS 和 CSS,提升构建速度
- 提取并压缩 CSS 文件
- 关闭性能提示(
performance.hints: false),避免构建时的警告信息
环境切换
框架提供了两个独立的启动脚本:
- app/webpack/dev.js:开发环境启动脚本,直接引用
webpack.dev.js配置并启动开发服务器 - app/webpack/prod.js:生产环境构建脚本,直接引用
webpack.prod.js配置执行构建
// app/webpack/dev.js
const { webpackConfig, DEV_SERVER_CONFIG } = require('./config/webpack.dev');
// app/webpack/prod.js
const webpackProdConfig = require('./config/webpack.prod.js');
通过调用不同的脚本文件实现环境切换,各环境独立启动,职责清晰。
2.3 自研热更新服务
Elpis 框架采用服务端渲染架构,前端构建后生成模板文件(.tpl)交给后端 Koa 服务渲染。这种架构对开发环境的热更新提出了特殊要求。
架构特殊性
在开发过程中需要同时满足:
- 前端代码改动后即时看到效果(热更新)
- 模板文件必须写入磁盘,供后端 Koa 服务读取
传统的 webpack-dev-server 将所有文件保存在内存中,无法满足模板落盘的需求。同时,前端构建服务(9002 端口)和后端 Koa 服务(8080 端口)分离,涉及跨域资源加载。
自研方案实现
基于以上需求,我们选择了 Express + webpack 中间件的自研方案(位于 app/webpack/dev.js):
const express = require('express');
const webpack = require('webpack');
const devMiddleware = require('webpack-dev-middleware');
const hotMiddleware = require('webpack-hot-middleware');
const app = express();
const compiler = webpack(webpackConfig);
// 开发中间件:监听文件变化、触发编译
app.use(devMiddleware(compiler, {
writeToDisk: (filepath) => filepath.endsWith('.tpl'), // 仅模板文件落盘
publicPath: webpackConfig.output.publicPath,
headers: {
'Access-Control-Allow-Origin': '*' // 跨域支持
}
}));
// 热更新中间件:WebSocket 通信
app.use(hotMiddleware(compiler, {
path: '/__webpack_hmr'
}));
app.listen(9002);
关键配置
writeToDisk 选择性落盘:
- 模板文件(.tpl)写入磁盘,供后端读取
- JS、CSS 等资源保留在内存,加快热更新速度
跨域头配置:
- 前端资源从 9002 端口加载
- 页面在 8080 端口渲染
- 需要设置 CORS 头允许跨域访问
HMR 通信机制:
- webpack.dev.js 在 entry 中注入 HMR 客户端代码
- 客户端通过 WebSocket 连接到
http://127.0.0.1:9002/__webpack_hmr - 文件变化时,服务端推送更新消息,客户端动态替换模块
2.4 业务自定义 Webpack 配置
Elpis 允许业务项目在不修改框架 webpack 配置的情况下,添加自己的构建规则。
扩展机制
框架在加载 webpack 配置时,会尝试读取业务项目的自定义配置:
// webpack.base.js (59-68行)
let businessWebpackConfig = {};
try {
businessWebpackConfig = require(path.resolve(
process.cwd(),
'./app/webpack.config.js'
));
} catch (error) {
console.log('[exception] there is no business webpack config file.');
}
// 合并配置
module.exports = merge.smart(baseConfig, businessWebpackConfig);
扩展方式
业务项目可以创建 ./app/webpack.config.js 文件,添加自定义配置:
- 添加新的 loader(如处理特殊文件格式)
- 添加新的 plugin(如代码分析、性能监控)
- 扩展 alias 别名
- 修改 optimization 优化策略
使用 webpack-merge 的 smart 模式进行配置合并,数组类型的配置会合并,对象类型的配置会深度合并。
三、领域模型架构建设
前两个阶段解决了"服务端内核"和"前端工程化",第三阶段的核心是业务建模能力。
设计理念:80%共性 20%定制化
在管理后台开发中,存在大量标准化、重复性的工作,这些工作技术含量不高但很费时间,每个项目都要做一遍:
80%共性(框架通过配置解决):
- 展示列表:数据表格展示
- 搜索数据:搜索栏、筛选条件
- 新增编辑:表单录入、字段校验
- 数据校验:必填项、格式验证
- 按钮权限控制:增删改查权限
通过统计,管理后台项目里,这类工作占了 80% 的开发时间。Elpis 通过 DSL 配置 + 解析引擎将这些标准功能抽象出来,开发者只需编写配置文件,框架自动渲染对应的 UI 和交互逻辑。
20%定制化(框架提供扩展接口):
- 业务特有逻辑:订单系统的复杂状态流转、库存系统的批量导入导出
- 个性化交互:特殊的图表展示、流程图、复杂的状态联动
- 定制化组件:业务特有的表单控件、展示组件
这些是每个业务不一样的,没法完全通用化。框架通过扩展机制(自定义组件、自定义页面、业务 hook)留接口让开发者自己实现。
划分标准:能不能复用。能复用的、有规律的,就归到 80%,用配置解决;不能复用的、逻辑复杂的,就归到 20%,留接口自己实现。
举例:
- 用户管理功能每个系统都有,这是 80%,通过 Model 配置即可实现
- 某个系统要求用户注册前必须人脸识别,这就是 20%,需要开发者自定义实现
这种设计带来的价值:
- 开发效率提升:80% 的标准功能无需编码,配置即可完成
- 质量保障:框架统一处理标准逻辑,减少重复开发带来的 bug
- 灵活性:20% 的定制化空间确保框架能适应各种复杂业务场景
3.1 Model 配置:统一的领域模型定义
Elpis 通过 Model 配置描述整个应用的业务领域模型,包括菜单结构、数据模型、UI 交互等所有要素,是实现"80%共性"的核心工具。
Model 配置结构
{
mode: 'dashboard', // 模板类型
name: '项目名称',
menu: [ // 菜单配置
{
key: 'user-mgmt',
name: '用户管理',
menuType: 'module', // 枚举:group / module
moduleType: 'schema', // 枚举:sider / iframe / schema / custom
schemaConfig: {
api: '/api/user', // RESTFUL API
schema: { // 字段定义
type: 'object',
properties: {
id: {
type: 'string',
label: '用户ID',
tableOption: { visible: true, width: 120 },
searchOption: { comType: 'input' },
createFormOption: { visible: false },
editFormOption: { visible: false }
},
name: {
type: 'string',
label: '用户名',
tableOption: { visible: true },
searchOption: { comType: 'input' },
createFormOption: { comType: 'input', required: true },
editFormOption: { comType: 'input', required: true }
}
}
},
tableConfig: { // 表格配置
headerButtons: [...],
rowButtons: [...]
},
searchConfig: {}, // 搜索栏配置
componentConfig: { // 动态组件配置
createForm: { title: '创建用户' },
editForm: { title: '编辑用户', mainKey: 'id' }
}
}
}
]
}
Model 配置的特点
通过一个统一的配置,定义:
- 菜单结构(menuType、moduleType)
- 字段模型(schema.properties)
- 各场景展示方式(xxxOption)
- 交互逻辑(tableConfig、componentConfig)
框架解析配置后,自动生成完整的 CRUD 页面,开发者无需编写任何组件代码。
3.2 DSL 解析引擎:动态提取场景化 Schema
框架提供了 DSL 解析引擎,根据组件类型从统一的 schema 中动态提取对应的配置。
解析引擎核心实现
位于 app/pages/dashboard/complex-view/schema-view/hooks/schema.js:
// 通用构建 schema 方法(清除噪音)
const buildDtoSchema = function (_schema, comName) {
const dtoSchema = {
type: 'object',
properties: {},
};
// 提取有效 schema 字段信息
for (const key in _schema.properties) {
const props = _schema.properties[key];
if (props[`${comName}Option`]) {
let dtoProps = {};
// 提取 props 中非 option 的部分
for (const pKey in props) {
if (pKey.indexOf('Option') < 0) {
dtoProps[pKey] = props[pKey];
}
}
// 合并 comName Option
dtoProps = Object.assign({}, dtoProps, {
option: props[`${comName}Option`],
});
dtoSchema.properties[key] = dtoProps;
}
}
return dtoSchema;
};
解析过程
- 输入:完整的 schema 配置 + 组件名称(如
'table'、'search'、'createForm') - 过滤:遍历所有字段,仅提取包含对应
xxxOption的字段 - 转换:将字段基础信息(type、label)+ 对应的 Option 合并为该组件的专属 schema
- 输出:针对特定组件优化的精简 schema
示例:从统一 schema 提取不同组件的 schema
原始 schema 定义:
{
properties: {
id: {
type: 'string',
label: '用户ID',
tableOption: { visible: true, width: 120 },
searchOption: { comType: 'input' }
},
name: {
type: 'string',
label: '用户名',
tableOption: { visible: true },
searchOption: { comType: 'input' },
createFormOption: { comType: 'input', required: true }
}
}
}
经过 buildDtoSchema(_schema, 'table') 提取后:
{
properties: {
id: {
type: 'string',
label: '用户ID',
option: { visible: true, width: 120 }
},
name: {
type: 'string',
label: '用户名',
option: { visible: true }
}
}
}
经过 buildDtoSchema(_schema, 'createForm') 提取后:
{
properties: {
name: { // id 字段被过滤掉(无 createFormOption)
type: 'string',
label: '用户名',
option: { comType: 'input', required: true }
}
}
}
解析引擎的价值
- 按需提取:组件只获取自己需要的字段配置,避免冗余数据传递
- 自动过滤:字段通过
visible: false或缺少对应 Option 自动排除 - 统一接口:所有组件(table/search/form)接收相同结构的 schema,降低组件耦合度
3.3 菜单驱动的页面渲染
Elpis 通过 Model 配置中的 menu 字段驱动整个应用的菜单和页面渲染。
菜单配置支持的类型
menuType 类型:
- group:菜单组,可包含
subMenu子菜单(支持递归嵌套) - module:功能模块,对应具体页面
moduleType 类型(当 menuType 为 module 时):
- sider:侧边栏模块,包含
siderConfig.menu二级菜单 - iframe:内嵌页面,通过
iframeConfig.path加载外部 URL - schema:Schema 驱动的 CRUD 页面,通过
schemaConfig配置 - custom:自定义页面,通过
customConfig.path指定路由路径
菜单查找机制
位于 app/pages/store/menu.js,框架提供了递归查找菜单项的能力:
const findMenuItem = function ({ key, value }, mList = menuList.value) {
for (let i = 0; i < mList.length; i++) {
const menuItem = mList[i];
const { menuType, moduleType } = menuItem;
if (menuItem[key] === value) {
return menuItem;
}
// 递归查找 group 的 subMenu
if (menuType === 'group' && menuItem.subMenu) {
const mItem = findMenuItem({ key, value }, menuItem.subMenu);
if (mItem) return mItem;
}
// 递归查找 sider 的二级菜单
if (moduleType === 'sider' && menuItem.siderConfig?.menu) {
const mItem = findMenuItem({ key, value }, menuItem.siderConfig.menu);
if (mItem) return mItem;
}
}
};
页面渲染流程
- 路由跳转:用户点击菜单时,通过
onMenuSelect回调触发路由跳转,携带key参数
位于 app/pages/dashboard/dashboard.vue:
const onMenuSelect = function (menuItem) {
const { moduleType, key, customConfig } = menuItem;
// 如果是当前页面,不处理
if (key === route.query.key) {
return;
}
const pathMap = {
sider: '/sider',
iframe: '/iframe',
schema: '/schema',
custom: customConfig?.path,
};
router.push({
path: `/view/dashboard${pathMap[moduleType]}`,
query: {
key,
proj_key: route.query.proj_key,
},
});
};
- 菜单查找:通过
findMenuItem在菜单树中递归查找对应的 menuItem - 提取配置:从 menuItem 中提取
schemaConfig(或 iframeConfig、customConfig 等) - 解析渲染:
- 调用
buildDtoSchema分别构造tableSchema、searchSchema、components各组件的 schema - 将 schema 和 config 传递给对应的动态组件(schema-table、schema-search-bar、schema-form 等)
- 调用
- 组件渲染:动态组件根据 schema 自动渲染 UI
代码位于 app/pages/dashboard/complex-view/schema-view/hooks/schema.js:
const buildData = function () {
const { key, sider_key: siderKey } = route.query;
// 1. 查找菜单项
const mItem = menuStore.findMenuItem({
key: 'key',
value: siderKey ?? key,
});
if (mItem && mItem.schemaConfig) {
const { schemaConfig: sConfig } = mItem;
const configSchema = JSON.parse(JSON.stringify(sConfig.schema));
api.value = sConfig.api ?? '';
// 2. 构造 tableSchema 和 tableConfig
tableSchema.value = buildDtoSchema(configSchema, 'table');
tableConfig.value = sConfig.tableConfig;
// 3. 构造 searchSchema 和 searchConfig
searchSchema.value = buildDtoSchema(configSchema, 'search');
searchConfig.value = sConfig.searchConfig;
// 4. 构造 components(createForm/editForm/detailPanel 等)
const { componentConfig } = sConfig;
if (componentConfig && Object.keys(componentConfig).length > 0) {
const dtoComponents = {};
for (const comName in componentConfig) {
dtoComponents[comName] = {
schema: buildDtoSchema(configSchema, comName),
config: componentConfig[comName],
};
}
components.value = dtoComponents;
}
}
};
3.4 Model 配置的核心地位
在 Elpis 的架构设计中,Model 配置是前后端协作的核心契约,具有极高的重要性。
Model 配置的三重角色
- 数据契约:通过 schema 字段定义明确前后端交互的数据结构、字段类型、必填项、取值范围
- UI 驱动:通过各种
xxxOption配置驱动前端组件的自动渲染(表格、表单、搜索栏等) - 业务语义:通过字段的
label、description等传达业务含义,形成团队的"通用语言"
Model 配置驱动的开发流程
在 Elpis 中,Model 配置是开发的起点:
- 业务分析阶段:梳理业务需求,编写 Model 配置(定义菜单、字段、交互逻辑)
- 前端渲染:框架自动解析 Model 配置,渲染表格、表单、搜索栏等组件
- 后端对接:后端根据 Model 中的
api字段提供 RESTFUL 接口,返回数据格式与 schema 对齐 - 联调验证:Model 配置作为验收标准,确保前后端数据结构一致
这种"配置驱动"的理念带来的价值:
- 快速开发:一个标准的 CRUD 页面,只需编写 Model 配置,无需写任何组件代码
- 一致性保障:字段定义统一,避免前后端数据结构不一致
- 易于维护:需求变更时,只需修改 Model 配置,所有相关页面自动同步
Model 配置示例
以用户管理为例,Model 配置的核心结构:
{
menu: [{
key: 'user-mgmt',
name: '用户管理',
moduleType: 'schema',
schemaConfig: {
api: '/api/user',
schema: {
properties: {
name: {
type: 'string',
label: '用户名',
tableOption: { visible: true }, // 表格中显示
searchOption: { comType: 'input' }, // 搜索栏用输入框
createFormOption: { comType: 'input', required: true }, // 创建表单必填
editFormOption: { comType: 'input', required: true } // 编辑表单必填
}
}
},
tableConfig: { headerButtons: [...], rowButtons: [...] },
componentConfig: { createForm: {...}, editForm: {...} }
}
}]
}
四、Vue3 动态组件库建设
第四阶段的核心任务是构建基于 JsonSchema 驱动的动态组件库,实现"配置即页面"的能力。
4.1 核心组件体系
Elpis 提供了一套完整的 Schema 驱动的 UI 组件库,位于 app/pages/widgets/ 目录:
核心组件:
- schema-table:表格组件,根据 tableSchema 自动渲染表格列、分页、操作按钮
- schema-search-bar:搜索栏组件,根据 searchSchema 自动渲染搜索条件
- schema-form:表单组件,根据 formSchema 自动渲染表单项、校验规则
- schema-view:综合视图组件,整合 table、search-bar、form 为完整的 CRUD 页面
组件特点:
- Schema 驱动:所有组件通过 JSON Schema 配置自动渲染
- 可扩展:支持业务自定义组件类型
- 类型丰富:支持 input、select、date、upload 等常见表单控件
4.2 schema-form:表单项自动渲染
schema-form 根据 schema 配置自动渲染表单项,支持各种 Element Plus 表单控件。
核心能力:
- 根据
option.comType自动选择对应的表单控件(input/select/date/upload 等) - 自动处理表单校验(必填、格式、自定义规则)
- 支持业务自定义表单项
扩展机制:
扩展发生在业务项目侧:框架在 form-item-config.js 中通过 $businessFormItemConfig 合并业务项目的配置(Webpack 将该别名指向业务项目下的 app/pages/widgets/schema-form/form-item-config.js,若不存在则指向空白模块)。业务项目在使用框架时,若需要自定义表单项,在业务项目下新增自定义组件,并在业务项目的 form-item-config 中注册;在 Model 配置中为该字段指定对应 comType 即可使用。
4.3 schema-table:表格自动渲染
schema-table 根据 schema 配置自动渲染表格,支持列配置、分页、操作按钮等。
核心能力:
- 根据
tableOption自动渲染表格列(列宽、对齐、格式化等) - 支持表头操作按钮(由 table-panel 配合 tableConfig.headerButtons 渲染)、行操作按钮(tableConfig.rowButtons)
操作按钮机制:
通过 tableConfig 配置操作按钮的事件:
{
tableConfig: {
headerButtons: [
{
label: '创建用户',
eventKey: 'showComponent',
eventOption: { comName: 'createForm' }
}
],
rowButtons: [
{
label: '编辑',
eventKey: 'showComponent',
eventOption: { comName: 'editForm' }
},
{
label: '删除',
eventKey: 'remove',
eventOption: {
params: { id: 'schema::id' } // 从表格行数据中取 id
}
}
]
}
}
4.4 schema-search-bar:搜索栏自动渲染
schema-search-bar 根据 schema 配置自动渲染搜索条件。
核心能力:
- 根据
searchOption.comType自动选择搜索控件类型 - 支持多种搜索控件(input、select、date-range、cascader 等)
- 自动处理搜索参数的收集和传递
扩展机制:
与 schema-form 类似,业务可以扩展自定义搜索项类型
4.5 动态组件注册机制
Elpis 通过 component-config.js 实现动态组件的注册和使用。
位于 app/pages/dashboard/complex-view/schema-view/components/component-config.js:
import CreateForm from './create-form/create-form.vue';
import EditForm from './edit-form/edit-form.vue';
import DetailPanel from './detail-panel/detail-panel.vue';
// 业务扩展 component 配置
import BusinessComponentConfig from '$businessComponentConfig';
const ComponentConfig = {
createForm: { component: CreateForm },
editForm: { component: EditForm },
detailPanel: { component: DetailPanel },
};
export default {
...ComponentConfig,
...BusinessComponentConfig,
};
使用方式:
在 Model 配置的 componentConfig 中声明要使用的组件:
{
componentConfig: {
createForm: {
title: '创建用户',
saveBtnText: '保存'
},
editForm: {
mainKey: 'id',
title: '编辑用户',
saveBtnText: '更新'
}
}
}
框架会自动:
- 根据
componentConfig的 key(如createForm)查找对应的组件 - 提取对应的 schema(通过
buildDtoSchema(_schema, 'createForm')) - 将 schema 和 config 传递给组件
- 在需要时(如点击"创建用户"按钮)动态渲染该组件
扩展机制:
与 schema-form 类似,业务可以扩展自定义搜索项类型
4.6 组件库的设计价值
标准化:
- 统一的 Schema 接口,所有组件遵循相同的数据协议
- 统一的交互模式,降低用户学习成本
灵活性:
- 支持业务自定义表单项、搜索项
- 支持业务自定义动态组件
- 支持业务覆盖默认组件行为
可维护性:
- 组件逻辑集中管理,统一升级
- Schema 配置与组件实现分离,需求变更时只需修改配置
开发效率:
- 一个完整的 CRUD 页面,从编写 Model 配置到页面渲染,无需编写任何组件代码
- 新增字段、修改展示逻辑,只需调整 Model 配置
五、npm 包抽象封装
第五阶段的核心任务是将 Elpis 框架封装为可发布的 npm 包,实现框架与业务的完全解耦。
5.1 包结构设计
Elpis 作为一个完整的框架,包含了服务端内核、前端工程化、Vue3 组件库等多个模块。
入口与使用:
index.js 作为包的统一入口,对外主要暴露两个能力:serverStart 与 frontendBuild。业务项目安装 @5c24/elpis 后,通过 serverStart(options) 启动服务端(options 透传给 elpis-core),通过 frontendBuild(env) 编译构建前端(env 为 dev 或 production 分别走开发构建与生产构建)。
5.2 框架与业务的解耦
通过 npm 包封装,实现了框架代码与业务代码的完全分离。
框架职责:
- 提供 Koa 服务端内核
- 提供 Webpack 工程化能力
- 提供 Vue3 组件库
- 提供 Loader 机制
- 提供配置管理
业务职责:
- 编写 Model 配置
- 实现业务 Controller/Service
- 实现自定义组件
- 配置环境变量
- 编写业务逻辑
目录结构:
业务项目/
├── node_modules/
│ └── @5c24/elpis/ # 框架代码
├── app/
│ ├── controller/ # 业务 controller
│ ├── service/ # 业务 service
│ ├── router/ # 业务路由
│ ├── router-schema/ # 业务路由 schema
│ ├── pages/ # 业务前端页面
│ └── config/ # 业务配置
├── config/
│ ├── config.default.js
│ ├── config.local.js
│ ├── config.beta.js
│ └── config.prod.js
└── package.json
六、未来发展方向
6.1 TypeScript 类型支持
现状:
- 当前框架使用 JavaScript 编写
- 缺少类型约束,容易出现运行时错误
方向:
- 使用 TypeScript 重构框架核心代码
- 为 Model 配置提供完整的类型定义
- 为动态组件提供泛型支持
- 为业务开发提供类型提示和编译时校验
价值:
- 减少低级错误
- 提升开发体验
- 便于大型项目维护
6.2 低代码可视化编辑器
现状:
- Model 配置通过手写 JSON 完成
- 需要一定的学习成本
方向:
- 开发可视化的 Model 配置编辑器
- 拖拽式配置字段、按钮、表单项
- 实时预览配置效果
- 导出 Model 配置文件
价值:
- 降低使用门槛
- 非技术人员也能配置页面
- 提升配置效率
6.3 多端支持
现状:
- 仅支持 PC 端管理后台
方向:
- 移动端响应式适配
- 小程序版本支持
- App 版本支持(React Native / Flutter)
价值:
- 扩大框架应用场景
总结
Elpis 框架从 0 到 1 的演进,经历了五个核心阶段:
- Node.js 服务端内核引擎:基于 Koa2 实现 Loader 机制,通过目录约定和自动加载,最大程度降低框架与业务的耦合
- Webpack5 工程化建设:实现多环境构建、自研热更新、业务扩展能力
- Vue3 领域模型架构建设:通过 Model 配置 + DSL 解析引擎实现"80%共性 20%定制化"
- Vue3 动态组件库建设:基于 JsonSchema 驱动的组件库,实现"配置即页面"
- npm 包抽象封装:实现框架与业务的完全解耦
核心设计理念:
- 框架优先,业务扩展:Loader 机制确保框架能力与业务逻辑的有机结合
- 配置驱动:通过 Model 配置描述业务模型,框架自动渲染页面
- 80%共性 20%定制化:标准功能通过配置实现,特殊需求通过扩展机制实现
- 渐进式增强:业务可以从最小配置开始,逐步扩展功能