# Roadmap：洛天依应用管理后台（qy-lty-admin）

## 概览

本路线图聚焦 **Milestone v1.0「通用凭据槽位前端集成」**：在 Web 管理后台的 `/ai-model` 大模型管理页面接入后端 v1.0 暴露的 `/api/v1/admin/credential-slot/` 端点，让运营者能够录入与编辑 APP ID + Access Token，且 Access Token 仅展示末 4 位脱敏掩码、留空保留旧值。粒度为 **coarse**（目标 2-4 phase），按"API 客户端 → 权限收敛 + 页面入口 → 编辑对话框 + 反馈"自下而上分三个 phase 串行推进。

**跨项目依赖（重要）**：本前端 milestone 的代码层工作（Phase 1-3）**不阻塞** qy_lty 后端，可独立开发并以 mock / 联调环境推进；但**端到端集成测试**与上线验收**强依赖** qy_lty 后端 Milestone v1.0 的 **Phase 2「管理端读写接口」**（GET/PUT `/api/v1/admin/credential-slot/`）落地后才能跑通。规划时序上，建议本仓库 Phase 1 与后端 Phase 1-2 并行，本仓库 Phase 3 与后端 Phase 2 收尾对齐，以便 milestone 完成时双方在 `docs/修改记录.md` 互相引用条目。

## Milestones

- 🚧 **v1.0 通用凭据槽位前端集成** — Phase 1-3（启动 2026-05-07，与 qy_lty 后端 v1.0 并行）

## Phases

**Phase 编号说明：**
- 整数 phase（1、2、3）：当期 milestone 计划工作
- 小数 phase（2.1、2.2）：紧急插入工作（标记 INSERTED）

小数 phase 在数值序内夹在前后整数之间执行。

- [x] **Phase 1: 凭据槽位 API 客户端** — 落地 `lib/api/credential-slot.ts`：类型定义、`mapBackendCredentialSlot` 适配器、`getCredentialSlot()` / `updateCredentialSlot()` 两个调用，并从 `lib/api/index.ts` 导出 ✅ 2026-05-08 完成
- [ ] **Phase 2: RBAC 收敛 + AI 模型页入口** — 在 `lib/permissions.ts` 新增 `credential-slot` 模块 key，分配给"超级管理员"与"AI模型管理员"；在 `/ai-model` 页面渲染受权限收敛的"凭据槽位"入口（按钮或卡片）
- [ ] **Phase 3: 编辑对话框 + 提交反馈** — 实现 `components/ai-model/CredentialSlotDialog.tsx`（React Hook Form + Zod、脱敏掩码预填、留空保留旧值语义），并通过 Sonner toast + `error-handler.ts` 完成成功/失败反馈

## Phase Details

### Phase 1: 凭据槽位 API 客户端
**Goal**: 在 `lib/api/` 层提供独立、无 UI 依赖的凭据槽位读写客户端，让后续 phase 可以直接以"调用 + 类型"方式接入，不必再次处理 axios / 适配器细节
**Depends on**: Nothing（本 milestone 首个 phase）
**Requirements**: CRED-FE-01
**Success Criteria**（必须为真）:
  1. `lib/api/credential-slot.ts` 导出 `getCredentialSlot()` 与 `updateCredentialSlot(payload)` 两个函数，分别走 `apiClient.get` / `apiClient.put` 命中 `/v1/admin/credential-slot/`，与现有 `lib/api/*.ts` 的拦截器、Bearer token 注入、`StandardResponseMiddleware` 解包行为完全一致
  2. 模块导出共享类型 `CredentialSlot { appId: string; accessTokenMasked: string; updatedAt: string }` 与提交载荷类型，前端类型为 camelCase；后端 snake_case 字段（`app_id` / `access_token` / `updated_at`）通过 `mapBackendCredentialSlot()` 适配器统一转换，沿用 `lib/api/adapters.ts` 的 `mapBackend*` 约定
  3. `lib/api/index.ts` 导出新模块，`import { getCredentialSlot, updateCredentialSlot, type CredentialSlot } from '@/lib/api'` 在任一组件文件中均能解析通过 `tsc --noEmit`
  4. 在浏览器开发态以 mock 后端或后端 Phase 2 联调环境调用 `getCredentialSlot()`，控制台可以看到一条带 `Authorization: Bearer ...` 的请求，且返回值字段名是前端 camelCase（说明适配器生效，未把后端原始 snake_case 直接透传）
**Plans**: 2 plans
  - [x] 01-01-PLAN.md — 新建 lib/api/credential-slot.ts（类型 + adapter + GET/PUT）+ lib/api/index.ts 末尾追加具名 re-export
  - [x] 01-02-PLAN.md — docs/修改记录.md 顶部追加 Phase 1 条目 + 跑双重验证（npm run lint + npx tsc --noEmit）+ 探针验证 barrel 入口

