# 前端 > AI摘要:衍水集团营销人员工资管理系统的前端。Vue 3 + TypeScript + Vite + Element Plus + Pinia。关键文件 `src/api/request.ts`(统一请求封装)、`src/router/index.ts`(路由守卫)、`src/store/user.ts`(会话状态)、`src/views/login/index.vue`(登录页)。当前完成登录链路,业务模块页面待接入。 ## 职责 - 登录页与图形验证码交互 - 会话状态管理与路由守卫 - 后续:基础数据、业务数据、绩效核算、报表等业务页面 ## 技术栈与版本约束 | 组件 | 版本 | 说明 | | --- | --- | --- | | 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 # 认证接口封装与类型 ├── assets/styles/ # 全局样式 ├── components/ # 通用组件(待补充) ├── composables/ # 组合式函数(待补充) ├── router/index.ts # 路由表与全局守卫 ├── store/ │ ├── index.ts # Pinia 实例 │ └── user.ts # 会话状态:token、用户信息、登录/登出 ├── utils/storage.ts # localStorage 读写 └── views/ ├── login/ # 登录页 └── dashboard/ # 首页(登录链路验证页) ``` ## 启动 ```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` | 正文 | **字体**:品牌名用**宋体**(衬线,拟正式、拟账册),正文用系统无衬线,数字与验证码用等宽。 **界面不引入网络字体**——内网环境字体加载不可靠,靠字重、字距与留白做层次更稳妥。新页面请沿用这套字号节奏。 **签名元素**:品牌面板的水纹层理线,越往下越密(如潜向深处),载入时用 `stroke-dashoffset` 逐条画出——**一个统一的入场动作,而不是散落的特效**。 **构图**:品牌内容(名称 / 金线 / 系统名 / 主标语 / 环节行)**垂直居中聚成一块**,配一层柔和径向光晕作落点。 早期版本用 `space-between` 把品牌与标语顶到两端,中间空出大半屏,两处都成了孤岛——超宽屏会把这种疏离放大得更明显。**内容块要限宽**(`max-width`),否则宽屏下文字会被拉散。 ### 写代码时的三条约定 - 覆盖 Element Plus 主题请改 **CSS 变量**(`--el-button-bg-color` 等),不要直接写 `background-color` —— 后者与 `.el-button--primary` 同权重,会受样式注入顺序影响而不稳定 - 中文可在任意字间断行,**关键文案的断行位置要显式控制**(如主标语拆成两个 ``),交给浏览器自动换行会出现单字独占一行的难看结果 - 新页面需响应式到移动端、保留键盘焦点可见、并尊重 `prefers-reduced-motion` ## 验证码图片 后端自绘渲染(`backend/app/core/captcha_image.py`),**不再使用 `captcha` 库的默认输出**——那是随机彩色字符 + 重噪点,与界面视觉体系冲突。自绘后字符为墨蓝、底为素纸、干扰线与噪点取自同一套配色。 识别难度由**逐字随机旋转(±14°)+ 垂直抖动(±2px)+ 正弦干扰曲线 + 双层噪点**共同保证,与原实现相当;笔画加了 1px 描边——本系统用户是营销人员,屏幕与年龄跨度大,验证码过难会直接变成支持负担。 ## 端到端测试 `e2e/login_flow.py` 用 Playwright 驱动真实浏览器,覆盖 8 项:路由守卫、验证码渲染、失败后自动刷新、登录跳转、用户信息展示、刷新恢复会话、登出、登出后令牌失效。 ```bash # 前置:MySQL/Redis 容器、后端、前端 dev server 均在运行 python e2e/login_flow.py ``` 需要 Python `playwright` 包与 chromium(`python -m playwright install chromium`)。 ## 常见问题 **登录后刷新页面被踢回登录页** —— 检查 `/api/v1/auth/me` 是否可达;路由守卫靠它恢复会话。 **验证码图片不显示** —— 多为代理未生效。访问 `http://localhost:5173/api/health` 应返回统一信封格式 JSON。 **`pnpm install` 报 peer 依赖错误** —— 确认 `@vue/devtools-api` 在 `dependencies` 中(pinia 4 的必需 peer)。