PayrollManagementSystem/frontend/README.md
liuwenxin 8355e0cc7d feat(system): 完成前端系统管理(布局框架、三个管理页面、e2e)
- BasicLayout:侧边栏/顶栏/面包屑/移动端抽屉;菜单从路由表派生;
  路由守卫新增 requiresAdmin 层(菜单隐藏 + 直连 URL 弹回)
- 用户/角色/日志三个页面:分页请求序号防竞态、确认动作流抽 useConfirmAction、
  时间范围配 default-time、日志详情按需懒加载并缓存
- 空 catch 统一改 handledError 显式声明;词表/选项常量集中(与后端对齐),
  「停用」文案全站统一为「禁用」
- 品牌色提升为全局 token,Element Plus 主色指向赭金
- e2e:公共工具抽 common.py;系统管理 13 项(含详情懒加载脱敏断言),
  管理员密码改 E2E_ADMIN_PASSWORD 环境变量;login_flow 8 项回归通过

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-10 16:28:53 +08:00

139 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 前端
> AI摘要:衍水集团营销人员工资管理系统的前端。Vue 3 + TypeScript + Vite + Element Plus + Pinia。关键文件 `src/api/request.ts`(统一请求封装)、`src/components/layout/BasicLayout.vue`(应用布局与导航)、`src/router/index.ts`(路由守卫)、`src/store/user.ts`(会话状态)。当前完成登录链路与系统管理页面(用户/角色/日志查询),业务模块页面待接入。
## 职责
- 登录页与图形验证码交互
- 会话状态管理与路由守卫
- 后续:基础数据、业务数据、绩效核算、报表等业务页面
## 技术栈与版本约束
| 组件 | 版本 | 说明 |
| --- | --- | --- |
| Vue | 3.5.x | |
| Vue Router | **5.x** | 新大版本;对 vite/pinia 的 peer 均为 optional |
| Pinia | **4.x** | **`@vue/devtools-api` 是其非可选 peer**,必须显式安装,否则装不上或运行时报警 |
| Element Plus | 2.14.x | 全量引入,产物约 1MB(gzip 347KB);后续可改按需引入 |
| Vite | 8.x | |
| TypeScript | **~5.9.3** | **不可用 7.x** —— TS 7 是 Go 重写的原生编译器,而 vue-tsc 的 peer 只写 `>=5.0.0` 拦不住,兼容性未经验证 |
| 包管理 | pnpm | |
## 目录结构
```text
src/
├── api/
│ ├── request.ts # axios 实例、拦截器、统一信封解包
│ ├── auth.ts # 认证接口封装与类型
│ ├── users.ts / roles.ts / logs.ts # 系统管理接口封装
│ └── types.ts # 分页等公共类型
├── assets/styles/ # 全局样式与品牌 token(:root 的 --ys-*)
├── components/
│ ├── layout/ # BasicLayout(侧边栏/顶栏/面包屑)、AppMenu
│ └── table/ # StatusTag(0/1 状态标签)、AppPagination
├── composables/
│ ├── useAppMenu.ts # 菜单从路由表派生(meta.title/group/order/requiresAdmin)
│ ├── useConfirmAction.ts # 确认框→执行→提示→刷新 动作流(启停/删除共用)
│ └── usePagedList.ts # 分页列表状态(请求序号防竞态、删除后页码回退)
├── router/index.ts # 路由表与全局守卫(四层,含 requiresAdmin)
├── store/
│ ├── index.ts # Pinia 实例
│ └── user.ts # 会话状态:token、用户信息、登录/登出
├── utils/ # storage/format/validate/constants/error(handledError)
└── views/
├── login/ # 登录页
├── dashboard/ # 首页(登录链路验证页)
└── system/ # 系统管理:users(用户)/ roles(角色)/ logs(日志查询)
```
## 启动
```bash
# 前置:后端已在 127.0.0.1:8000 运行
pnpm install
pnpm dev # http://localhost:5173
pnpm type-check # vue-tsc 类型检查
pnpm build # 类型检查 + 生产构建
```
## 几个关键约定
**开发期用 Vite 代理,不开 CORS**
`/api` 被代理到 `http://127.0.0.1:8000`。用 `127.0.0.1` 而非 `localhost` 是因为 Node 17+ 对 `localhost` 优先解析 IPv6 的 `::1`,而 uvicorn 默认只监听 IPv4。
反向地,dev server 自身显式绑定 `host: '127.0.0.1'` —— 不指定时 Vite 只监听 `[::1]`,会出现「`localhost:5173` 能开、`127.0.0.1:5173` 连不上」的困惑。此配置让两种写法都可用。
代理成立后 `/api/docs`(Swagger)也能从 5173 端口打开,联调期只需一个域名。
**401 只表示「登录态失效」**
登录接口自身的失败返回 **400/403**,绝不会是 401。因此前端拦截器可以放心地把 401 一律当作「清 token + 跳登录页」处理,不会出现「用户输错一次密码就被弹回登录页」的循环。
并发 401 用模块级标志去重,避免同时弹出多条提示并反复跳转。
**登录失败后必须刷新验证码**
后端用 Redis `GETDEL` 原子消费验证码,**任何一次提交后该验证码都已失效**。登录页在 `catch` 分支里调用了 `refreshCaptcha()` —— 漏掉这一步,用户第二次提交必然报「验证码错误」,且极难自查。
**令牌存 localStorage**
实现最简,代价是 XSS 可窃取。缓解手段是 JWT 有效期上限 2 小时,以及后端支持 `token_version` 提版即时失效。若后续要求更高,可改为 httpOnly Cookie,届时只需改 `src/utils/storage.ts` 与后端签发方式。
## 视觉设计
**设计立意**:公司名「衍水」是水,而中文「流水账」字面就是**流水之账**——系统的全部工作正是记录回款怎么流、绩效怎么算。视觉方向由此而生:深水面板 + 流动的层理线,即公司名的意象,也是业务的隐喻。
**配色**(金色分两档是有意的:亮金只用于深色底、深金只用于浅色底,两处对比度都达标)
| 名 | 值 | 用途 |
| --- | --- | --- |
| 深渊 | `#061A22` | 品牌面板底色 |
| 沉碧 | `#0F3646` | 面板渐变、水纹线 |
| 青金 | `#C9A227` | **深色底上**的细线与边框 |
| 赭金 | `#8F6520` | **浅色底上**的主按钮(配白字约 6.2:1) |
| 素纸 | `#FAFAF7` | 表单侧底色 |
| 墨 | `#16262D` | 正文 |
**字体**:品牌名用**宋体**(衬线,拟正式、拟账册),正文用系统无衬线,数字与验证码用等宽。
**界面不引入网络字体**——内网环境字体加载不可靠,靠字重、字距与留白做层次更稳妥。新页面请沿用这套字号节奏。
**落地方式**:品牌色已提升为全局 CSS 变量(`src/assets/styles/index.css` 的 `:root`,`--ys-*` 前缀),登录页与布局、系统管理页面共用;全局还把 `--el-color-primary` 指向赭金,Element Plus 组件默认走品牌色。登录页 scoped 里的同名局部变量只是全局 token 的别名。
**签名元素**:品牌面板的水纹层理线,越往下越密(如潜向深处),载入时用 `stroke-dashoffset` 逐条画出——**一个统一的入场动作,而不是散落的特效**。
**构图**:品牌内容(名称 / 金线 / 系统名 / 主标语 / 环节行)**垂直居中聚成一块**,配一层柔和径向光晕作落点。
早期版本用 `space-between` 把品牌与标语顶到两端,中间空出大半屏,两处都成了孤岛——超宽屏会把这种疏离放大得更明显。**内容块要限宽**(`max-width`),否则宽屏下文字会被拉散。
### 写代码时的三条约定
- 覆盖 Element Plus 主题请改 **CSS 变量**(`--el-button-bg-color` 等),不要直接写 `background-color` —— 后者与 `.el-button--primary` 同权重,会受样式注入顺序影响而不稳定
- 中文可在任意字间断行,**关键文案的断行位置要显式控制**(如主标语拆成两个 `<span>`),交给浏览器自动换行会出现单字独占一行的难看结果
- 新页面需响应式到移动端、保留键盘焦点可见、并尊重 `prefers-reduced-motion`
## 验证码图片
后端自绘渲染(`backend/app/core/captcha_image.py`),**不再使用 `captcha` 库的默认输出**——那是随机彩色字符 + 重噪点,与界面视觉体系冲突。自绘后字符为墨蓝、底为素纸、干扰线与噪点取自同一套配色。
识别难度由**逐字随机旋转(±14°)+ 垂直抖动(±2px)+ 正弦干扰曲线 + 双层噪点**共同保证,与原实现相当;笔画加了 1px 描边——本系统用户是营销人员,屏幕与年龄跨度大,验证码过难会直接变成支持负担。
## 端到端测试
`e2e/` 下两个脚本用 Playwright 驱动真实浏览器,公共工具抽在 `e2e/common.py`(登录动作、从 Redis 取验证码答案、提示等待、失败断言与控制台错误收集):
- `login_flow.py`(8 项):路由守卫、验证码渲染、失败后自动刷新、登录跳转、用户信息展示、刷新恢复会话、登出、登出后令牌失效
- `system_management.py`(13 项):菜单导航、用户增删改查(含重名负例)、重置密码、启用/禁用、角色保护与 CRUD、操作日志留痕与详情懒加载脱敏、登录日志、普通用户菜单隐藏与路由守卫
```bash
# 前置:MySQL/Redis 容器、后端、前端 dev server 均在运行
python e2e/login_flow.py
# 管理员密码经环境变量传入(不写死在源码里)
E2E_ADMIN_PASSWORD=<开发库admin密码> python e2e/system_management.py
```
需要 Python `playwright` 包与 chromium(`python -m playwright install chromium`)。
脚本自建的测试数据(`e2e_` 前缀)在正常流程内清理;中途失败会残留在开发库。
## 常见问题
**登录后刷新页面被踢回登录页** —— 检查 `/api/v1/auth/me` 是否可达;路由守卫靠它恢复会话。
**验证码图片不显示** —— 多为代理未生效。访问 `http://localhost:5173/api/health` 应返回统一信封格式 JSON。
**`pnpm install` 报 peer 依赖错误** —— 确认 `@vue/devtools-api` 在 `dependencies` 中(pinia 4 的必需 peer)。