@hanvzur/elpis
| Install | |
|---|---|
npm install @hanvzur/elpis |
|
| Latest Version: | 1.0.2 |
| Vue: | 3 |
| License: | ISC |
| Last Updated: | Sep 13, 2026 |
| Links: | Repository · npm |
Elpis
基于 Koa + Vue3 的企业级全栈开发框架(SDK)。后端提供开箱即用的服务端容器与目录约定,前端内置一套 Webpack 工程化体系(多页面、多环境构建),并通过 Schema 驱动的方式快速搭建管理台页面(表格 / 搜索 / 表单 / 侧边栏布局)。
业务项目只需安装本包、按目录约定放置文件,即可获得:
- 后端:自动加载的 controller / service / router / 中间件 / 配置 / 扩展
- 前端:多入口(entry.*.js)自动注册、HMR 开发服务、生产构建
- 页面:schema 驱动的 dashboard(菜单、路由、搜索栏、表格、表单全部由配置生成)
- 扩展:业务侧组件覆盖机制、自定义路由、API 签名校验、参数校验(json-schema + ajv)
目录
架构总览
┌─────────────────────────── 业务项目 (elpis-demo) ───────────────────────────┐
│ server.js 调用 elpis.serverStart() 启动服务 │
│ build.js 调用 elpis.frontendBuild(env) 执行前端构建 │
│ app/ 业务前后端代码(controller / service / router / pages ...) │
│ model/ schema 模型与项目配置 │
│ config/ 环境配置 │
└──────────────────────────────────┬──────────────────────────────────────────┘
│ require('@hanvzur/elpis')
┌──────────────────────────────────▼──────────────────────────────────────────┐
│ Elpis (@hanvzur/elpis) │
│ index.js 对外导出 serverStart / frontendBuild │
│ eplis-core/ Koa 服务容器(loader 体系自动装配业务目录) │
│ app/ 框架内置后端(controller/service/中间件) + 前端页面(dashboard) │
│ app/webpack/ 前端工程化(base/dev/prod 配置、dev-server、生产构建) │
└─────────────────────────────────────────────────────────────────────────────┘
serverStart(options):启动 Koa 服务(默认3001),自动装载框架侧与业务侧代码;_ENV=local时同时启动 Webpack dev-server(9002,HMR 热更新)frontendBuild(env):local走 dev-server;prod执行生产构建,产物输出到业务项目app/public/dist/prod/
安装
npm install @hanvzur/elpis
前置要求:Node.js >= 14,Webpack 构建相关依赖见 发布到 npm 一节的说明。
快速开始
1. 初始化项目
mkdir elpis-demo && cd elpis-demo
npm init -y
npm install @hanvzur/elpis
2. 创建服务入口 server.js
const { serverStart } = require('@hanvzur/elpis')
const app = serverStart({
name: 'ElpisDemo', // 页面标题
homePage: '/view/dashboard', // 未匹配路由时的重定向首页
})
3. 创建构建入口 build.js
const { frontendBuild } = require('@hanvzur/elpis')
frontendBuild(process.env._ENV)
4. 配置 package.json 脚本
{
"scripts": {
"dev": "set _ENV=local&& nodemon ./server.js",
"beta": "set _ENV=beta&& nodemon ./server.js",
"prod": "set _ENV=prod&& nodemon ./server.js",
"build:dev": "set _ENV=local&& node --max_old_space_size=4096 ./build.js",
"build:prod": "set _ENV=prod&& node ./build.js"
}
}
Linux / macOS 请把
set _ENV=xxx&&换成cross-env _ENV=xxx。
5. 启动
npm run dev # http://localhost:3001 (前端资源由 9002 dev-server 内存提供)
访问 http://localhost:3001/view/{页面名} 即可渲染对应页面,例如 /view/dashboard。
目录约定
框架按 约定优于配置 的原则自动扫描并装载以下目录(框架侧与业务侧同名机制合并):
| 目录(业务项目根下) | 说明 | 装载方式 |
|---|---|---|
server.js |
服务入口,调用 serverStart() |
手动 |
build.js |
构建入口,调用 frontendBuild() |
手动 |
app/controller/ |
控制器,module.exports = (app) => class extends base |
挂到 app.controllers |
app/service/ |
服务层,module.exports = (app) => class extends base |
挂到 app.services |
app/router/ |
路由,module.exports = (app, router) => {} |
注册到 KoaRouter |
app/route-schema/ |
API 参数规则(json-schema),按 path 聚合 | 挂到 app.routerSchema |
app/middleware.js |
全局中间件 module.exports = (app) => {} |
app.use(...) |
app/middleware/*.js |
命名中间件 | 挂到 app.middlewares |
app/extend/ |
扩展(如 logger.js → app.logger) |
挂到 app.extend |
config/config.default.js |
默认配置 | 合并到 app.config |
| `config/config.{local | beta | prod}.js` |
app/pages/**/entry.*.js |
前端多页面入口 | Webpack entry + HTML 模板 |
app/pages/dashboard/router.js |
dashboard 自定义路由(见下文) | Webpack alias 注入 |
app/pages/widgets/ |
业务侧组件覆盖(见下文) | Webpack alias 注入 |
model/*/model.js |
模型(业务域)配置 | schema 数据源 |
model/*/project/*.js |
项目配置,继承所属模型 | schema 数据源 |
框架启动流程(eplis-core/index.js):
env → middleware → routerSchema → controller → service → config → extend
→ 框架全局中间件 → 业务全局中间件 → router → listen(PORT=3001)
后端开发指南
Controller
// app/controller/business.js
module.exports = (app) => {
return class BusinessController extends app.controllers.base {
async list(ctx) {
const data = await app.services.business.getList()
this.success(ctx, data) // { success: true, data }
// this.fail(ctx, '错误信息', 50000) // { success: false, message, code }
}
}
}
Router 与路由约定
// app/router/business.js
module.exports = (app, router) => {
router.get('/api/business/list', app.controllers.business.list.bind(app.controllers.business))
}
内置路由:
| 路由 | 说明 |
|---|---|
GET /view/:page |
渲染前端页面(nunjucks 模板 dist/entry.{page}.tpl) |
GET /api/project?proj_key= |
获取项目配置(含 menu) |
GET /api/project/list |
项目列表 |
GET /api/project/model_list |
模型 + 项目结构化数据(首页卡片数据源) |
其他 GET * |
302 重定向到 homePage |
API 签名校验
所有 /api/ 请求需携带签名头(由前端封装的 $curl 自动完成):
s_t:当前毫秒时间戳s_sign:md5(signKey + '_' + s_t)
校验失败返回 { success: false, code: 445 };时间戳超过 600 秒视为过期。signKey 前后端一致(见 app/middleware/api-sign-verify.js),生产环境建议将其改为从配置读取。
参数校验(json-schema + ajv)
在 app/route-schema/ 中按路径声明请求规则,api-params-verify 中间件自动校验,失败返回 code: 442:
// app/route-schema/business.js
module.exports = {
'/api/business/list': {
get: {
query: {
type: 'object',
properties: { page: { type: 'string' } },
required: ['page']
}
}
}
}
项目级接口(/api/proj/)
以 /api/proj/ 为前缀的接口会被 project-handler 中间件拦截,要求请求头携带 proj_key(前端 $curl 对 /api/proj/ 请求自动注入 window.projKey),缺失返回 code: 446。ctx.projKey 可在后续中间件 / 控制器中获取。
前端开发指南
页面入口
在 app/pages/<页面名>/ 下创建 entry.<页面名>.js(命名必须以 entry. 开头),框架自动完成:
- 注册为 Webpack 多页面入口
- 生成
dist/entry.<页面名>.tpl模板(/view/<页面名>渲染)
// app/pages/dashboard/entry.dashboard.js
import boot from '$elpisBoot'
import dashboard from './dashboard.vue'
boot(dashboard, { routes: [] }) // routes 为空则直接挂载;有值则启用 vue-router
全局别名(Webpack alias)
| 别名 | 指向 | 用途 |
|---|---|---|
$elpisBoot |
框架 app/pages/boot.js |
页面启动器(创建 Vue 应用、装配 ElementPlus / Pinia / 路由) |
$elpisPages |
框架 app/pages/ |
框架页面目录 |
$elpisCommon |
框架 app/pages/common/ |
curl.js、utils.js |
$elpisCurl |
common/curl.js |
请求封装 |
$elpisUtils |
common/utils.js |
工具函数 |
$elpisWidgets |
框架 app/pages/widgets/ |
内置组件(header-container 等) |
$elpisStore |
框架 app/pages/store/ |
Pinia store(menu / project) |
$businessDashboardRouterConfig |
业务 app/pages/dashboard/router.js |
dashboard 自定义路由 |
$BusinessFromItemConfig |
业务 schema-form 注册文件 | 业务表单控件注册 |
$BusinessSearchBar |
业务 schema-search-bar 注册文件 | 业务搜索控件注册 |
vue |
框架内置 vue | 保证全局单实例 |
组件覆盖 / 扩展(业务侧自定义控件)
框架的 schema-form / schema-search-bar 预留了业务侧注册入口,在业务项目对应路径放置注册文件即可(路径固定,不可移动):
app/pages/widgets/schema-form/complex-view/form-item-config.js ← 覆盖表单控件
app/pages/widgets/schema-search-bar/complex-view/schema-item-config.js ← 覆盖搜索控件
// app/pages/widgets/schema-search-bar/complex-view/schema-item-config.js
import demoSearchItem from './demo-search-item/demo-search-item.vue'
export default {
demoSearchItem: { component: demoSearchItem }
}
注册后,schema 配置中即可使用 comType: 'demoSearchItem'。
dashboard 自定义路由
// app/pages/dashboard/router.js
module.exports = ({ routes, siderRouter }) => {
// 顶部路由(整页,无侧边栏)
routes.push({
path: '/view/dashboard/todo',
component: () => import('./todo/todo.vue')
})
// 侧边栏内子路由(path 必须是相对路径!)
siderRouter.push({
path: 'todo',
component: () => import('./todo/todo.vue')
})
}
注意:
siderRouter的path必须是相对路径,写成以/开头的绝对路径会被 vue-router 注册为根路径,导致页面套上侧边栏布局。
页面渲染机制
npm run dev(_ENV=local):3001只负责渲染 HTML 模板,JS/CSS 由9002dev-server 提供(内存产物 + HMR),Network 面板可见资源指向127.0.0.1:9002npm run build:prod后:资源路径为/dist/prod/...,由3001从磁盘app/public/dist/prod/提供静态文件
Schema 配置
schema 由 model/ 目录驱动,用于生成项目列表与 dashboard 页面:
model/
├─ business/ # 模型(业务域)
│ ├─ model.js # 模型配置:默认菜单(项目继承)
│ └─ project/
│ ├─ jd.js # 项目:京东
│ └─ taobao.js # 项目:淘宝
└─ course/
├─ model.js
└─ project/bilibili.js
继承规则:project 深度继承所属 model(lodash mergeWith,数组按 key 合并——项目可覆盖/新增/省略继承项)。
模型配置:
// model/business/model.js
module.exports = {
name: '电商系统',
menu: [
{
key: 'product',
name: '商品管理',
menuType: 'module', // module | group
moduleType: 'schema', // schema | iframe | sider | custom
schemaConfig: { // schema 型:表格/搜索/表单全部由 schema 驱动
api: '/api/proj/product',
schema: { type: 'object', properties: { /* 字段定义 */ } },
tableConfig: { headerButtons: [], rowButtons: [] },
componentConfig: { createForm: {}, editForm: {}, detailPanel: {} }
}
},
{
key: 'order',
name: '订单管理',
menuType: 'module',
moduleType: 'custom', // 自定义页面
customConfig: { path: '/todo' } // 跳转 /view/dashboard + path
}
]
}
项目配置:
// model/business/project/taobao.js
module.exports = {
name: '淘宝',
desc: '淘宝电商系统',
homePage: '/schema?proj_key=taobao&key=product', // 首页地址(相对 /view/dashboard)
menu: [ /* 覆盖或继承模型菜单 */ ]
}
moduleType 类型与跳转规则:
| moduleType | 顶部菜单跳转 | 侧边栏内跳转 | 必需配置 |
|---|---|---|---|
schema |
/view/dashboard/schema |
/view/dashboard/sider/schema |
schemaConfig |
iframe |
/view/dashboard/iframe |
/view/dashboard/sider/iframe |
iframeConfig.path |
sider |
/view/dashboard/sider |
— | siderConfig.menu(子菜单项为 iframe/schema/custom) |
custom |
/view/dashboard + customConfig.path |
/view/dashboard/sider + customConfig.path |
customConfig.path |
menuType: 'group'的菜单项为分组(需subMenu数组),本身不可点击跳转。
Schema 字段完整约束
一个 menuType: 'module' 菜单项的完整配置结构(各 *Option / *Config 均为「标准 Element Plus 组件属性透传 + 框架扩展字段」):
{
key: '', // 菜单唯一标识
name: '', // 菜单名称
menuType: 'module', // 枚举:group(分组,用 subMenu,可递归 menuItem)| module
moduleType: 'schema', // 枚举:schema | iframe | sider | custom
// menuType == 'group' 时
subMenu: [/* 递归 menuItem */],
// moduleType == 'sider' 时:侧边栏容器,子菜单可递归 menuItem,但不能嵌套 sider
siderConfig: {
menu: [/* 递归 menuItem */]
},
// moduleType == 'iframe' 时
iframeConfig: {
path: '' // iframe 加载的地址
},
// moduleType == 'custom' 时
customConfig: {
path: '' // 自定义页面路径(相对 /view/dashboard)
},
// moduleType == 'schema' 时
schemaConfig: {
api: '', // 数据源 API,遵循 RESTful 规范
schema: { // 板块数据结构
type: 'object',
properties: {
<字段key>: {
type: '', // 字段类型(标准 json-schema)
label: '', // 字段中文名
// 字段在 table 中的配置(透传 el-table-column 全部属性)
tableOption: {
toFixed: 0, // 保留小数位数
visible: true // false 或不配置时,该列不显示
},
// 字段在 search-bar 中的配置(透传对应 el 组件属性)
searchOption: {
comType: '', // 组件类型:input / select / dateRange / dynamicSelect ...
default: '', // 默认值
enumList: [], // comType == 'select' 时:下拉可选项
api: '' // comType == 'dynamicSelect' 时:远程选项接口
},
// 字段在动态表单/面板中的配置,前缀与 componentConfig 的键对应:
// componentConfig.createForm → createFormOption
// componentConfig.editForm → editFormOption
// componentConfig.detailPanel → detailPanelOption
createFormOption: {
comType: '', // 控件类型:input / select / input-number
visible: true, // 是否展示,默认 true
disabled: false, // 是否禁用,默认 false
default: '', // 默认值
enumList: [] // comType == 'select' 时生效
}
// editFormOption / detailPanelOption 结构相同
}
},
required: [] // 必填字段 key 列表
},
// 表格相关配置
tableConfig: {
headerButtons: [{ // 表头按钮
label: '', // 按钮中文名
eventKey: '', // 按钮事件名
eventOption: { // 事件具体配置
comName: '' // eventKey == 'showComponent' 时:动态组件名
},
...elButtonConfig // 透传 el-button 标准属性
}],
rowButtons: [{ // 行按钮
label: '',
eventKey: '',
eventOption: {
comName: '',
// eventKey == 'remove' 时:事件参数
params: {
// paramKey 为参数键;值为 schema 字段名时,自动取当前行该字段的值
<paramKey>: '<rowValueKey>'
}
},
...elButtonConfig
}]
},
// 动态组件相关配置
componentConfig: {
createForm: { // 新建表单
title: '', // 表单标题
saveBtnText: '' // 保存按钮文案
},
editForm: { // 编辑表单
title: '',
mainKey: '', // 唯一标识待修改数据对象的字段(一般为主键)
saveBtnText: ''
},
detailPanel: { // 详情面板
title: '',
mainKey: ''
}
}
}
}
通用扩展字段速查:
| 字段 | 出现位置 | 说明 |
|---|---|---|
visible |
tableOption / form 类 Option | 是否展示,默认 true |
disabled |
form 类 Option | 是否禁用,默认 false |
default |
searchOption / form 类 Option | 默认值 |
comType |
searchOption / form 类 Option | 组件类型 |
enumList |
comType: 'select' 时 |
枚举选项,格式 { label, value } |
api |
searchOption.comType: 'dynamicSelect' 时 |
远程枚举数据接口 |
toFixed |
tableOption | 数字保留小数位 |
mainKey |
editForm / detailPanel | 唯一标识操作对象的字段名 |
环境与配置
_ENV 环境变量决定运行环境(local / beta / prod),影响:
- 配置加载:
config/config.default.js被config/config.{env}.js覆盖,最终挂到app.config - 前端构建:
local启动 dev-server,prod输出压缩产物到app/public/dist/prod/ - 页面注入:模板将
env写入window.env,前端可按环境执行逻辑
构建与部署
npm run build:prod # 生产构建 → app/public/dist/prod/{js,css} + entry.*.tpl
生产部署只需业务项目(含 node_modules)+ Node 进程:
- 执行
npm run build:prod npm run prod(_ENV=prod)启动服务- 静态资源由
3001的koa-static(app/public)提供,可按需在前置 Nginx 缓存/dist/prod/
发布到 npm
本包以 源码形态 发布(使用者安装后由框架内的 Webpack 完成构建),发布前请确认:
1. 依赖分类(重要)
构建工具链必须让使用者可安装。将以下包从 devDependencies 移到 dependencies(否则使用者 npm install 后运行构建会报 Can't resolve 'babel-loader' 等错误):
webpack、webpack-merge、webpack-dev-middleware、webpack-hot-middleware、
babel-loader、@babel/core、@babel/preset-env、@babel/plugin-transform-runtime、
vue-loader、vue-style-loader、style-loader、css-loader、less、less-loader、
url-loader、file-loader、happypack、mini-css-extract-plugin、
css-minimizer-webpack-plugin、terser-webpack-plugin、clean-webpack-plugin、
html-webpack-plugin、html-webpack-inject-attributes-plugin、
directory-named-webpack-plugin、md5
2. 配置 files 白名单(package.json)
{
"files": ["index.js", "eplis-core/", "app/", "config/", "model/", "readme.md"]
}
避免把 .git/、test/、docs/、构建产物 app/public/dist/ 发布上去。
3. 发布流程
# npm whoami # 确认登录(scoped 包首次发布需 --access public)
npm version patch # 或 minor / major
npm publish --access public
4. 使用方安装
npm install @hanvzur/elpis
常见问题
Q: 启动报 EADDRINUSE 3001 / 9002?
有残留进程占用端口。Get-NetTCPConnection -LocalPort 3001,9002 找到 PID 后 Stop-Process -Id <pid>(Windows)或 lsof -i:3001(macOS/Linux)。
Q: 业务页面报 Can't resolve 'vue'?
业务页面可用的运行时依赖(vue、axios、lodash 等)已通过框架内 alias 统一解析,业务项目无需重复安装;如需新增库,优先在 webpack.base.js 的 alias 中指向框架内版本,避免打包出多份实例。
Q: 新增页面后报 template not found: dist/entry.xxx.tpl?
入口扫描在 dev 进程启动时执行一次,新增 entry.*.js 后必须重启 dev。同时确认文件名以 entry. 开头、位于 app/pages/<页面名>/ 下。
Q: 接口返回 signature not correct!! (code 445)?
请求缺少 s_t / s_sign 头或签名不匹配。请使用框架封装的 $curl 发请求,它会自动生成签名;直接用 Postman/curl 测试时需手动携带这两个头。
Q: 接口返回 proj_key not found (code 446)?
/api/proj/ 前缀接口要求请求头携带 proj_key,$curl 会自动注入 window.projKey(由页面模板写入)。直接调试接口时请在 header 中手动携带。
Q: 点击菜单跳到 /view/dashboardundefined?
菜单项缺少 moduleType,或 moduleType: 'custom' 时缺少 customConfig.path。检查 schema 配置。
Q: 修改 webpack.base.js / model/ / 入口文件后页面没变化?
这三类都是启动时加载(Webpack 配置、后端 schema 数据源、入口扫描),修改后需重启 dev;普通 .vue / .js 业务代码由 HMR 热更新,无需重启。
Related Packages
webpack plugin to find duplicated dependencies from your bundle
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.2 | ^3.3.4 | ISC | |
| 1.0.1 | ^3.3.4 | ISC | |
| 1.0.0 | ^3.3.4 | ISC |