diff --git a/qy-lty-admin/.planning/REQUIREMENTS.md b/qy-lty-admin/.planning/REQUIREMENTS.md index eaf63e1..a077b12 100644 --- a/qy-lty-admin/.planning/REQUIREMENTS.md +++ b/qy-lty-admin/.planning/REQUIREMENTS.md @@ -90,8 +90,8 @@ - [x] **CRED-FE-01** API 客户端 `lib/api/credential-slot.ts`:导出 `getCredentialSlot()`、`updateCredentialSlot({ app_id, access_token })`;含响应适配器 `mapBackendCredentialSlot()`(snake_case → camelCase);共享类型 `CredentialSlot { appId, accessTokenMasked, updatedAt }`;从 `lib/api/index.ts` 导出 - [x] **CRED-FE-02** RBAC 模块声明:`lib/permissions.ts` 加入 `credential-slot` 模块 key(`PermissionModule` 类型扩充);`PERMISSION_MATRIX` 把该模块分配给"超级管理员"和"AI模型管理员"两个角色;`getModuleFromPath()` 不需要新映射(凭据槽位是内嵌于 `/ai-model` 的子能力,不占独立路由) - [x] **CRED-FE-03** `/ai-model` 页面入口:在合适位置(如页头工具栏 / Header 右侧)渲染"凭据槽位"按钮或卡片;仅当 `hasPermission('credential-slot')` 为 true 时可见;点击触发对话框打开 -- [ ] **CRED-FE-04** 编辑对话框组件 `components/ai-model/CredentialSlotDialog.tsx`:基于 `components/ui/dialog.tsx`;表单 React Hook Form + Zod 校验;预填态显示后端返回的 `app_id` 明文 + `access_token` 末 4 位掩码 + 不可改的 `updated_at`;表单语义为"留空保留旧值,重新输入才覆写"(避免把脱敏掩码当真值回写);提交触发 `updateCredentialSlot()`,仅提交用户实际输入的字段 -- [ ] **CRED-FE-05** 提交反馈:成功调用 `useToast()` 弹 Sonner 成功 toast 并自动关闭对话框、重新触发 GET 刷新预填;失败走 `lib/api/error-handler.ts` 统一映射后端错误并 toast 提示 +- [x] **CRED-FE-04** 编辑对话框组件 `components/ai-model/credential-slot-dialog.tsx`(kebab-case,191 行):基于 `components/ui/dialog.tsx`;表单 React Hook Form + Zod 校验;预填态显示后端返回的 `app_id` 明文 + `access_token` 末 4 位掩码(仅 placeholder)+ 不可改的 `updated_at`(toLocaleString('zh-CN'));表单语义改为"access_token 强制输入"(CONTEXT D-提交逻辑 锁定 — 后端 PUT 全字段覆写 + 前端无法识别脱敏掩码格式,「留空保留旧值」需后端配合,记入下一周期 milestone);提交触发 `updateCredentialSlot()` 全字段覆写 +- [x] **CRED-FE-05** 提交反馈:Sonner 命令式 `import { toast } from "sonner"` — 成功 `toast.success("凭据槽位已更新", { description: "配置已生效" })` + 自动 `handleOpenChange(false)`;失败 `import { handleApiError } from "@/lib/api/error-handler"` 显式路径 → `toast.error("保存失败", { description: handleApiError(e) })` + 对话框保持打开 + 表单值不丢;GET 加载失败 `toast.error("加载失败", ...)` ### 候选优先级(已转移自 brownfield 文档化阶段,本期不消化) @@ -136,8 +136,8 @@ | CRED-FE-01 API 客户端 `lib/api/credential-slot.ts`(类型 + 适配器 + GET/PUT) | Phase 1 凭据槽位 API 客户端 | — | ✅ Done (Plan 01-01 commits a0d0b9c + c072bbe;Plan 01-02 commit c1743a3 修改记录追加 + 双重验证;Phase 1 已封盘 2026-05-08) | | CRED-FE-02 RBAC 模块声明(`lib/permissions.ts` 加 `credential-slot` key + 矩阵分配) | Phase 2 RBAC 收敛 + AI 模型页入口 | yes | ✅ Done (Plan 02-01 commit d60dd89, 2026-05-08) | | CRED-FE-03 `/ai-model` 页面入口(受 `hasPermission('credential-slot')` 收敛) | Phase 2 RBAC 收敛 + AI 模型页入口 | yes | ✅ Done (Plan 02-01 commit 0bcaa39, 2026-05-08) | -| CRED-FE-04 编辑对话框 `CredentialSlotDialog.tsx`(RHF + Zod,留空保留旧值语义) | Phase 3 编辑对话框 + 提交反馈 | yes | Pending | -| CRED-FE-05 提交反馈(Sonner toast 成功 + `error-handler.ts` 失败映射) | Phase 3 编辑对话框 + 提交反馈 | yes | Pending | +| CRED-FE-04 编辑对话框 `credential-slot-dialog.tsx`(RHF + Zod,access_token 强制输入语义) | Phase 3 编辑对话框 + 提交反馈 | yes | ✅ Done (Plan 03-02 commit d719891, 2026-05-08) | +| CRED-FE-05 提交反馈(Sonner toast 成功 + `error-handler.ts` 失败映射) | Phase 3 编辑对话框 + 提交反馈 | yes | ✅ Done (Plan 03-02 commit d719891 + 03-01 commit 7065d73 Toaster 前置, 2026-05-08) | **覆盖率**:5/5 Active 需求映射到 phase ✓(无孤儿,无重复) @@ -151,3 +151,4 @@ *2026-05-08 更新:Plan 02-01 落地(commits d60dd89 + 0bcaa39),CRED-FE-02 + CRED-FE-03 状态切到 ✅ Done;Phase 2 进度 1/2,等待 Plan 02-02 收尾* *2026-05-08 更新:Plan 02-02 落地(commit 2be1f1d 修改记录追加 + plan 级双重验证),Phase 2 全部交付(2/2 plan);Milestone 进度 2/3 phase(67%),等待 /gsd-plan-phase 3 启动 Phase 3* *2026-05-08 更新:Plan 03-01 落地(commit 7065d73 — RootLayout 挂载 Sonner Toaster,修复仓库 9 处 toast pre-existing dead code,CRED-FE-05 反馈通道前置打通;CRED-FE-05 完整闭环仍依赖 03-02 接入);Phase 3 进度 1/3* +*2026-05-08 更新:Plan 03-02 落地(commits d719891 + 7872840 — 新建 CredentialSlotDialog 组件 191 行 RHF+Zod+Sonner+handleApiError + page.tsx 删占位 Dialog 接入新组件),CRED-FE-04 + CRED-FE-05 状态切到 ✅ Done;Phase 3 进度 2/3,等待 Plan 03-03 收尾(修改记录追加 + plan 级双重验证)* diff --git a/qy-lty-admin/.planning/ROADMAP.md b/qy-lty-admin/.planning/ROADMAP.md index f9521db..0886542 100644 --- a/qy-lty-admin/.planning/ROADMAP.md +++ b/qy-lty-admin/.planning/ROADMAP.md @@ -63,7 +63,7 @@ 5. 端到端串联(依赖 qy_lty 后端 Phase 2 落地):以"超级管理员"账户登录 → 进入 `/ai-model` → 点击凭据槽位入口 → 输入一组真实 APP ID + Access Token → 提交 → 看到成功 toast → 关闭后重新打开对话框,`access_token` 仅显示新值末 4 位、`updated_at` 已刷新 **Plans**: 3 plans - [x] 03-01-PLAN.md — 在 app/layout.tsx 挂载 Sonner Toaster(修复仓库 pre-existing dead code,解锁 toast 反馈)✅ 2026-05-08(commit 7065d73) - - [ ] 03-02-PLAN.md — 新建 components/ai-model/credential-slot-dialog.tsx(RHF + Zod + Sonner + handleApiError)+ 改 app/ai-model/page.tsx(删占位 Dialog + 接入新组件) + - [x] 03-02-PLAN.md — 新建 components/ai-model/credential-slot-dialog.tsx(RHF + Zod + Sonner + handleApiError)+ 改 app/ai-model/page.tsx(删占位 Dialog + 接入新组件)✅ 2026-05-08(commits d719891 + 7872840) - [ ] 03-03-PLAN.md — docs/修改记录.md 顶部追加 Phase 3 条目(含 access_token 强制输入权衡说明 + 候选下一周期 milestone 锚点)+ plan 级双重验证(tsc 反向断言 + 13 条 grep specifics + lockfile diff) **UI hint**: yes @@ -76,7 +76,7 @@ Phase 按数值顺序执行:1 → 2 → 3(如出现紧急插入,记为 1.1 |-------|----------------|--------|-----------| | 1. 凭据槽位 API 客户端 | 2/2 | ✅ Complete | 2026-05-08 | | 2. RBAC 收敛 + AI 模型页入口 | 2/2 | ✅ Complete | 2026-05-08 | -| 3. 编辑对话框 + 提交反馈 | 1/3 | In Progress | - | +| 3. 编辑对话框 + 提交反馈 | 2/3 | In Progress | - | --- @@ -84,3 +84,4 @@ Phase 按数值顺序执行:1 → 2 → 3(如出现紧急插入,记为 1.1 *2026-05-08 更新:Phase 2 全部交付(Plan 02-01 + Plan 02-02 共 2/2 完成;commit 2be1f1d 修改记录追加 + plan 级双重验证);Milestone 进度 2/3 phase(67%),等待 /gsd-plan-phase 3 启动 Phase 3* *2026-05-08 更新:Phase 3 plan 规划完成(3 plan 串行:03-01 挂载 Sonner Toaster → 03-02 新组件 + page 接入 → 03-03 修改记录追加 + 双重验证);等待 /gsd-execute-phase 3 启动执行* *2026-05-08 更新:Plan 03-01 落地(commit 7065d73 — RootLayout 挂载 Sonner Toaster,修复 9 处 toast pre-existing dead code);Phase 3 进度 1/3,等待 Plan 03-02 启动* +*2026-05-08 更新:Plan 03-02 落地(commits d719891 — 新建 CredentialSlotDialog 组件 191 行 RHF+Zod+Sonner+handleApiError;7872840 — page.tsx 删占位 Dialog 接入新组件);CRED-FE-04 + CRED-FE-05 完整闭环;Phase 3 进度 2/3,等待 Plan 03-03 收尾* diff --git a/qy-lty-admin/.planning/STATE.md b/qy-lty-admin/.planning/STATE.md index 65da6f4..1ea3d43 100644 --- a/qy-lty-admin/.planning/STATE.md +++ b/qy-lty-admin/.planning/STATE.md @@ -2,20 +2,20 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: 通用凭据槽位前端集成 -status: completed -last_updated: "2026-05-08T04:26:30Z" +status: in_progress +last_updated: "2026-05-08T04:32:34Z" last_activity: 2026-05-08 progress: total_phases: 3 completed_phases: 2 total_plans: 7 - completed_plans: 5 - percent: 71 + completed_plans: 6 + percent: 86 --- # Project State — 洛天依应用管理后台(qy-lty-admin) -**最后更新**: 2026-05-08(Phase 3 启动;Plan 03-01 落地 — RootLayout 挂载 Sonner Toaster,修复仓库 9 处 toast pre-existing dead code,commit 7065d73;Phase 3 进度 1/3,等待 Plan 03-02 启动) +**最后更新**: 2026-05-08(Plan 03-02 落地 — 新建 components/ai-model/credential-slot-dialog.tsx 191 行 RHF+Zod+Sonner+handleApiError,commit d719891;改 app/ai-model/page.tsx 删占位 Dialog 接入新组件,commit 7872840;CRED-FE-04 + CRED-FE-05 完整闭环;Phase 3 进度 2/3,等待 Plan 03-03 收尾) ## 项目引用 @@ -30,13 +30,13 @@ progress: ``` Milestone: v1.0 通用凭据槽位前端集成 Phase: Phase 3「编辑对话框 + 提交反馈」🚧 进行中 -Plan: 03-01 完成 ✅ / 03-02 待启动 / 03-03 待启动 -Status: Phase 3 in progress (1/3 plans done);待 Plan 03-02 启动 -Progress: [███▓░░░░░░] 33%(Phase 3 内部 1/3 plan 完成;milestone 整体 71%) +Plan: 03-01 完成 ✅ / 03-02 完成 ✅ / 03-03 待启动 +Status: Phase 3 in progress (2/3 plans done);待 Plan 03-03 启动 +Progress: [██████▓░░░] 67%(Phase 3 内部 2/3 plan 完成;milestone 整体 86%) Last activity: 2026-05-08 ``` -**下一步行动**:运行 Plan 03-02(新建 `components/ai-model/credential-slot-dialog.tsx` + RHF/Zod/Sonner + handleApiError;改 `app/ai-model/page.tsx` 删占位 Dialog 接入新组件)。Sonner Toaster 已在 RootLayout 挂载(commit 7065d73),全局 toast 反馈通道已通。 +**下一步行动**:运行 Plan 03-03(在 `docs/修改记录.md` 顶部追加 Phase 3 条目,含 access_token 强制输入语义的权衡说明 + 候选下一周期 milestone「后端识别脱敏掩码保留旧值」+ 「跨项目联动」字段;plan 级整体双重验证)。CRED-FE-04 + CRED-FE-05 业务功能已完整闭环(commits d719891 + 7872840)。 ## Phase 概览 @@ -44,7 +44,7 @@ Last activity: 2026-05-08 |-------|------|------|---------|------| | 1 | 凭据槽位 API 客户端 | CRED-FE-01 ✅ | — | ✅ 已交付(2/2 plan,2026-05-08)| | 2 | RBAC 收敛 + AI 模型页入口 | CRED-FE-02 ✅, CRED-FE-03 ✅ | yes | ✅ 已交付(2/2 plan,2026-05-08)| -| 3 | 编辑对话框 + 提交反馈 | CRED-FE-04, CRED-FE-05(部分 — Toaster 前置已就绪)| yes | 🚧 进行中(1/3 plan,2026-05-08)| +| 3 | 编辑对话框 + 提交反馈 | CRED-FE-04 ✅, CRED-FE-05 ✅(业务闭环 — 修改记录追加待 Plan 03-03 收尾)| yes | 🚧 进行中(2/3 plan,2026-05-08)| ## 联动 milestone @@ -58,10 +58,10 @@ Last activity: 2026-05-08 | 指标 | 数值 | |------|------| | 已完成 phase | 2 / 3 | -| 已完成 plan | 5 / 7(Phase 1 全部交付 + Phase 2 全部交付 + Phase 3 进行中 1/3)| -| Milestone 进度 | ~71%(2/3 phase 完成 + Phase 3 内部 33%)| +| 已完成 plan | 6 / 7(Phase 1 全部交付 + Phase 2 全部交付 + Phase 3 进行中 2/3)| +| Milestone 进度 | ~86%(2/3 phase 完成 + Phase 3 内部 67%)| | 启动日期 | 2026-05-07 | -| 最近活动 | 2026-05-08 Plan 03-01 落地(commit 7065d73,RootLayout 挂载 Sonner Toaster)| +| 最近活动 | 2026-05-08 Plan 03-02 落地(commits d719891 + 7872840,新组件 191 行 + page.tsx 接入)| ### Plan 执行记录 @@ -72,6 +72,7 @@ Last activity: 2026-05-08 | 02-01 | 2 | 2 | ~6min | 2026-05-08 | | 02-02 | 2 | 1 | ~3min | 2026-05-08 | | 03-01 | 1 | 1 | ~1min | 2026-05-08 | +| 03-02 | 2 | 2 | ~85s | 2026-05-08 | ## 累积上下文 @@ -85,6 +86,7 @@ Last activity: 2026-05-08 - **2026-05-08 Plan 02-01 落地**:lib/permissions.ts PermissionModule union +1('credential-slot' 第 14 项)+ 「超级管理员」/「AI模型管理员」两角色数组末尾追加 + 顶部注释表新增「凭据槽位」行(commit d60dd89);app/ai-model/page.tsx 转 Client Component(line 1 加 'use client')+ 加 useState/useEffect mounted 守卫(复用 sidebar.tsx 同模式)+ DashboardHeader 内追加凭据槽位 Button(variant=outline / KeyRound 图标 / 受 mounted && hasPermission('credential-slot') 收敛)+ 后插入 controlled mode 占位 Dialog(DialogTitle「通用凭据槽位」+ DialogDescription「对话框真实内容由 Phase 3 落地」)(commit 0bcaa39);`npx tsc --noEmit` 不引入指向 lib/permissions.ts / app/ai-model/page.tsx 的新错误(67 条存量错误与本 phase 无关);不引入新依赖(4 个 lockfile 全部未动)。CRED-FE-02 + CRED-FE-03 已交付,等待 Plan 02-02 收尾修改记录追加。 - **2026-05-08 Plan 02-02 落地**:docs/修改记录.md 顶部追加 [2026-05-08] Phase 2 条目(commit 2be1f1d,纯追加 +32 行 / -0 行;含 7 字段结构 + CONTEXT.md D-XX 锁定的「跨项目联动」字段「无 — 不引入新跨项目契约 / 后端 commit 46d72b8 互引仍有效 / Phase 3 引入实质 PUT 调用时再评估」);plan 级整体双重验证:tsc 整体 67 条存量错误 + 反向断言 0 条指向本 phase 改动文件(A 段)/ 14 条 grep 全命中含 specifics 11 条 + 反向断言 4 角色数组(B 段,原 PLAN awk pattern 因 Windows Bash 转义警告失败,替换为 sed -n 'N,Mp' | grep -c 行号区间方案,结果一致)/ 4 个 manifest+lockfile 在工作区 + HEAD~1 比较均 0 行 diff(C 段)/ next lint 因项目无 .eslintrc* 跳过沿用 Phase 1 判定(D 段)。CLAUDE.md 修改记录强制规则闭环;Phase 2 全部 5 条 success criteria 全部确认通过,Phase 2 已交付(2/2 plan)。 - **2026-05-08 Plan 03-01 落地**:app/layout.tsx 第 3 行新增 `import { Toaster } from '@/components/ui/sonner'`;第 17-21 行 `` 块由单行改为多行结构、`{children}` 之后追加 ``(共 +5 / -1 行;commit 7065d73)。修复仓库 9 处 `toast(...)` 调用因 portal 未挂载而全部静默失败的 pre-existing dead code 问题,Phase 3 业务功能 toast 反馈通道前置打通。tsc 反向断言 0 条指向 app/layout.tsx;4 个 lockfile 工作区 0 行 diff(不引入新依赖,sonner@^1.7.1 已在 deps)。决策点:挂在 `` 内 `{children}` 之后(不是 ``、不是 children 之前);不挂第二个 Radix Toast Toaster(CONTEXT D-Toast 锁单一 Sonner 通道);不给 RootLayout 加 `"use client"`(components/ui/sonner.tsx 已 'use client',RSC layout 直接渲染 client child 即可);不新增 ThemeProvider(sonner.tsx:9 useTheme 已有 'system' fallback)。Phase 3 进度 1/3,等待 Plan 03-02 启动。 +- **2026-05-08 Plan 03-02 落地**:新建 `components/ai-model/credential-slot-dialog.tsx`(191 行;commit d719891)— 首行 `"use client"` + RHF/Zod schema(appId/accessToken 强制 min(1))+ useEffect on `open` 拉数据 with cancelled flag + form.reset({ appId, accessToken: "" }) + Sonner 命令式 `toast.success("凭据槽位已更新", { description: "配置已生效" })` / `toast.error("保存失败"|"加载失败", { description: handleApiError(e) })` + `import { handleApiError } from "@/lib/api/error-handler"` 显式路径(不走 barrel)+ `placeholder={slot?.accessTokenMasked ?? "..."}` 仅作视觉提示 + `defaultValues.accessToken = ""` 永远空串(避免回写脱敏掩码)+ `updatedAt` 用 `toLocaleString('zh-CN')` 只读显示 + 失败路径不关闭 Dialog 不 reset 表单。改 `app/ai-model/page.tsx`(commit 7872840;+3 / -18)— 删 L9-15 Dialog 系列命名导入 + 加 1 行 `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"` + 删 L473-485 占位 Dialog(含「对话框真实内容由 Phase 3 落地」字面量)+ 加 4 行 ``;保留 `mounted && hasPermission("credential-slot")` 守卫与 Button 入口(Phase 2 不破坏)。验证:tsc 反向断言 0 条新错误指向 2 个改动文件;12+5 条正向 grep 全命中;4+3 条反向断言全满足;4 个 lockfile 0 行 diff。决策点:文件命名 kebab-case 与仓库 9 个现有业务对话框对齐;access_token 强制输入(不实现"留空保留旧值",因后端 PUT 全字段覆写 + 前端无法识别脱敏掩码格式,需后端配合,记入候选下一周期 milestone);失败路径不关 Dialog 不 reset 表单(CONTEXT D-错误处理);Sonner 命令式 toast 不走 useToast hook(Radix Toast 与 Sonner 不通);handleApiError 显式路径不走 barrel(避免 namespace 歧义);Loader2 仅在新组件内用、page.tsx 不加 import;updatedAt 用 toLocaleString('zh-CN') 零依赖。CRED-FE-04 + CRED-FE-05 完整闭环。Phase 3 进度 2/3,等待 Plan 03-03 收尾(修改记录追加 + plan 级双重验证)。 ### 待办事项 @@ -105,16 +107,16 @@ Last activity: 2026-05-08 |------|------| | 代码库映射 | ✅ `.planning/codebase/` 7 文档(commit `a85b6a7`) | | PROJECT.md | ✅ 已加入 Milestone v1.0 段 + Active 5 项 | -| REQUIREMENTS.md | ✅ Active 段已落地,Traceability 已回填 5/5;CRED-FE-01 + CRED-FE-02 + CRED-FE-03 已勾选完成 | -| 路线图 | ✅ ROADMAP.md 落地(3 phase,coarse),Phase 1 + Phase 2 已完成、Phase 3 待启动 | -| 当前 phase | Phase 3 🚧 进行中(03-01 完成,03-02 / 03-03 待启动) | +| REQUIREMENTS.md | ✅ Active 段已落地,Traceability 已回填 5/5;CRED-FE-01 + CRED-FE-02 + CRED-FE-03 + CRED-FE-04 + CRED-FE-05 已勾选完成 | +| 路线图 | ✅ ROADMAP.md 落地(3 phase,coarse),Phase 1 + Phase 2 已完成、Phase 3 进行中 | +| 当前 phase | Phase 3 🚧 进行中(03-01 + 03-02 完成,03-03 待启动) | | 当前 milestone | v1.0 通用凭据槽位前端集成 | ## 会话连续性 **最近会话**:2026-05-08 -**最近动作**:执行 Plan 03-01(app/layout.tsx 挂载 Sonner Toaster + tsc 反向断言 0 条 + lockfile 0 diff + SUMMARY 落地);commit 7065d73;Phase 3 进度 1/3,CRED-FE-05 反馈通道前置打通 -**下一会话起点**:运行 Plan 03-02(新建 components/ai-model/credential-slot-dialog.tsx — RHF + Zod + Sonner toast + handleApiError;改 app/ai-model/page.tsx 删 Phase 2 占位 Dialog 接入新组件) +**最近动作**:执行 Plan 03-02(新建 components/ai-model/credential-slot-dialog.tsx 191 行 RHF+Zod+Sonner+handleApiError + 改 app/ai-model/page.tsx 删占位 Dialog 接入新组件 + tsc 反向断言 0 条 + 12+5 条正向 grep 全命中 + 4+3 条反向断言全满足 + 4 lockfile 0 diff + SUMMARY 落地);commits d719891 + 7872840;Phase 3 进度 2/3,CRED-FE-04 + CRED-FE-05 完整闭环 +**下一会话起点**:运行 Plan 03-03(在 docs/修改记录.md 顶部追加 Phase 3 条目,含 access_token 强制输入语义的权衡说明 + 候选下一周期 milestone「后端识别脱敏掩码保留旧值」+ 「跨项目联动」字段;plan 级整体双重验证) ## 工作流配置 @@ -152,3 +154,4 @@ CLAUDE.md 中两条强制规则,做任何 phase 时必须遵守: *2026-05-08 Plan 02-01 完成(RBAC 扩展 + /ai-model 页面入口 Button + 占位 Dialog),CRED-FE-02 + CRED-FE-03 已交付;Phase 2 进度 1/2,等待 Plan 02-02 收尾* *2026-05-08 Plan 02-02 完成(修改记录追加 + 双重验证),Phase 2 全部交付(2/2 plan);milestone 进度 67%(2/3 phase),等待 /gsd-plan-phase 3 启动 Phase 3* *2026-05-08 Plan 03-01 完成(RootLayout 挂载 Sonner Toaster — commit 7065d73,修复 9 处 toast pre-existing dead code);Phase 3 进度 1/3(33%),milestone 进度 71%(5/7 plan),等待 Plan 03-02 启动* +*2026-05-08 Plan 03-02 完成(新建 CredentialSlotDialog 组件 191 行 commit d719891 + 改 page.tsx 删占位 Dialog 接入新组件 commit 7872840);CRED-FE-04 + CRED-FE-05 完整闭环;Phase 3 进度 2/3(67%),milestone 进度 86%(6/7 plan),等待 Plan 03-03 收尾* diff --git a/qy-lty-admin/.planning/phases/03-dialog-feedback/03-02-SUMMARY.md b/qy-lty-admin/.planning/phases/03-dialog-feedback/03-02-SUMMARY.md new file mode 100644 index 0000000..2c03b95 --- /dev/null +++ b/qy-lty-admin/.planning/phases/03-dialog-feedback/03-02-SUMMARY.md @@ -0,0 +1,250 @@ +--- +phase: 03-dialog-feedback +plan: 02 +subsystem: ui +tags: [next.js, react, react-hook-form, zod, sonner, dialog, credential-slot] + +# Dependency graph +requires: + - phase: 01-api-client + provides: lib/api/credential-slot.ts(getCredentialSlot / updateCredentialSlot / 类型) + - phase: 02-rbac-entry + provides: app/ai-model/page.tsx 占位 Dialog + isCredentialDialogOpen state + 凭据槽位 Button 入口 + - phase: 03-01 + provides: app/layout.tsx 已挂载 Sonner Toaster portal(toast 反馈通道前置就绪) +provides: + - components/ai-model/credential-slot-dialog.tsx:CredentialSlotDialog 组件(RHF + Zod + Sonner + handleApiError) + - app/ai-model/page.tsx:删除占位 Dialog + 接入新组件,凭据槽位编辑端到端可用 +affects: + - 03-03(修改记录追加 + plan 级整体双重验证) + +# Tech tracking +tech-stack: + added: [] # 无新依赖;react-hook-form / @hookform/resolvers / zod / sonner / lucide-react 全部已在 deps + patterns: + - "受控 Dialog + 内部 RHF 状态:page 持有 open / onOpenChange,子组件管理表单状态(与 user-form-dialog.tsx 一致)" + - "open=true 触发 useEffect 拉数据 + form.reset:cancelled flag 防 race condition" + - "脱敏掩码语义屏障:accessToken defaultValue 永远空串,accessTokenMasked 仅作 placeholder 视觉提示" + - "Sonner 命令式 toast:import { toast } from \"sonner\" 直接 toast.success / toast.error,不走 useToast hook(仓库 useToast 是 Radix 实现,与 Sonner 不通)" + - "handleApiError 显式路径:from \"@/lib/api/error-handler\",不走 barrel @/lib/api(barrel 里有同名 dead-code 重复定义)" + +key-files: + created: + - "components/ai-model/credential-slot-dialog.tsx(191 行)" + modified: + - "app/ai-model/page.tsx(+3 / -18 行;删 L9-15 Dialog 系列 import + 加 1 行 CredentialSlotDialog import + 删 L473-485 占位 Dialog 13 行 + 加 4 行新组件 JSX)" + +key-decisions: + - "文件命名 kebab-case credential-slot-dialog.tsx(与仓库 9 个现有业务对话框 user-form-dialog.tsx / role-dialog.tsx / add-song-dialog.tsx 等对齐);导出名 PascalCase CredentialSlotDialog 沿用 export function 命名导出风格" + - "access_token 强制输入(CONTEXT D-提交逻辑 锁定):defaultValues.accessToken 永远空串、Zod schema accessToken: z.string().min(1),避免回写脱敏掩码;「留空保留旧值」语义需后端识别脱敏掩码格式,记入候选下一周期 milestone" + - "失败路径不关闭对话框、不 reset 表单:toast.error + 表单字段保留以便用户重试(CONTEXT D-错误处理 锁定)" + - "Loader2 仅在新组件内使用,page.tsx 不加 Loader2 import(最小化 page.tsx 依赖面)" + - "updatedAt 用 toLocaleString('zh-CN'):无新依赖、零成本;如未来需要相对时间「3 分钟前」再切 date-fns" + +requirements-completed: [CRED-FE-04, CRED-FE-05] # CRED-FE-04 完整闭环;CRED-FE-05 在 03-01 Toaster 挂载基础上完整闭环 + +# Metrics +duration: ~85s +completed: 2026-05-08 +--- + +# Phase 3 Plan 02:编辑对话框组件落地 + 页面接入 Summary + +**新建 `components/ai-model/credential-slot-dialog.tsx`(191 行;RHF + Zod + Sonner + handleApiError),改 `app/ai-model/page.tsx` 删除 Phase 2 占位 Dialog 接入新组件;access_token 强制输入语义、updated_at 只读显示、成功 toast.success + 自动关闭、失败 toast.error + 对话框保持打开 + 表单值不丢,CRED-FE-04 + CRED-FE-05 完整闭环** + +## Performance + +- **Duration**: ~85 秒(00:31:09Z → 00:32:34Z) +- **Started**: 2026-05-08T04:31:09Z +- **Completed**: 2026-05-08T04:32:34Z +- **Tasks**: 2 / 2 +- **Files**: 1 created + 1 modified + +## Accomplishments + +- **新增组件 `components/ai-model/credential-slot-dialog.tsx`(191 行)**:基于 `components/users/user-form-dialog.tsx` 模板改写,具名导出 `CredentialSlotDialog`,接口 `{ open, onOpenChange }`;React Hook Form + Zod(`appId.min(1)` + `accessToken.min(1)` 强制非空)+ shadcn Form wrapper(`Form / FormField / FormItem / FormLabel / FormControl / FormDescription / FormMessage`)+ Sonner 命令式 toast + handleApiError 显式路径 +- **`app/ai-model/page.tsx` 改造完成**:删 L9-15 Dialog 系列命名导入 + 删 L473-485 占位 Dialog(含「对话框真实内容由 Phase 3 落地」字面量)+ 加 1 行 `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"` + 加 4 行新组件 JSX,复用既有 `isCredentialDialogOpen` state;保留 `mounted && hasPermission("credential-slot")` 守卫与 Button 入口(Phase 2 不被破坏) +- **CRED-FE-04 完整落地**:编辑对话框组件可读取后端数据(`getCredentialSlot`)、明文预填 `appId`、`accessToken` placeholder 显示脱敏掩码、`updatedAt` toLocaleString('zh-CN') 只读显示、提交触发 `updateCredentialSlot` 全字段覆写 +- **CRED-FE-05 完整闭环(基于 03-01 Toaster 挂载)**:成功路径 `toast.success("凭据槽位已更新", { description: "配置已生效" })` + 自动关闭对话框;失败路径 `toast.error("保存失败", { description: handleApiError(e) })` + 对话框保持打开 + 表单字段不丢;加载失败路径 `toast.error("加载失败", { description: handleApiError(e) })` +- **不引入新依赖**:4 个 lockfile(package.json / yarn.lock / package-lock.json / pnpm-lock.yaml)全部 0 行 diff +- **不破坏存量类型检查**:`npx tsc --noEmit` 过滤后 0 条新错误指向本 plan 的 2 个改动文件 + +## Task Commits + +每个 task 原子提交: + +1. **Task 1:新建 components/ai-model/credential-slot-dialog.tsx** - `d719891` (feat) + - 191 行新文件;先 `mkdir -p components/ai-model/`(目录此前不存在) + - import 块:useEffect / useState(react)、useForm(react-hook-form)、zodResolver(@hookform/resolvers/zod)、z(zod)、toast(sonner)、Loader2(lucide-react)、Button / Dialog 系列 / Form 系列 / Input(@/components/ui/*)、getCredentialSlot / updateCredentialSlot / type CredentialSlot(@/lib/api/credential-slot)、handleApiError(@/lib/api/error-handler) + - Zod schema:`{ appId: z.string().min(1, "App ID 不能为空"), accessToken: z.string().min(1, "请输入 Access Token") }` + - useEffect on `open`:cancelled flag + try/catch;getCredentialSlot 成功 → form.reset({ appId, accessToken: "" }) + setSlot + - handleSubmit:updateCredentialSlot → toast.success + handleOpenChange(false);失败 → toast.error + 对话框保持 + - JSX:DialogHeader(标题「通用凭据槽位」+ 中文描述)/ isLoading spinner / Form / FormField APP ID / FormField Access Token(`placeholder={slot?.accessTokenMasked ?? "输入 Access Token"}` + FormDescription「每次保存都需要重新输入...」)/ updatedAt 只读 `

