# FE-04 用户信息单据 — AI 代码评审报告(第 2 轮) - 规格:`docs/superpowers/specs/2026-06-01-FE-04.md` - 阶段:前端(frontend) - 关联 REQ:REQ-USR-001(增加用户)/ REQ-USR-002(修改用户) - 关联原型:`prototype/erp.html` `
` - 本轮 fix commit:`96e88d3`(编辑预填走 navigate state + 补 loadError 返回列表入口) - 裁决:**approve** --- ## 一、作用域核查(通过) 本轮 diff 实现文件全部落在 `frontend/` 下(`src/pages/usr/UserDetail/`、`src/pages/usr/UserList/index.tsx`、`tests/unit/`),未触碰 `backend/` / `sql/` / `scripts/`,符合前端阶段路径作用域硬约束。工作树干净(fix 已 commit)。 ## 二、上轮 must-fix 复验(核心维度) ### B1(上轮 blocker)— 已修复 ✔ 上轮判定:edit 态预填把路由主键 `:id` 当「用户号」去 `GET /api/usr/users?queryField=用户号` 精确匹配,真实后端必然返回空 → 误报 40401,无法满足 REQ-USR-002 编辑预填(BR17)。 本轮 fix(commit `96e88d3`)按上轮修复方向 #1 落地,复验确认到位: - `frontend/src/pages/usr/UserList/index.tsx:36` 双击行改为 `navigate('/usr/users/' + row.id, { state: { user: row } })`,把列表行 `UserVO` 经 navigate state 透传。 - `frontend/src/pages/usr/UserDetail/index.tsx:43` 从 `location.state.user` 取 `presetUser` 传入 hook。 - `frontend/src/pages/usr/UserDetail/useUserDetail.ts:116-131` edit 分支改为:有 `presetUser` → `initFromVo` 回填;缺 `presetUser`(直链访问 / 刷新丢 state)→ 进入 `loadError`(`loadFailed=true` + `MSG_LOAD_DETAIL_FAIL`),**不再**调 `getUserDetail`、不再把主键塞进「用户号」查询。原 `getUserDetail` import 与 `notFound` 状态已彻底移除。 - `frontend/src/pages/usr/UserDetail/index.tsx:108-128` loadError 整页态在 edit 模式额外提供「返回列表」入口(spec § 4「edit 详情失败给整页重试或返回列表」),替代原误导性的 40401「该用户不存在」页。 - 残留核查(grep):`src/` 内已无 `getUserDetail` 调用方(仅 `usrApi.ts` 保留导出,见 S1)、无 `notFound` 状态、无「按主键查用户号」路径;`MSG_ERR_USER_NOT_FOUND` 仅在 **submit** 路径(`useUserDetail.ts:225`,PUT 返回 40401)保留使用,符合 spec § 4「edit submit 可命中 40401」,非误用。 测试同步更新且通过: - `tests/unit/useUserDetail.test.tsx`:新增「edit 缺 presetUser → loadFailed 且不调 getUserDetail」、保留「edit 带 presetUser 跳过 getUserDetail」。 - `tests/unit/UserDetailPage.test.tsx`:edit 预填用例改为经 navigate state 注入 `presetUser` 并断言 `mockedDetail` 未被调用;新增「edit 缺 state → loadError 提供 点击重试 + 返回列表」。 - `tests/e2e/userdetail.spec.ts:97` `edit user prefill then save`:经列表双击(携带 state)预填→改语言→PUT 保存→回流列表,通过。 > 结论:B1 已被真实修复(非掩盖),数据流自洽,且测试改造正确反映了「主键无 by-id 读端点、预填依赖列表行 state」这一事实约束。 ## 三、本轮门禁证据(复跑确认) - `npm run lint`:exit 0(无 error/warning)。 - `npx tsc --noEmit`:exit 0(含 `renderShell.tsx` 新增 `ShellInitialEntry` 本地类型,类型自洽)。 - `npm run test:unit`:40 文件 / 193 用例全通过(FE-04 相关 UserDetailPage 10、useUserDetail、UserDetailToolbar 等全绿)。 - `npm run test:e2e -- userdetail.spec.ts`:5 passed / 0 failed。ECONNREFUSED 为 route 层 mock 下的预期 vite proxy 噪声,不影响判定。 ## 四、对照确认(七维 + 通用维,沿用上轮通过项,本轮 diff 未改动者不再赘述) - 原型一致性:工具栏 / 3 列 form-grid / tabs-row(权限组 active + 5 占位页签 disabled)/ perm-list 结构与 `#screen-userdetail` 一致,主操作「保存」置工具栏,无结构性偏移;本轮 diff 未触碰布局。 - Design Tokens:语义色仍走 `var(--color-*)`;本轮 diff 未引入新色值。 - a11y(客观项):表单控件经 AntD `Form.Item label` 关联;「取消」脏表单二次确认(`Modal.confirm`)保留;loadError「返回列表」为标准 `Button`,键盘可达。 - 业务校验前端复刻:BR3/BR4/BR6/BR7/BR8/BR5/BR9/BR11 与错误码 40001/40901/40401/40301 文案及就近高亮均保持。 - API 调用一致性:create/update/listEmployees/listPermissions 经统一 `request.ts` 实例,无裸 fetch/axios;请求体字段对齐 docs/05。本轮去掉了 edit 预填对列表端点的误用,API 调用面更收敛。 - 状态机覆盖:loading / empty / error(loadError 重试 + edit 返回列表)/ 正常 / 提交中 五态齐备;edit 缺 state 归入 loadError,语义比上轮 40401 更准确。 ## 五、建议项(非 must-fix,可后续消化,不影响本轮 approve) - S1(沿上轮 S1,已部分自然消解):`frontend/src/api/usrApi.ts:107` `getUserDetail` 现已无生产调用方(仅测试 mock 中作为占位导出)。可在后续清理为「删除该函数」或「待后端补单用户详情/按主键定位端点后复用」。当前保留不构成缺陷(lint/tsc 均不报未用导出)。 - S2:`userVoToFormValues`(`constants.ts:146`)因 `UserVO`(FE-03 列表 VO)不含 `iEmployeeId` / `iCanModifyBill` / 已授权权限 id,edit 预填时这三项被置默认(`iEmployeeId=null` / `iCanModifyBill=0` / `checkedPermissionIds=[]`)。这是 spec D4 已登记的数据模型限制(列表 VO 为唯一可用源),非本轮回归;待后端补单用户详情读端点后可完整回勾。已属 spec 决策范畴,不作 must-fix。 - S3(沿上轮 S3):`useUserDetail.ts:143` catch 文案分支读 `permissions.length` 为闭包旧值,仅影响 loadError 文案精度,不影响功能。 ## 六、裁决 上轮唯一 blocker(B1)已在本轮 diff 中真实修复,数据流自洽、测试改造正确、门禁四项(lint / tsc / unit / e2e)全绿,未引入新缺陷、未回归既有通过项。其余为可选建议项,均不构成 must-fix。 **verdict: approve**