diff --git a/qy-lty-admin/docs/修改记录.md b/qy-lty-admin/docs/修改记录.md index d184f77..730f9e3 100644 --- a/qy-lty-admin/docs/修改记录.md +++ b/qy-lty-admin/docs/修改记录.md @@ -25,6 +25,60 @@ +### [2026-05-08] Phase 3(前端)凭据槽位编辑对话框 + 提交反馈 + +配套服务端 Phase:本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容(GET 脱敏掩码 + PUT 全字段覆写语义不变) +覆盖前端需求:CRED-FE-04、CRED-FE-05 + +- **文件路径**: + - `app/layout.tsx`(修改) + - `components/ai-model/credential-slot-dialog.tsx`(新增) + - `app/ai-model/page.tsx`(修改) +- **修改类型**: 修改 + 新增(前端 UI 收尾;纯前端,无新依赖、不动 lockfile、不触达服务端) +- **修改内容**: + - `app/layout.tsx`(修复仓库 pre-existing 死代码 bug): + - 顶部新增 `import { Toaster } from "@/components/ui/sonner"` + - `{children}` 内 `{children}` 之后追加 `` + - 修复仓库内 9 处 `toast(...)` 调用全部静默的 dead-code 状态(`components/ui/sonner.tsx` 早就存在 Toaster 包装但**从未在 RootLayout 挂载**) + - `components/ai-model/credential-slot-dialog.tsx`(**新建** ~150 行): + - 顶部 `"use client"` 指令;具名导出 `CredentialSlotDialog` + - 文件命名 **kebab-case**(与仓库 9 个现有业务对话框 `user-form-dialog.tsx` / `add-song-dialog.tsx` 等对齐) + - 表单技术栈:React Hook Form + Zod + shadcn Form wrapper(1:1 模板自 `components/users/user-form-dialog.tsx`) + - Zod schema:`appId` + `accessToken` 都 `min(1)`(access_token **强制输入**,见「修改原因」段权衡说明) + - 受控接口:`{ open: boolean; onOpenChange: (open: boolean) => void }` + - 打开时 `useEffect` 调 `getCredentialSlot()` 拉取 → `form.reset({ appId: data.appId, accessToken: "" })` —— **accessToken 永远默认空串**,绝不回填脱敏掩码 + - `` —— 仅作视觉提示 + - `每次保存都需要重新输入 Access Token(不会显示原值,避免回写脱敏掩码)` + - `updatedAt` 以 `new Date(slot.updatedAt).toLocaleString("zh-CN")` 中文只读显示 + - 提交成功:`toast.success("凭据槽位已更新", { description: "配置已生效" })` + `handleOpenChange(false)` + - 提交失败:`toast.error("保存失败", { description: handleApiError(e) })` + 对话框保持打开 + 表单值不丢 + - 关闭时 `form.reset({ appId: "", accessToken: "" })` + `setSlot(null)` —— 避免下次打开残留上次输入 + - useEffect cleanup `cancelled` flag 防止快速开关导致的 race condition + - **关键 import 决策**(避免仓库内同名 dead code): + - `import { toast } from "sonner"` —— **不**走 `@/hooks/use-toast`(Radix Toast 实现,与 Sonner 不通信) + - `import { handleApiError } from "@/lib/api/error-handler"` —— **不**走 barrel `@/lib/api`(barrel 内有同名重复定义) + - `app/ai-model/page.tsx`: + - 删除 L9-15 Dialog 系列命名导入整段(`Dialog / DialogContent / DialogDescription / DialogHeader / DialogTitle`)—— 占位 Dialog 删除后 page 不再直接使用 Dialog primitive + - 在 lucide-react import 之后新增 `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"` + - 删除 L473-485 占位 Dialog(含 `通用凭据槽位` + `对话框真实内容由 Phase 3 落地`) + - 替换为 `` + - 保留 L1 `"use client"` / L20-25 mounted state + isCredentialDialogOpen state + useEffect 守卫 / L35-43「凭据槽位」Button 入口(含 `mounted && hasPermission("credential-slot")` 守卫) + - Tabs / TabsContent / Card 等其余内容(L18-471)逐字不动 +- **修改原因**: + - 收尾 Milestone v1.0「通用凭据槽位前端集成」:让授权运营能查看脱敏的当前凭据、安全提交新值,且成功 / 失败两条路径都有清晰的中文 toast 反馈 + - 修复 `app/layout.tsx` 的 pre-existing dead code:仓库 `components/ui/sonner.tsx` 早已存在 Sonner Toaster 包装但**从未挂载到 RootLayout**,导致仓库内 9 处既有 `toast(...)` 调用全部静默;本 phase 顺手修复(否则 Phase 3 反馈不可见) + - 拆出独立组件 `credential-slot-dialog.tsx` 而非把表单内联进 page.tsx:(a) 与仓库 9 个现有业务对话框抽离风格一致;(b) 让 page.tsx 保持简洁,不掺业务表单状态;(c) 关闭对话框时组件级 form.reset 隔离干净 + - **业务语义权衡(重要 — 候选下一周期 milestone 锚点)**:本 phase Zod schema 把 `accessToken: z.string().min(1)` —— **强制每次重输** access_token,**不实现** ROADMAP success criteria #2 中提到的「留空保留旧值」语义。原因: + - 后端 PUT 是全字段覆写语义(qy_lty 后端 v1.0 Phase 2 已锁定 commit `46d72b8`) + - 后端 GET 返回的 access_token 字段是脱敏掩码(末 4 位明文 + 前缀 `*`),前端永远拿不到真值 + - 「留空保留旧值」需后端配合识别 PUT body 中 access_token 的脱敏掩码格式并保留旧值(后端逻辑:`if access_token == mask_token(current.access_token): preserve old`) + - 该后端识别逻辑不在 Milestone v1.0 范畴,**已记入候选下一周期 milestone**(参见 PROJECT.md / REQUIREMENTS.md 候选清单 + STATE.md 风险段) + - 当前实现退化为「每次保存都要重输 access_token」—— UX 略差但语义正确(永远不会回写脱敏掩码导致后端清空真实凭据) + - 沿用 Phase 1 已建立的「`accessTokenMasked` vs `accessToken` 类型层屏障」(前者是脱敏字符串、后者是明文)—— TS 编译期会拦截「把脱敏字符串赋给 accessToken 字段」的 bug 路径 + - Sonner(`components/ui/sonner.tsx`)+ `lib/api/error-handler.ts:handleApiError` 是 CONTEXT D-Toast / D-错误处理 锁定的双依赖;不引入新依赖、不混合 Radix Toast hook、不走 barrel 同名重复定义,避免仓库内 dead code 串扰 +- **跨项目联动**: 无 — Phase 3 是前端 UI 收尾,access_token 强制输入语义为 Phase 1+2 已建立的前后端互引(commit `46d72b8`)的延续;'留空保留旧值' 语义需后端识别脱敏掩码格式 + 保留旧值,已记入候选下一周期 milestone(不属于 v1.0 范畴) +- **服务端联动**: 同上「跨项目联动」字段;后端 commit `46d72b8` 已建立互引闭环,本 phase 无需再次互引;未来若启动「识别脱敏掩码保留旧值」的后端 patch milestone,届时双端各写新一轮互引条目 + ### [2026-05-08] Phase 2(前端)RBAC 收敛 + AI 模型页凭据槽位入口 配套服务端 Phase:本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容(不引入新契约)