` 用 `toLocaleString('zh-CN')` / DialogFooter 取消 + 保存按钮(loading 态 Loader2 + 「保存中...」) + +2. **Task 2:改 app/ai-model/page.tsx 删占位 Dialog 接入新组件** - `7872840` (feat) + - 删 L9-15 Dialog 系列命名导入(7 行) + - 加 1 行 `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"` + - 删 L473-485 占位 Dialog(13 行,含「对话框真实内容由 Phase 3 落地」字面量) + - 加 4 行 `` + - 净变化 +3 / -18 + +## Files Created/Modified + +- **`components/ai-model/credential-slot-dialog.tsx`**(191 行新文件)——首行 `"use client"` + 191 行整体;CredentialSlotDialog 命名导出 +- **`app/ai-model/page.tsx`**(+3 / -18)——删 Dialog 系列 import + 加 CredentialSlotDialog import + 删占位 Dialog + 加新组件 JSX;保留所有其他内容(Tabs / TabsContent / Card / Button 入口 / mounted 守卫 / hasPermission 收敛) + +## Decisions Made + +- **文件命名 kebab-case**:与仓库 9 个现有业务对话框对齐(`user-form-dialog.tsx` / `role-dialog.tsx` / `add-song-dialog.tsx` / `add-outfit-dialog.tsx` / `add-print-batch-dialog.tsx` / `add-dance-dialog.tsx` / `add-achievement-dialog.tsx` / `add-food-dialog.tsx` / `song-detail-dialog.tsx`);CONTEXT.md L32 写的 `CredentialSlotDialog.tsx`(PascalCase)属于 Discretion 范畴,研究阶段已建议改为 kebab-case +- **`access_token` 强制输入语义**(不实现"留空保留旧值"):CONTEXT D-提交逻辑 锁定。理由:后端 PUT 全字段覆写、前端无法识别脱敏掩码格式;`defaultValues.accessToken` 永远空串、Zod schema 强制 `min(1)`、`placeholder` 用 `slot?.accessTokenMasked` 仅作视觉提示。"留空保留旧值"语义需后端配合识别脱敏掩码格式(候选下一周期 milestone) +- **失败路径不关闭对话框、不 reset 表单**:CONTEXT D-错误处理 锁定。`handleSubmit` catch 块仅 `toast.error + handleApiError`,不调 `handleOpenChange(false)`、不调 `form.reset()`,让用户能看到错误并直接重试,不丢失输入 +- **Sonner 命令式 toast,不走 useToast hook**:仓库 `hooks/use-toast.ts` 与 `components/ui/use-toast.ts` 是两份完全相同的 Radix Toast 实现(295 行 dead code),与 Sonner 互不通信。本组件 `import { toast } from "sonner"` 直接命令式调用 `toast.success / toast.error` +- **handleApiError 显式路径**:`from "@/lib/api/error-handler"`(即 `lib/api/error-handler.ts:38` `(error: unknown): string`),不走 barrel `@/lib/api`(`lib/api/index.ts:191` 有同名 `(error: any) => string` 重复定义,存在 namespace 歧义) +- **Loader2 仅在新组件内使用**:page.tsx 不加 Loader2 import,组件内部 isLoading(GET 拉数据时)+ isSubmitting(PUT 提交时)两处 spinner 都用 Loader2,最小化 page.tsx 依赖面 +- **updatedAt 时间格式选 `toLocaleString('zh-CN')`**:零依赖、零成本;如未来需要相对时间「3 分钟前」再切 `date-fns`(已在 deps) + +## Deviations from Plan + +None — plan executed exactly as written。所有「严格约束」全部遵守: + +| 约束 | 状态 | +|------|------| +| 文件命名 kebab-case | ✅ `credential-slot-dialog.tsx` | +| 组件导出 PascalCase + 具名 | ✅ `export function CredentialSlotDialog` | +| 首行 `"use client"` | ✅ Line 1 | +| `import { toast } from "sonner"` 命令式 | ✅ Line 7 | +| `import { handleApiError } from "@/lib/api/error-handler"` 显式路径 | ✅ Line 34 | +| `defaultValues.accessToken = ""` 永远空串 | ✅ Line 60 | +| `placeholder={slot?.accessTokenMasked ?? ...}` 仅作视觉提示 | ✅ Line 149 | +| Zod `accessToken.min(1)` 强制输入 | ✅ Line 43 | +| 失败路径不调 `handleOpenChange(false)` / `form.reset()` | ✅ Line 105-108 | +| 不引入新依赖 | ✅ 4 lockfile 0 diff | +| page.tsx 删 Dialog 系列 import | ✅ | +| page.tsx 加 CredentialSlotDialog import 紧邻 lucide-react import 之后 | ✅ Line 10 | +| page.tsx 删 L473-485 占位 Dialog | ✅ | +| page.tsx 替换为 `` | ✅ Line 467 | +| page.tsx 保留 `mounted && hasPermission("credential-slot")` 守卫 | ✅ Line 29 | + +## Issues Encountered + +无。Plan 描述精确(2 个 task + 13 条 grep specifics + 4 条反向断言),全部一次过;TypeScript 类型检查无新错误(67 条存量错误与本 phase 无关,沿用 Phase 1+2 判定)。 + +## 验证结果 + +### Task 1 验证(components/ai-model/credential-slot-dialog.tsx) + +**A 段:tsc 反向断言** +| 期望 | 实际 | +|------|------| +| `npx tsc --noEmit` 过滤后 0 条指向新文件 | ✅ 0 条命中 | + +**B 段:12 条正向 grep(CONTEXT.md L253-268 specifics #1-3, #5-10)** +| # | 模式 | 命中行 | 状态 | +|---|------|--------|------| +| 1 | `export function CredentialSlotDialog` | L53 | ✅ | +| 2a | `useForm` | L4, L58 | ✅ | +| 2b | `zodResolver` | L5, L59 | ✅ | +| 2c | `z\.object` | L41 | ✅ | +| 3a | `useEffect` | L3, L64 | ✅ | +| 3b | `getCredentialSlot` | L30, L68 | ✅ | +| 5 | `placeholder.*accessTokenMasked` | L149 | ✅ | +| 6 | `每次保存都需要重新输入` | L154 | ✅ | +| 7 | `slot\.updatedAt` | L162 | ✅ | +| 8 | `updateCredentialSlot` | L31, L98 | ✅ | +| 9 | `toast\.success.*凭据槽位已更新` | L102 | ✅ | +| 10 | `handleApiError` | L34, L76, L106 | ✅ | + +**C 段:4 条反向断言(防回归)** +| 模式 | 期望 | 实际 | +|------|------|------| +| `defaultValues.*accessTokenMasked`(绝不能把脱敏掩码当默认值) | 0 行 | ✅ 0 行 | +| `from "@/hooks/use-toast"`(绝不走 Radix Toast hook) | 0 行 | ✅ 0 行 | +| `from "@/lib/api"\s*$`(必须显式 from `@/lib/api/error-handler`,不走 barrel) | 0 行 | ✅ 0 行 | +| `^"use client"`(首行必须 "use client") | 1 行 | ✅ L1 命中 | + +**D 段:lockfile 未动** +| 期望 | 实际 | +|------|------| +| `git diff --stat HEAD -- package.json yarn.lock package-lock.json pnpm-lock.yaml` 0 行 | ✅ 0 行 | + +**done 综合**: +- ✅ 文件存在 191 行(≥130 阈值) +- ✅ 12 条 grep specifics 全部 ≥1 行命中 +- ✅ 4 条反向断言全部满足 +- ✅ tsc 0 条新错误指向本文件 +- ✅ lockfile 0 行 diff + +### Task 2 验证(app/ai-model/page.tsx) + +**A 段:tsc 反向断言** +| 期望 | 实际 | +|------|------| +| `npx tsc --noEmit` 过滤后 0 条指向 page.tsx | ✅ 0 条命中 | + +**B 段:5 条正向 grep** +| 模式 | 命中行 | 状态 | +|------|--------|------| +| `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"` | L10 | ✅ | +| `` +- ✅ 旧占位 Dialog 字面量 0 行命中 +- ✅ Dialog 系列命名导入 0 行命中 +- ✅ `mounted && hasPermission("credential-slot")` 守卫保留(Phase 2 不破坏) +- ✅ tsc 0 条新错误指向本文件 +- ✅ lockfile 0 行 diff + +## Phase 3 Success Criteria(ROADMAP.md L58-63)对应表 + +| # | Criterion | 状态 | 备注 | +|---|-----------|------|------| +| 1 | 打开自动 GET 拉取 + appId 明文预填 + accessToken placeholder 掩码 + updatedAt 只读 | ✅ Task 1 | useEffect on `open` → `getCredentialSlot()` → `form.reset({ appId, accessToken: "" })`;placeholder 用 `slot?.accessTokenMasked`;updatedAt 用 `toLocaleString('zh-CN')` 只读 `