### Phase 2: RBAC 收敛 + AI 模型页入口
**Goal**: 通过 `lib/permissions.ts` 把"凭据槽位"声明为受控模块、仅向"超级管理员"与"AI模型管理员"开放；并在 `/ai-model` 页面渲染受权限校验收敛的入口控件，让授权用户能看到入口、未授权用户看不到入口
**Depends on**: Phase 1
**Requirements**: CRED-FE-02, CRED-FE-03
**Success Criteria**（必须为真）:
  1. `lib/permissions.ts` 的 `PermissionModule` 类型新增 `'credential-slot'` 字面量，`PERMISSION_MATRIX` 中"超级管理员"与"AI模型管理员"两个角色的模块列表均包含 `'credential-slot'`，其余角色（内容管理员、卡牌管理员、查看者）不包含；调用 `hasPermission('credential-slot')` 在两类账户下返回 `true`，其他角色返回 `false`
  2. `getModuleFromPath('/ai-model')` 行为不变（凭据槽位是 `/ai-model` 内嵌子能力，不占独立路由），不引入侧边栏新菜单项
  3. 以"AI模型管理员"角色登录访问 `/ai-model`，页面工具栏 / Header 区域可见明确的"凭据槽位"入口控件（按钮或卡片，文案明确）；以"内容管理员"或"查看者"角色登录访问同一页面，入口控件不渲染（DOM 中不存在，而非仅隐藏）
  4. 入口控件的可见性判断走 `hasPermission('credential-slot')`，不直接读 `localStorage.user_role` 字符串比较；点击入口控件触发对话框打开行为（Phase 3 落地后端到端可用，本 phase 至少打开一个空对话框占位以验证联动点存在）
**Plans**: 2 plans
  - [x] 02-01-PLAN.md — 扩展 lib/permissions.ts RBAC（PermissionModule union +1 / 矩阵 +2 角色）+ app/ai-model/page.tsx 加 "use client"、入口 Button、占位 Dialog ✅ 2026-05-08（commits d60dd89 + 0bcaa39）
  - [ ] 02-02-PLAN.md — docs/修改记录.md 顶部追加 Phase 2 条目 + plan 级双重验证（npx tsc --noEmit 反向断言 + grep 11 条 specifics + 不引入新依赖）
**UI hint**: yes

### Phase 3: 编辑对话框 + 提交反馈
**Goal**: 落地 `CredentialSlotDialog` 组件让授权运营者能够查看脱敏的当前凭据、安全地提交新值，且成功 / 失败两条路径都有清晰的 toast 反馈；表单语义采用"留空保留旧值"避免回写脱敏掩码假值
**Depends on**: Phase 2
**Requirements**: CRED-FE-04, CRED-FE-05
**Success Criteria**（必须为真）:
  1. 打开对话框时自动调用 `getCredentialSlot()` 拉取数据：`app_id` 字段以**明文**预填、`access_token` 字段以**末 4 位掩码**显示并附"如需更新请重新输入，留空保留旧值"提示文案、`updated_at` 以只读形式呈现（运营可看到"上次更新"时间）
  2. 表单使用 React Hook Form + Zod 校验：当 `app_id` 输入框被清空且与原值不同则提示"不能为空"；`access_token` 字段允许留空（语义=保留旧值），但**一旦用户输入新值**则要求非空白字符；提交时**仅传递用户实际改动过的字段**给 `updateCredentialSlot()`，**绝不**把脱敏掩码当真值回写
  3. 提交成功路径：`updateCredentialSlot()` 返回成功后，调用 `useToast()` 弹出 Sonner 成功 toast（中文文案，如"凭据槽位已更新"），对话框自动关闭，再次打开时数据被重新拉取并展示新的 `updated_at`
  4. 提交失败路径：后端返回非成功响应或网络异常时，错误经由 `lib/api/error-handler.ts` 统一映射为可读中文消息后通过 toast 提示；对话框保持打开、表单字段保留用户输入、不丢失编辑态
  5. 端到端串联（依赖 qy_lty 后端 Phase 2 落地）：以"超级管理员"账户登录 → 进入 `/ai-model` → 点击凭据槽位入口 → 输入一组真实 APP ID + Access Token → 提交 → 看到成功 toast → 关闭后重新打开对话框，`access_token` 仅显示新值末 4 位、`updated_at` 已刷新
**Plans**: TBD
**UI hint**: yes

## Progress

**执行顺序：**
Phase 按数值顺序执行：1 → 2 → 3（如出现紧急插入，记为 1.1 / 2.1 等）

| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. 凭据槽位 API 客户端 | 2/2 | ✅ Complete | 2026-05-08 |
| 2. RBAC 收敛 + AI 模型页入口 | 1/2 | 🚧 In progress | - |
| 3. 编辑对话框 + 提交反馈 | 0/TBD | Not started | - |

---

*生成时间：2026-05-07，Milestone v1.0「通用凭据槽位前端集成」启动；与 qy_lty 后端 v1.0 并行，端到端验收依赖后端 Phase 2 落地*
