技术栈:Vue 3 + TypeScript + Vite + Pinia + Vue Router + Axios + Vitest + Playwright
这是 EMS 的前端管理台工程,目录位于 frontend-web。当前工程已经具备独立开发、单元测试和前端自带 mock 的 Playwright 冒烟回归能力。
- Node.js
>= 18.18.0 - pnpm
10.32.1
当前项目已在 package.json 中声明:
{
"packageManager": "pnpm@10.32.1",
"engines": {
"node": ">=18.18.0"
}
}在 frontend-web 目录执行:
pnpm install
pnpm dev默认地址:http://127.0.0.1:4173
Vite 开发服务器配置:
- 监听地址:
0.0.0.0 - 端口:
4173 - 前端请求前缀:
/api - 默认代理目标:
http://localhost:8080
如果本地后端不是 8080,可以在启动前覆盖代理目标:
VITE_PROXY_TARGET=http://127.0.0.1:18080 pnpm dev当前代理规则在 vite.config.ts 中定义:
/api/** -> ${VITE_PROXY_TARGET}/**- 会移除
/api前缀后再转发到后端
pnpm dev
pnpm build
pnpm preview
pnpm typecheck
pnpm test
pnpm test:unit
pnpm test:unit:coverage
pnpm test:e2e说明:
pnpm test当前等价于pnpm test:unitpnpm build会先执行vue-tsc --noEmit再执行 Vite 生产构建pnpm test:e2e运行 Playwright 冒烟测试
当前目录已经从纯技术分层,演进为“基础设施 + 模块化增强”的结构。
src/
api/
raw/ # 直接贴近后端接口契约
adapters/ # 面向页面的数据归一与字段适配
components/ # 通用组件与业务弹窗组件
composables/ # 通用组合逻辑
directives/ # 全局指令,如 v-menu-permission
layouts/ # 主布局
modules/ # 按业务模块收口的类型、composable、子组件
devices/
electric-meters/
components/
composables/
types.ts
system/
users/
composables/
types.ts
roles/
types.ts
menus/
types.ts
organizations/
types.ts
spaces/
types.ts
router/
modules/ # 按业务拆分的路由声明
guard.ts # 可测试的路由守卫逻辑
index.ts # 路由汇总与装配
stores/
auth.ts
permission.ts
styles/
types/ # 仍保留跨模块共享类型
utils/
views/ # 页面级视图,当前主要负责装配
api/raw:请求后端接口,不做页面语义包装api/adapters:负责 envelope 解包、分页归一、字段格式化、VO 转页面模型views:页面装配层,尽量不堆业务逻辑modules/**/composables:页面级业务逻辑和状态管理modules/**/types.ts:模块内类型定义,避免把所有类型堆到全局src/typescomponents:复用 UI 和表单/详情/确认弹窗
当前路由声明已拆到:
src/router/modules/accounts.tssrc/router/modules/devices.tssrc/router/modules/plans.tssrc/router/modules/trades.tssrc/router/modules/system.tssrc/router/modules/reports.tssrc/router/modules/operations.ts
src/router/index.ts 只负责:
- 汇总 routes
- 创建 router
- 挂载
beforeEach/afterEach
路由守卫逻辑位于:
src/router/guard.ts
其中已经抽出可测试函数:
resolveDocumentTitlenormalizePathbuildRegisteredProtectedPathsresolveFirstAccessiblePathhandleMenuLoadFailurecreateRouteGuardupdateDocumentTitle
当前菜单接口:
GET /v1/users/current/menus?source=1
前端在 src/stores/permission.ts 中完成:
- 平铺菜单转树
- 可访问页面路径收集
- 按钮权限 key 收集
- 首个可访问页面推导
当前约定:
menuType === 1:页面菜单menuType === 2:按钮权限
前端权限控制分两层:
- 路由访问控制
- 根据
allowedPaths判断当前路由是否允许访问 - 菜单加载失败或当前路径无权限时,自动回退或跳登录
- 按钮显隐控制
- 简单场景使用
v-menu-permission - 复杂逻辑可使用
stores/permission.ts暴露的菜单 key 数据
工程统一处理如下结构:
{ "success": true, "code": 100001, "message": "", "data": ... }关键逻辑位于:
src/api/raw/types.ts
当前约定:
- 成功码:
100001 - 未登录:
-103001 - 权限不足:
-103002
分页会统一归一到:
{ list: T[]; total: number; pageNum?: number; pageSize?: number }当前兼容的后端字段包括:
listrecordsitemstotaltotalSizetotalCount
- 鉴权头:
Authorization: <token> - token 存储工具:
src/utils/token.ts 401会清理 token 并跳转/login- envelope 的未登录码
-103001也会触发跳登录
- 账户管理
/accounts/info/accounts/cancel-records
- 设备管理
/devices/electric-meters/devices/gateways/devices/categories
- 方案管理
/plans/electric/plans/warn
- 交易管理
/trade/recharge/trade/order-flows/trade/consumption-records
- 系统管理
/system/users/system/roles/system/menus/system/spaces/system/organizations
当前仍为占位页:
/reports/electric-bill/reports/daily-electricity/operations
这轮前端收口已经完成两块高复杂度页面的逻辑拆分。
页面:
src/views/system/UserManagementView.vue
已抽离逻辑:
src/modules/system/users/composables/useUserNotice.tssrc/modules/system/users/composables/useUserQuery.tssrc/modules/system/users/composables/useUserCrud.tssrc/modules/system/users/composables/userShared.ts
页面:
src/views/devices/DeviceElectricMeterView.vue
已抽离逻辑:
src/modules/devices/electric-meters/composables/electricMeterShared.tssrc/modules/devices/electric-meters/composables/useElectricMeterNotice.tssrc/modules/devices/electric-meters/composables/useElectricMeterQuery.tssrc/modules/devices/electric-meters/composables/useElectricMeterActions.ts
已拆子组件:
src/modules/devices/electric-meters/components/DeviceElectricMeterSearchPanel.vuesrc/modules/devices/electric-meters/components/DeviceElectricMeterTableSection.vue
当前单元测试使用:
VitestVue Test Utilsjsdom
测试启动文件:
tests/unit/setup.ts
覆盖率配置位于:
vitest.config.ts
当前 coverage 阈值:
- lines
95 - statements
95 - branches
90 - functions
90
当前重点覆盖对象:
src/api/raw/types.tssrc/stores/auth.tssrc/stores/permission.tssrc/api/adapters/user.ts- 用户管理 composables
- 电表管理 composables
src/router/guard.ts
当前冒烟测试位于:
tests/energy_permission_regression.spec.cjstests/device_electric_meter_smoke.spec.cjstests/system_user_smoke.spec.cjstests/system_organization_smoke.spec.cjs
这些测试目前采用前端自带请求拦截 mock,目标是验证页面主流程,而不是跑真实后端集成环境。
运行方式:
- 先启动前端 dev server
- 再执行 Playwright
示例:
pnpm dev另开终端:
TEST_BASE_URL=http://127.0.0.1:4173 pnpm test:e2e如果只跑某一条 smoke:
TEST_BASE_URL=http://127.0.0.1:4173 pnpm exec playwright test tests/system_user_smoke.spec.cjs- 新页面优先沿用现有分层:
raw -> adapter -> composable -> view - 不要把复杂业务逻辑重新堆回
.vue页面文件 - 新增高风险页面时,先补单元测试,再补 Playwright 冒烟
- 如果模块继续变大,优先把类型、composable、子组件放进
src/modules/<domain>收口
因为开发环境通过 Vite proxy 转发到后端,浏览器只访问前端域名,不直接跨域访问后端。
当前 smoke 的目标是稳定验证页面主流程和权限/弹窗/提交流程,避免把数据库、MQ、后端启动一起引进前端 CI。
因为后端接口契约和页面展示模型不是同一个概念。raw 负责贴近后端,adapters 负责把接口数据清洗成页面真正要用的结构,避免页面直接消费原始 VO。