` | +| 2 | RHF + Zod + 强制输入 access_token(替代「留空保留旧值」语义) | ✅ Task 1 | Zod schema `accessToken.min(1)`;权衡说明已并入 03-03 修改记录 | +| 3 | 提交成功 → toast.success + 关闭 | ✅ Task 1 | `toast.success("凭据槽位已更新")` + `handleOpenChange(false)`;下次重新打开时 useEffect 自动 reload | +| 4 | 提交失败 → handleApiError + toast.error + 不关闭 + 表单值不丢 | ✅ Task 1 | catch 块仅 `toast.error("保存失败", { description: handleApiError(e) })`,不调 close / 不调 reset | +| 5 | 端到端串联(依赖后端 Phase 2 落地) | ✅ 程序化验证(tsc + grep)| 浏览器 E2E 推迟(无 E2E 框架);本仓 Phase 3 收尾节奏与后端 Phase 2 完工对齐 | + +## User Setup Required + +无 — 本 plan 不引入新依赖、不需要环境变量、不需要外部服务配置。 + +## Next Phase Readiness + +- ✅ CRED-FE-04 + CRED-FE-05 已落地,Milestone v1.0 前端集成业务功能完整闭环 +- ✅ 4 个 lockfile 0 diff,下游 plan 不需重装依赖 +- ⏭️ 下一步:执行 Plan 03-03(在 `docs/修改记录.md` 顶部追加 Phase 3 条目,含「修改原因」段显式说明 access_token 强制输入语义的权衡 + 候选下一周期 milestone「后端识别脱敏掩码保留旧值」+ 「跨项目联动」字段;plan 级整体双重验证) +- ⚠️ 端到端浏览器测试推迟:依赖后端 qy_lty Phase 2「管理端读写接口」联调,本仓库代码层程序化验证(tsc + grep)已通过 +- ⚠️ 候选下一周期 milestone:给后端加「识别 PUT body 中 access_token 是脱敏掩码格式则保留旧值」的逻辑,使前端能去掉「强制输入」UX 退化 + +## Self-Check: PASSED + +- ✅ `components/ai-model/credential-slot-dialog.tsx` 存在 191 行(grep + ls 双验证) +- ✅ `app/ai-model/page.tsx` 含 CredentialSlotDialog import + JSX(grep 命中 L10 + L467) +- ✅ commit `d719891` 存在于 git log(feat(03-02): 新建 CredentialSlotDialog 组件) +- ✅ commit `7872840` 存在于 git log(feat(03-02): /ai-model 页面接入 CredentialSlotDialog 组件) +- ✅ tsc 反向断言 0 条新错误指向本 plan 改动文件 +- ✅ 4 个 lockfile 工作区 0 行 diff +- ✅ 12+5 条正向 grep 全部命中;4+3 条反向断言全部满足 + +--- + +*Phase: 03-dialog-feedback* +*Plan: 02* +*Completed: 2026-05-08*