Name Last Update
node_modules Loading commit data...
public Loading commit data...
src Loading commit data...
types Loading commit data...
.env Loading commit data...
.env.dev Loading commit data...
.env.prod Loading commit data...
.gitignore Loading commit data...
README.md Loading commit data...
index.html Loading commit data...
package.json Loading commit data...
pnpm-lock.yaml Loading commit data...
pnpm-workspace.yaml Loading commit data...
tsconfig.json Loading commit data...
uno.config.ts Loading commit data...
vite.config.ts Loading commit data...

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/ 只做展示与接口调用