P

README.md

parking-cloud-ui — ZTEITS 智能停车云平台管理前端

停车平台 + 政务平台的同一个管理前端(一套框架、两套业务域)。前后端分离部署:后端只出一个 Jar,前端独立构建发布。


1. 技术栈(版本已锁定,改动前先看第 6 节)

依赖 版本 说明
Vue 3.3.8
Vite 4.5.0
Element Plus 2.4.2 组件 + @element-plus/icons-vue 2.3.1
TypeScript 5.2.2 vue-tsc 1.8.27(pnpm type-check)
Pinia 2.1.7
Vue Router 4.2.5
VueUse 10.6.1
vue-i18n 9.6.5 zh-CN 起步
UnoCSS 0.57.4 + @iconify-json/ep 1.1.15
wangEditor 5.1.23 富文本(@wangeditor/editor-for-vue 5.1.10)
其他 axios 1.6.2 / dayjs 1.11.10 / echarts 5.4.3 / nprogress 0.2.0 / sortablejs 1.15.0

包管理器 pnpm。注意 pnpm 11 已不再读 package.json 的 pnpm 字段,构建脚本白名单写在 pnpm-workspace.yaml 的 allowBuilds 里(esbuild / vue-demi 必须为 true,否则装完跑不起来)。


2. 开发

pnpm install
pnpm dev            # http://localhost:5173 ,/admin-api、/app-api 代理到 127.0.0.1:48080

后端不在本机时的代理目标在 .env.dev 里改:

VITE_PROXY_TARGET=http://127.0.0.1:48080

其他命令:

pnpm build          # 生产构建(mode=prod,terser + gzip)
pnpm build:dev      # 生产构建但用 dev 环境变量
pnpm type-check     # vue-tsc --noEmit,CI 必过
pnpm preview        # 本地预览 dist

3. 环境变量

只从 import.meta.env 读,任何后端地址/密钥都不写进代码(见 src/config/index.ts)。

变量 用途
VITE_APP_TITLE 浏览器标题、登录页标题
VITE_API_BASE_URL 后端基础地址;留空表示同源(开发走 Vite 代理,生产走 Nginx 反代)
VITE_BASE_PATH 部署子路径,如 /parking/
VITE_PROXY_TARGET 仅开发期的代理目标

生产用 Nginx 反代 /admin-api、/app-api 到后端 Jar,前端只发静态文件(dist/,已自带 .gz,Nginx 配 gzip_static on;)。


4. 目录结构

src/
├── api/{module}/{entity}/index.ts   # 一个实体一个文件,与后端模块同构
├── views/{module}/{entity}/         # 页面(component 字段按相对 src/views 的路径给后端菜单表)
├── views/gov/                       # ← 政务业务域(P6 迁入,原 PTMS-MANAGE 109 页)
├── layout/                          # Layout / Sidebar / Navbar / TagsView / Breadcrumb / AppMain
├── router/
│   ├── routes.ts                    # 静态路由:登录 / 404 / redirect / 外壳(layoutRoute)
│   └── index.ts
├── store/modules/                   # app / user / permission / dict / tagsView / locale
├── utils/request.ts                 # axios 封装:Bearer + CommonResult 解包 + 401/403
├── components/ directives/ styles/ locales/ types/
└── permission.ts                    # 路由守卫

菜单是后端驱动的:get-permission-info 下发菜单树 → store/modules/permission.ts 把有 component 的叶子用 import.meta.glob('/src/views/**/*.vue') 转成动态路由;侧边栏直接渲染后端菜单树(不是路由表),所以新增页面只要后端菜单表加一条 + 前端有对应组件文件,前端零改动。

后端菜单的 component 字段填相对 src/views 的路径(不带 .vue),例如 system/user/index → src/views/system/user/index.vue。

/ 外壳路由是静态注册的(router/routes.ts 的 layoutRoute,redirect 到 HOME_PATH),动态子路由用 router.addRoute('Root', child) 追加。这样即使某账号一个菜单都没有也不会白屏。


5. 接口契约

项 约定
前缀 /admin-api/**(管理后台)、/app-api/**(移动端/大屏),由后端包名自动加
认证 Authorization: Bearer <token>,token 存 localStorage.ACCESS_TOKEN
返回 {code, msg, data},code === 0 为成功,request.ts 直接 resolve 出 data
未登录 HTTP 200 + body code=401 → 清 token、弹一次「重新登录」、跳登录页并带 redirect
无权限 HTTP 200 + body code=403 → toast「没有访问权限」,不登出

⚠️ 401/403 判定按响应体 code,不是 HTTP 状态码——这是与参考工程一致的约定,写新请求时不要改回按 HTTP 状态码判断。


6. 系统管理页面(P1 已全部接通)

6 个系统页已全部接真实接口,取消所有骨架/开关写法(P1_ENABLED、DICT_API_ENABLED 已删除)。

页面 路径 列表端点 权限点
用户管理 /system/user GET /admin-api/system/user/page system:user:query
角色管理 /system/role GET /admin-api/system/role/page system:role:query
菜单管理 /system/menu GET /admin-api/system/menu/list(树,无分页) system:menu:query
组织管理 /system/org GET /admin-api/system/org/list(树,无分页) system:org:query
字典管理 /system/dict GET /admin-api/system/dict-type/page + dict-data/page system:dict:query
日志管理 /system/log GET /admin-api/system/operate-log/page system:operate-log:query

CRUD 的 URL 约定统一为 ${BASE}/create POST、/update PUT、/delete DELETE、/delete-list DELETE(BASE 见各 src/api/system/*/index.ts 顶部常量)。

src/views/system/user/index.vue + src/api/system/user/index.ts 是其余页面的参照样板,新增页面直接照抄其结构。

写新页面时最容易犯的三个错

  1. 列表页必须 onMounted(loadList)——漏了它页面能渲染但一个请求都不发,表现为「永远共 0 条、点刷新才出数据」(日志页当时就是这个症状,库里其实已有 30 条)
  2. 表单里收的字段,后端 RespVO 必须下发——否则编辑时拿不到原值、只能提交空串,而 updateById 覆盖整列,等于静默清空(组织 remark 的教训,详见 ../parking-cloud/AGENTS.md §1 第 11 条)
  3. 日期范围用 value-format="YYYY-MM-DD"——操作日志的 createTime 是 List<LocalDate>,传 ISO 串会 HTTP 400;数组查询参数靠 paramsSerializer: { indexes: null } 序列化成重复 key(createTime=X&createTime=Y)

7. 已知现象(不是 Bug)

  • 首次登录跳转时控制台有一条 [Vue Router warn]: No match found for location with path "/dashboard"。这是"菜单驱动动态路由"的固有现象:跳转发生在动态路由注册之前,守卫会立刻注册路由并重新导航,功能不受影响(vue-router 仅在 dev 模式告警)。
  • 504 (Outdated Optimize Dep):Vite 首次预构建依赖时出现,刷新一次即消失;改动 package.json 依赖后 rm -rf node_modules/.vite 重启即可。

8. 红线

  1. 不改锁定版本(第 1 节表格);升级 Element Plus 前先确认 el-tag type="primary" 这类 2.5+ 才有的取值(本项目用 type="" + effect="dark" 表示选中态)
  2. 不在代码里写死后端地址、AK、密钥,一律走环境变量
  3. 不绕过 src/utils/request.ts 直接用 axios,否则会丢掉 Bearer 注入与 CommonResult 解包
  4. 路由不写死在 routes.ts,业务页面一律走后端菜单下发
  5. Portal 层不写业务逻辑;views/ 只做展示与接口调用