@hanvzur/elpis

基于 Koa + Vue3 的企业级全栈开发框架
588
Install
npm install @hanvzur/elpis
Latest Version:1.0.2
Vue:3
License:ISC
Last Updated:Sep 13, 2026
Links: Repository  ·  npm
Maintainer: hanvzur

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. 开头),框架自动完成:

  1. 注册为 Webpack 多页面入口
  2. 生成 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 由 9002 dev-server 提供(内存产物 + HMR),Network 面板可见资源指向 127.0.0.1:9002
  • npm 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 进程:

  1. 执行 npm run build:prod
  2. npm run prod(_ENV=prod)启动服务
  3. 静态资源由 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

lumfall

Lumfall is a Koa-based full-stack framework with directory-driven auto-loading,...

1,140 0
koa-webpack-server

koa2 webpack hmr development server

1,184 14
create-lumfall

lumfall 应用脚手架:一条命令从 GitHub 拉取并生成基础业务项目 / B 端管理台 / 技术...

560
duplicate-dependencies-webpack-plugin

webpack plugin to find duplicated dependencies from your bundle

332,104 6
aiot-vue-cli

阿里云iot 小程序构建工具

19,608

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