# 前端 ## 产品、人员及首页数据体验(2026-11-02) 产品提取入口移到操作栏,来源空值改为明确说明;产品和人员长标签、日期控件及手机布局已修复。人员经理参照只列在职且绑定启用经理计提模板的人员,旧参考关系保留;实际绩效仍按业务分管。岗位编辑说明内置模板与参数维护的区别,提供参数入口。首页以趋势金额是否为 `null` 区分尚无台账/回款与真实零值,并说明正式导入和核算步骤。 本轮 6 项真实浏览器检查覆盖产品提取预览、经理候选、模板入口、首页,以及产品/人员表单五种宽度,错误 0;类型和构建通过。演示业务 `YW20261102-0002` 的样件B留待用户提取;完整操作见 [产品提取与人员岗位操作说明](../docs/产品提取与人员岗位操作说明_2026-11-02.md)。 ## 当前权限入口(2026-11-02) 新增角色在同一抽屉填写资料、选择权限并一次保存,无需填写修改原因;后台自动留痕。已有角色继续通过“配置权限”调整并填写原因。权限抽屉支持点击灰色遮罩关闭;未保存时先确认放弃,保存中阻止关闭。全局确认框保留 420px 宽度上限,窄屏留出两侧间距;最新 8 项真实浏览器检查、类型及构建通过,含 2560/1440/768/375/320 宽度。证据 `.ai/role-create-no-reason/result.json`。 角色管理配置模块权限,用户管理分配角色并预览只读的继承结果;个人允许/禁止及全局制作人设置入口已取消,旧授权地址跳转角色管理。菜单、路由、按钮与元数据查询按有效权限工作;工资月份锁和经办留痕继续保留。 最新类型检查及 Vite 构建通过。独立测试库、独立前后台的真实 Edge 联调 6 项通过,覆盖角色保存与撤权、用户分配工资角色、管理员区域保存、只读提示、768 像素布局和减少动态效果;页面异常及服务端失败为 0。结果 `.ai/authorization-role-real/result.json`;合成响应面板交互另见 `.ai/rbac-browser-result.json`,两类证据分开记录。 ## 客户页面文案与轻动效(2026-11-02) - 客户操作页只呈现业务状态、操作结果和下一步处理提示。开发环境、数据库版本验收、部署进度、实现细节和开发待办保留在内部文档,不作为页面常驻提醒;历史报告中的待确认备注不能冒充当前业务待办。 - “暂存修改”不等于提交或生效;审核相关文案必须与当前开关及返回结果一致。已生成工资的差额按调整单生成当月处理,原记录保留。 - 计算依据使用 `components/payroll/LedgerCalculationDetails.vue` 展示中文字段及业务明细,不直接显示原始 JSON。必要的系统日志诊断信息仍按管理员职责展示。 - 全局弹窗提供正文滚动、独立按钮区和窄屏宽度限制;参数弹窗的类别切换同步取值,错误在字段旁展示,打开后聚焦主要输入控件。 - 页面切换进入 160ms、离开 80ms,位移 4px;组件反馈 120~180ms。`prefers-reduced-motion: reduce` 下停用页面和组件过渡,不用数字滚动制造金额变化。 - 本轮前端类型检查、生产构建和相关差异检查通过;浏览器使用合成数据检查参数、T+、账户弹窗、台账依据和分配调整,参数弹窗在 1440、768、390px 宽度下不越界。未执行会写入业务数据的完整 e2e,合成界面检查不替代客户金额验收。 - 内部审查记录及截图:`.ai/ui-review-20261102/`;参数目录说明与公式相关单元测试结果见 `backend/tests/README.md`。 - 审查修复:FIFO 跨月续算将历史回款和成本标为“原匹配”,与本期应付差额区分;长弹窗按实际顶部间距和底部留白限制高度。运行 `node frontend/e2e/ledger-dialog-regression.mjs`(项目根目录)可隔离渲染真实组件,验证首次计提、续算、旧记录、加载/空状态,以及 8 种视口下的长弹窗滚动和按钮操作。需已安装的 Playwright 与 Edge,模块路径可通过 `PLAYWRIGHT_MODULE` 指定;仅使用合成数据,结果默认写入 `.ai/ui-review-20261102/regression/`,不连接业务接口。 ## 角色授权面板(2026-11-02) 角色管理改为业务分组矩阵:左侧按业务域定位,右侧模块内集中展示操作,支持全选、半选、搜索、复制角色与修改原因。用户管理只分配角色,继承结果只读展示,不再出现个人允许/禁止和全局制作人绑定。旧 `/system/authorization` 跳转角色管理,原“岗位角色”菜单更名为“计提岗位”。 `node frontend/e2e/authorization_roles.mjs` 从项目根执行,使用合成响应验证管理员只读、模块矩阵、用户分配、预览失败、版本冲突提示、未保存关闭保护和三档宽度布局;不登录真实账号、不写业务数据。 ## 系统监控(2026-11-02) 管理员入口 `/system/monitor`,包括健康总览、接口性能、异常排查和任务与同步。复用现有样式和 ECharts,支持请求编号复制、脱敏诊断摘要及关联 T+ 批次跳转;后台采集不依赖页面打开。 `node e2e/monitor.mjs` 从项目根执行时改为 `node frontend/e2e/monitor.mjs`,使用合成响应,不登录真实账号或写业务数据。验证覆盖 1366/1920/768 布局、筛选及详情、下载与任务跳转、连接失败与空数据、关闭刷新、页面隐藏/恢复、离开页面停止轮询、普通用户拒绝访问,共 9 组检查,页面脚本错误 0。需要已安装的 Playwright,可用 `PLAYWRIGHT_MODULE` 指定模块路径;默认前端为 `http://127.0.0.1:5193`,可通过 `MONITOR_UI_URL` 指定。 本轮前端 `vue-tsc --noEmit` 与 Vite 生产构建通过。采集范围、指标近似口径及故障边界见 [系统监控运维说明](../docs/系统监控运维说明.md)。 > 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`(会话状态)。已实现登录链路(2026-10 视觉方向重做:点阵底/涟漪/天光/居中卡片)、系统管理页面、岗位角色与计提参数配置页、Bento 仪表盘首页;视觉体系与设计预览见 `docs/design/视觉方向预览.html`。 ## 职责 - 登录页与图形验证码交互 - 会话状态管理与路由守卫 - 管理员 T+ 数据同步页:`/system/tplus`,支持读取排队、关联检查、批次/异常详情和失败重试;只读配置未就绪时禁用创建。 - T+ 页面位于 `src/views/system/tplus/`,合同在 `src/api/tplus.ts`;创建或重试失败后保留请求键,范围改变或提交成功后才换键。任务由独立读取服务执行,页面刷新查看状态,运行命令见 T+ 本地恢复手册。**e2e 待补(2026-10-30)**:按模块测试约定应在本模块验收节点统一编写并记录,当前仅有 type-check 验证,尚未落 Playwright 用例。 - Vite 开发代理可通过 `DEV_API_TARGET` 指定后端,默认本地 8000;浏览器请求继续使用 `/api` 相对地址。 - 岗位角色管理页(模板绑定)、计提参数配置页(分组目录/批次提交/版本对比/导出/计算预览) - 首页 Bento 仪表盘(M1 空态设计;业务上线后接入真实统计,图表用 ECharts) - 后续:基础数据、业务数据、绩效核算、报表等业务页面 ## 技术栈与版本约束 | 组件 | 版本 | 说明 | | --- | --- | --- | | 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` 与后端签发方式。 ## 视觉设计 **设计立意**:公司名「衍水」是水,而中文「流水账」字面就是**流水之账**——系统的全部工作正是记录回款怎么流、绩效怎么算。视觉方向由此而生:中性灰白的水面纸感 + 靛蓝点缀,安静、克制,长时间核数不累眼。完整规范见 `docs/design/视觉方向预览.html`(2026-10-12 起为 v3「质感版」;2026-10-22 升 v4「同步版」——设计稿与已交付的月结 / 审批 / 报表 / 工作台对齐,色板、字体与组件基调未变)。 **配色**(靛蓝只作重点点缀——主按钮 / 链接 / 焦点 / 当前菜单,大面积用中性灰白) | 名 | 值 | 用途 | | --- | --- | --- | | 靛蓝 | `#2e4ba8` | 主按钮、链接、焦点、当前菜单(配白字约 7.5:1) | | 深靛 | `#223a85` | 主色深阶(按压态) | | 淡靛 | `#edf1fa` | 选中底色、浅色强调底 | | 纸白 | `#ffffff` | 卡片与面板底色 | | 雾灰 | `#f4f5f8` | 页面底色 | | 墨 | `#1c2333` | 正文 | | 墨灰 | `#5b6577` / `#9aa3b2` | 次级文本 / 辅助文本 | **字体**:品牌名用**思源黑体**(`Noto Sans SC` / `Source Han Sans SC`,开源可商用),正文用同一无衬线栈,数字与验证码用等宽。**禁止引入微软雅黑、宋体等需商业授权的字体**——CSS 字体栈、设计稿、导出物一律只用开源或免费商用字体(如阿里巴巴普惠体、HarmonyOS Sans)。 **界面不引入网络字体**——内网环境字体加载不可靠,靠字重、字距与留白做层次更稳妥。新页面请沿用这套字号节奏。 **质感来源**(而非装饰):表面 1–2% 明度差的微渐变、半透明描边、近距 + 远距分层阴影、内高光 / 内阴影、极微噪点(内联 SVG)、精确排版(字距 / 等宽数字 / 行高节律)。 **落地方式**:品牌色已提升为全局 CSS 变量(`src/assets/styles/index.css` 的 `:root`,`--ys-*` 前缀),登录页、布局与各业务页面共用;全局把 `--el-color-primary` 指向靛蓝,并按 Element Plus 公式预计算了 light-3/5/7/8/9 与 dark-2 色阶(不用 `color-mix()`,避免依赖较新浏览器特性)。旧 token 名(深水青 / 金色系列)在 `:root` 中保留映射,存量页面迁移完成后移除。 **语义三色**(2026-10-22 落地):成功 `#1e9e6a` / 警示 `#c98a1b` / 危险 `#c94f4f`,已覆盖 `--el-color-success/warning/danger/error` 及其 `-rgb`、`light-3/5/7/8/9`、`dark-2` 全阶。Element Plus 默认的绿黄红饱和度过高,放在灰底卡片上会跳色。**`-rgb` 变体必须一并覆盖**——组件库的半透明底色用 `rgba(var(--el-color-success-rgb), .1)` 取色,漏改会出现「文字是新的、底色还是旧的」。色阶按 Element Plus 公式预计算,不用 `color-mix()`。 **组件基调**(同样写在 `src/assets/styles/index.css`,页面不要再重复):按钮高 34px / 圆角 7px / 字号 13px,主按钮是 `#3a58bc → #2e4ba8` 微渐变 + 内高光 + 悬停上抬 1px 并加深阴影;输入框高 34px,焦点为靛蓝描边加 3px 柔光外环;表头 12px/500/字距 `0.04em` + 极浅渐变底,单元格内距 12px 16px、行高 1.5,行悬停 120ms;分页需 `background` 模式,当前页与主按钮同材质;`el-tag` 压到 11px 方角。动效统一 120–180ms,只做颜色、阴影与 1px 位移,并尊重 `prefers-reduced-motion`。 **表格行高必须是 1.5**(全局 `.el-table .cell`):Element Plus 默认给单元格 23px 行高(≈1.77 倍),加上 12px 上下内距,单行数据行会到 51px——一屏少看两行。收到 1.5 后单行数据行 44px、纯文本行 44px,与设计稿「12px 内距 + 13px 字」对齐;多行单元格仍按内容撑开。 **页面版式:只用全局类**(新页面不再各写一套) | 类 | 用途 | | --- | --- | | `.page-head` / `.page-title` / `.page-desc` / `.waterline` | 页面头:18px 标题 + 一句 12px 描述 + 水线(**每页一条,不重复**) | | `.card` / `.card-head` / `.card-title` / `.card-body` / `.card-foot` | 内容卡片;卡脚右对齐,承载分页或合计条 | | `.filter-bar` / `.filter-bar__actions` | 筛选条(横向 flex);按钮组靠右 | | `.toolbar` / `.toolbar-note` | 主操作行 / 右侧提示文字 | | `.section-title` | 区块小标题(13px/600) | | `.cell-code` / `.cell-num` / `.cell-muted` | 表格列语义:等宽编号 / 数字右对齐 + 等宽数字 / 弱化文本 | | `.ys-pill` + `--ok` `--wait` `--lock` `--reject` | 状态胶囊(浅底 + 6px 圆点),四档语义与月结生命周期一致 | | `.ys-rise` | 入场上浮动画(卡片默认已带) | `.cell-num` 有两种用法:直接挂在 `td` 上,或包一层 `` 处理「一行里既文本又数字」。给后者单独撑满一行是必要的——行内元素的 `text-align` 无效;但**不能**给 `.cell-num` 统一加 `display`,那会把 `td` 从 `table-cell` 变成 `inline-block`,表格列宽算法随即失效。 **表单页版式(公司信息页为样板)** 设置类表单页不要把表单缩在左上角一小块——内容区再宽也只占三分之一,页面看着像没做完。`views/system/settings/index.vue` 是基准: | 做法 | 口径 | | --- | --- | | 网格 | 左列 `minmax(0, 1fr)` + 右列固定 480px(实景预览),`align-items: start`;≤1200px 折成单列 | | 控件行 | 主控件与说明同一行(`.field-control`):控件 ≤420px,说明跟在后面 20px;≥1600px 说明改右对齐贴卡片右缘,整行铺满而不是缩在左上角 | | 实景预览 | 右侧「登录页预览」按**真实登录页 1:1 复刻**(控件 38px 高、品牌名 17px / 0.34em 字距、logo ≤56×36、三项输入含验证码位、登录按钮、版权行),不是缩比示意——控件高度对不上就等于没有所见即所得 | | 生效范围 | 独立一张卡列出这份设置会出现在哪(登录页品牌位与版权行 / Excel 导出页眉 / 打印稿页眉),用户不用存完再去别处找 | | 预览态 | 预览卡 `aria-hidden` + 标注「示意,非交互」,避免被当成可点的登录框 | **状态与流程表达(2026-10-23 实测补)** - 月结生命周期条四段圆点按阶段着色:草稿中性 / 待审批琥珀 / 已审定靛蓝 / 已驳回砖红。颜色之外每段仍有文字,不靠颜色单独表意。 - 流程条这类分段控件旁要挂一行操作提示(如「点状态只看该阶段,再点一次取消」)。没有提示时,未选中态的四段读起来像四颗孤立的小按钮,不像一条流程。 - 卡片内空态(工作台 `.b-empty-mini`)在卡片剩余高度内居中,不要全挤在顶部留一整块空白。 **菜单图标**(2026-10-22 落地,M1 遗留项):路由 `meta.icon` 只写**语义 key**,图标实现集中在 `src/components/layout/menuIcons.ts` 的注册表(`@element-plus/icons-vue` 具名导入,可摇树)。换图标库或换风格只改注册表,路由表不动。视觉口径沿用设计稿:16px、透明度 0.7(激活项 1)、右间距 9px;分组标题**不加图标**,只有菜单项有。**新增页面必须补 `meta.icon`**,否则菜单项只有文字。注册表是 **UTF-8** 文件——批量转码脚本会把中文注释转成乱码(2026-10-23 已在该文件踩过一次,已修回),改这个文件不要过任何编码转换。 **侧栏层级**(2026-10-23 改版):组标题与菜单项**同字号 13px**——层级靠「折叠箭头 + 组间留白 + 子项缩进 12px」表达,不靠字号/字重压过父级。前两版都不采用:11px 弱墨「目录行」让父级弱于子项,14px/600 加靛蓝竖标又让父级刻意压过子项、像一排小标题在喊叫。激活态只保留**白底浮起 + 靛蓝字**,不再叠左侧 2px 竖条——竖条只能落在侧栏内边缘、与白底悬空,是重复的第二个信号。菜单横向 10px 留白由 `AppMenu.vue` 的 `.app-menu` 提供,`BasicLayout.vue` 的 `.app-aside__menu` 只写上下内距:两处都用 `padding` 简写会同权重靠注入顺序决胜,横向留白会被整条抹掉、条目贴到侧栏边缘。 **签名元素**:**渐隐靛蓝水线**——各页面标题下的细线(56px,左实右虚)与登录页品牌名下的短线(40px),「衍水」之水的提纯,全站唯一的装饰性元素;登录页右上角另有涟漪同心圆与顶部靛蓝天光作背景(低透明度)。与早期深色面板 + 水纹层理同一意象,品牌连续而非推倒重来。 **打印视图**(`views/reports/print/`):纸面产物与屏幕稿不同源——独立顶层路由、不挂侧栏顶栏,白灰画布上一张定宽白稿纸,表格 11px 细实线、表头浅底左对齐(数值列表头右对齐)、章节编号、三栏签字(制表 / 复核 / 审批),无圆角无阴影。屏幕上的操作条带 `.no-print`,打印时整块消失。超出打印行数上限时只打前 N 行并在稿面明确提示,不静默截断。 **构图**:登录页品牌内容(名称 / 标语 / 水线)**垂直居中聚成一块**,配一层柔和径向光晕作落点。 早期版本用 `space-between` 把品牌与标语顶到两端,中间空出大半屏,两处都成了孤岛——超宽屏会把这种疏离放大得更明显。**内容块要限宽**(`max-width`),否则宽屏下文字会被拉散。 ### 写代码时的六条约定 - 覆盖 Element Plus 主题请改 **CSS 变量**(`--el-button-bg-color` 等),不要直接写 `background-color` —— 后者与 `.el-button--primary` 同权重,会受样式注入顺序影响而不稳定 - 中文可在任意字间断行,**关键文案的断行位置要显式控制**(如主标语拆成两个 ``),交给浏览器自动换行会出现单字独占一行的难看结果 - 新页面需响应式到移动端、保留键盘焦点可见、并尊重 `prefers-reduced-motion` - **确认框统一加 `customClass: 'ys-confirm-box'`,不要再传 `type: 'warning'`** —— Element Plus 的默认 warning 图标会渲染在正文左侧独立成列(多行正文时图标悬在文字块中间),与 v3 靛蓝体系也冲突。全局样式(`src/assets/styles/index.css`)已把图标改为 18px 描边样式并放在标题行左侧,正文与标题文字对齐 - **页面 scoped 里只写本页特有的东西**:表格 / 按钮 / 输入框 / 表头 / 分页的皮肤已经全局覆盖,页面再写一遍与组件库同权重,会受样式注入顺序影响而不稳定;结构用全局类(见上一节表格),样式只补本页独有的块(如展开区、抽屉内的键值表) - **状态 / 结果列的固定宽度要按胶囊内容给足**:`.cell` 左右各 16px 内距(共 32px),2 字胶囊约 55px、3 字约 67px、4 字约 79px,宽度不够时 Element Plus 会在胶囊右侧画一个省略号(看起来像多了个小点)。口径:2 字列 ≥96px(如 启用/禁用、成功/失败)、3 字列 ≥108px(如 已核算/已替代/执行中)、4 字列 ≥120px(如 以物抵顶) - **`v-loading` 只罩数据区,不要罩到分页条上**:遮罩会拦截指针事件,盖住分页器后用户点翻页其实点在遮罩上被吞掉,得再点一次,观感就是「数据反复变化」。详见下文「分页与「数据反复变化」的两个坑」 ## 验证码图片 后端自绘渲染(`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)。 ## 端到端测试(2026-10-13 追加 · M1 参数体系) | 脚本 | 覆盖 | 结果 | | --- | --- | --- | | `e2e/login_flow.py` | 登录链路 8 项 | ✓ 全通过(登录页视觉重做后回归) | | `e2e/system_management.py` | 系统管理 13 项 | ✓ 全通过(布局白化后回归) | | `e2e/param_management.py` | 计提参数 7 步:目录分组 → 修改入待提交 → 批次提交(立即生效)→ 目录反映新值 → 版本记录 → 恢复种子值 → 计算预览金标准(27 天 / 9.7‰ / 145.5 元) | ✓ 全通过(首跑) | 运行前置:容器 + 后端(8000)+ 前端 dev(5173);`E2E_ADMIN_PASSWORD` 传管理员密码。 ## 前端页面(2026-10-17 追加 · M3-core ④ 核算与月结) | 页面 | 说明 | | --- | --- | | `/calc/ledger` 绩效台账(新组「绩效工资」order17) | 月/版本筛选、行展开计算快照、重算(原因对话框,管理员/财务) | | `/settlements` 月度结算(order18) | 生成快照(月份输入)→ 状态筛选 → 提交审批(开关关=自动通过锁定)→ 详情抽屉(按人三口径明细/试用期说明/审批单号) | 路由组「绩效工资」;api 封装 `src/api/{calc,settlements}.ts`。type-check 与 build 通过(2026-10-17)。 ## 端到端测试(2026-10-13 追加 · M2 基础数据与业务数据) | 脚本 | 覆盖 | 结果 | | --- | --- | --- | | `e2e/base_data.py` | 基础数据 13 步:菜单两组 → 人员建(经理/业务员指派)→ 编辑抽屉加辐射区域回显 → 删除保护(400 引导改离职)→ 产品建 + 重名 400 → 客户建 → 归属变更(确认文案含「不回溯历史」)→ 留痕抽屉可见 → 控制台无 JS 错误 | ✓ 全通过(首跑) | | `e2e/business_data.py` | 业务全链路 16 步:基础建数 → 业务建(双类别明细)→ 编辑汇总重算(65000→115000)→ 详情抽屉(时间线/毛利润卡片)→ 归属调整 → 发货(SH 编号)→ 回款级联指定批次 → 回款天数「6 天」→ 未指定批次文案 → 状态流转已完成 → 终态禁发货 → 评级计算器出 A 级 → 清理留档 | ✓ 全通过(修复单价小数位/删除保护后) | 运行前置同上;测试数据 `e2e_` 前缀,正常流程内清理(业务含发货/回款时按设计不可删,留档说明)。 **期间修复的缺陷**:后端删除保护两处漏检(客户被业务引用/人员被业务归属引用 → 500)、 单价列 `DECIMAL(14,4)` 被两位小数校验拒收(前后端同源)、`wait_toast` 超时丢失诊断信息。 ## 端到端测试(2026-10-16 追加 · M3 增量批次) | 脚本 | 覆盖 | 结果 | | --- | --- | --- | | `e2e/settings_flow.py` | 公司信息 4 步:进设置页改公司名(随机名防撞)→ 保存 → 退出登录 → **登录页品牌位与页脚即时显示新名**(public 端点)→ finally 还原默认名(防污染其它 e2e 的品牌断言) | ✓ 全通过(修复 public 缓存缺陷后) | | `e2e/secondary_allocation_flow.py` | 二次分配 6 步:路由直达业务管理 → 建客户 → 建 2 名在职营销员 → 新增业务录两行参与人(30%+40%)→ **编辑回显 2 行 + 合计条「余额 30.00%(归属待业务确认)」** → 删除清理 | ✓ 全通过(修复 logo 无关的三处 e2e 选择器后) | 运行前置同上(需 `E2E_ADMIN_PASSWORD`)。**期间修复的缺陷**:public 端点 300s 响应缓存让登录页拿旧公司名(改 `fetch cache: no-store` + 时间戳绕过磁盘缓存);业务弹窗分配表与产品明细共用 `items-table` 类导致选择器错位(分配表加专属 `alloc-table` 类)。 **回归**:既有五脚本(登录 8 / 系统管理 13 / 基础数据 12 / 业务 16 / 参数 7 步)在 allowedRoles 路由扩展与品牌名动态化后全部复跑通过。 ## 前端页面(2026-10-19 追加 · M4 ⑤ 报表统计) | 页面 | 说明 | | --- | --- | | `/reports/personal-detail` 个人绩效明细(新组「报表统计」order19) | 人员/月份/客户类型/产品类别/岗位筛选;需规 2.4.1 全部列;行展开懒加载台账快照;页脚**全筛选合计**(注明非当前页);导出/打印 | | `/reports/department-summary` 部门绩效汇总(order20) | 月份必填;全公司分成独立卡片(单经理 5% × 计入人数 = 合计,每位有聚合行的经理各计一次);团队表(基数/团队分成/全公司分成/占比/调整/净合计/月结冻结额),净合计与月结聚合行同口径,比例列头随在线参数变化;展开成员 → 跨页下钻个人明细(人员与月份带入筛选);未分配桶(按经理筛选时为空) | | `/reports/business-stats` 业务统计(order21) | 关键字/客户/归属人/状态/业务日期;发货额/回款额/绩效(累计口径提示);展开下钻回款与台账明细 | | `/reports/payment-stats` 回款统计(order22) | 月份/回款日期/客户类型/产品类别;8 档区间表(空桶 0,档位由后端下发);展开桶下钻分页明细;客户类型/产品类别对比表 | | `/reports/print/:code` 报表打印(顶层路由,`menu:false`) | 不挂 BasicLayout(打印不带侧栏/顶栏);页眉公司信息走 public 端点;循环翻页取全量(以接口 total 判定超过 2000 行并提示截断);部门汇总按「团队分成 → 全公司 5% → 对账 → 成员明细 → 未分配」五块渲染;`@media print` A4 横版 + 每页表头;签字栏;PDF 由浏览器「另存为」覆盖 | - 路由组「报表统计」四页 `allowedRoles: [admin, finance, leader]`(前端菜单 + 后端 `require_roles` 双层;admin 须显式列入)。 - api 封装 `src/api/reports.ts`(类型 + 查询 + `exportReport` 裸 axios blob;产品类别标签额外覆盖 `mixed`)。 - 权限:营销后勤「授权范围内」待业务权限清单 #6,当前不开放报表。 - type-check 与 build 通过(2026-10-19)。e2e `report_flow.py` 已补 12 步:登录 → API 造数 → 四表筛选/导出下载 → 部门下钻筛选生效 → 回款多桶同时展开互不串行 → 打印视图 → 打印截断提示 → 部门汇总打印五块结构;需 `E2E_ADMIN_PASSWORD` 且前后端在跑。 - **e2e 实跑(2026-10-20)**:`login_flow` 8/8、`system_management` 13/13、`role_home_flow` 4/4、`report_flow` 12/12、`calc_settlement_flow` 8/8 全过。`report_flow` 的回款统计改为展开目标桶后按**本次业务编号**定位明细行,不依赖桶累计值,脚本可重复运行(历史 e2e 数据累计不再造成假失败)。 ## 端到端测试(2026-10-19 追加 · M3 台账与月结) | 脚本 | 覆盖 | 结果 | | --- | --- | --- | | `e2e/calc_settlement_flow.py` | 绩效核算与月度结算 8 步:登录进台账页 → API 预置(客户/人员/业务/发货/回款/核算,随机远未来月份防碰撞)→ 按归属月筛选见 v1 行 → 重算对话框(原因必填)→ v2 生效 → 版本三态筛选(仅历史 / 仅当前 / 清空=全部)→ 生成月结草稿 → 提交审批(开关显式关闭=自动通过锁定)→ 详情抽屉(按人明细/净工资/锁定状态) | ✓ 全通过(2026-10-19 首跑,修复下述缺陷后) | 运行前置同上(需 `E2E_ADMIN_PASSWORD`);数据 `e2e_` 前缀 + 随机远未来月份(209x),核算/月结按设计不可删,行数据留档开发库。 **首跑即抓出三处真实前端缺陷**(M3 台账/月结两页自 2026-10-17 交付起一直「暂无数据」): ① `src/api/{calc,settlements,approvals}.ts` 误用默认导出 `request`(axios 实例,返回 AxiosResponse 未解包),列表 `list` 恒为 undefined → 三页永远「暂无数据」;改为具名导入 `{ request }`(信封解包版)。 ② 三页均无 `onMounted` 初始加载 → 进页不取数;补 `onMounted(() => void load())`。 ③ 三页 `AppPagination` 用 `v-model:page-num` + `@change`(组件无该事件、也未实现 `update:*`)→ 翻页失效;统一为 `:page-num/:page-size` + `@page-change/@size-change`(usePagedList 的 onPageChange/onSizeChange)。 **注意**:e2e 与 `pytest tests/integration` **不可并发**——conftest 每用例整体清空开发库,会清掉 e2e 造的数据造成假失败(本次排查耗时即源于此,两次假失败的报错均为「台账行消失 / 401 登录失效」)。 ## 前端页面(2026-10-19 追加 · M4 ⑧ 工作台) `/dashboard` 由 Bento 占位升级为工作台(**总览/待办/预警一次聚合请求** `/api/v1/dashboard`;最近动态是分页列表,走独立端点): | 区块 | 展示 | | --- | --- | | 经营绩效总览 | KPI 四卡(本月绩效/应付/核算人数/团队数 + 月结状态);近 6 个月绩效折线 + 回款柱状(ECharts) | | 待办事项 | 待审批(跳审批管理)、回款未指定批次(跳回款管理)、T+ 同步失败(未启用显式占位) | | 风险预警 | 回款逾期(danger)/ 审批超时(warning),点击跳处理页;总开关关闭时显示参数提示 | | 最近动态 | 操作日志列表,**服务端分页**(每页 10/20/50)+ 按模块筛选 | - **最近动态独立分页端点**(2026-10-23):`GET /api/v1/dashboard/activities?pageNum&pageSize&module`,返回 `list/total/pageNum/pageSize/modules`(`modules` 是全量模块名,供下拉用,不随翻页变化)。与聚合端点拆开的原因:操作日志可能很长,塞进聚合响应只能截断前 20 条;拆开后**翻页只重取这张列表**,不必重算总览/待办/预警。分页字段沿用全站规范(`pageNum`/`pageSize`/`total`),组件用统一的 `AppPagination` + `usePagedList`(含请求序号防竞态)。 - **分页与「数据反复变化」的两个坑(2026-10-23 实测定位并修复,改分页相关代码前先读)**: 1. **`v-loading` 不能挂在包含分页条的容器上**。Element Plus 的遮罩会拦截指针事件:遮罩一出现,用户点的「上一页 / 下一页」其实点在遮罩上被吞掉,必须再点一次才生效,观感就是「页码和数据对不上、来回跳」。工作台最近动态原先 `v-loading` 挂在 `.activity-body`(同时包住列表与分页条),已改为只罩列表的 `.activity-list-wrap`;其余列表页的 `v-loading` 都挂在 `el-table` 上,分页条在表格之外,天然没有这个问题——**新增页面请照此办理**。 2. **翻页失败必须把分页器退回「屏幕上正在显示的那一页」**。`records` 只在请求成功时更新,而分页器高亮绑的是 `pageNum`;失败不回滚就会出现「高亮第 1 页、列表是第 2 页的数据」的永久错位(用户来回点才恢复)。`usePagedList` 里用 `shownPageNum` / `shownPageSize` 记录实际显示的页,失败时 `restoreShownPage(seq)` 退回;**回滚本身也要防竞态**——只有本次请求仍是最新请求(`seq === requestSeq`)才回滚,否则连点时旧请求失败会把用户刚点出的新页码覆盖掉,表现为「页码自己跳回去」。页码与条数在 `await` 之前取快照,避免等待期间被别的点击改动后记错「显示的页」。 - 两个缺陷都用 Playwright 探针实测过:`ai-aux/preview/probe_pager7.py`(8/8:正常连点、请求在飞时命中分页器而非遮罩、失败回滚、解除注入后恢复)、`probe_pager13.py`(旧请求失败不拉回页码)、`probe_pager14.py`(点第 3 页失败退回第 1 页、连续两次失败仍停在当前页、解除注入后可正常翻页)、`probe_pager8.py`(其它 `usePagedList` 列表页回归)。 - **ECharts 5.6 按需引入**:`echarts/core` + `BarChart/LineChart/Grid/Tooltip` + `SVGRenderer`(与设计预览一致),只注册用到的模块。 - **字体红线**:图表 `textStyle.fontFamily` 必须显式设为思源黑体栈——ECharts 在 Windows 默认微软雅黑(字体商用合规要求)。 - 权限:路由 `allowedRoles: [admin, finance, leader]`,与后端 `require_roles` 双层;营销后勤待权限清单 #6。 - **普通用户落地页**:守卫不再硬编码回 `/dashboard`,统一走 `src/router/access.ts` 的 `firstAccessiblePath`(工作台 → 业务数据 → 回款),普通用户登录/刷新/直连受限页均落 `/business/businesses`,不会无限重定向;登录回跳也经 `canAccessPath` 校验。菜单与面包屑复用同一份准入判定。 - 预警徽标用后端 `warnings.total`(全部命中数),`items` 仅为展示上限内的子集。 - `package.json` 新增依赖 `echarts@5.6.0`(MIT);type-check 与 build 通过(2026-10-19)。 - e2e `e2e/role_home_flow.py`:普通用户登录落地、菜单隐藏、刷新保持、直连 `/dashboard` 与报表页回退;需 `E2E_ADMIN_PASSWORD`。 ## 2026-10-30 正式核算入口 业务列表增加正式核算,台账可看逐段依据和创建当月分配调整;T+页面增加正式业务导入预览/执行。会话返回can_write_payroll控制按钮,后端仍逐请求校验指定制作人。审核能力默认关闭,不展示提交审核步骤或审批单列;开启后原节点可用。 本轮vue-tsc与Vite构建通过;真实浏览器使用合成接口验证资料表单、封存确认请求及768宽弹窗、默认无审核入口,控制台错误0。界面检查不替代后端真实金额与客户样本验收;详情见 `docs/正式工资闭环开发记录_2026-10-30.md`。