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

## 项目简介

qy-lty-admin 是「洛天依（Luotianyi）智能陪伴产品」生态的 **Web 管理后台前端**，基于 **Next.js 15 (App Router) + React 19 + TypeScript** 构建，搭配 Tailwind CSS + Radix UI / shadcn 风格组件。它通过 `/api/v1/admin/` 命名空间消费独立后端 [qy_lty](../qy_lty/)（Django）的 REST 接口，为运营者提供 AI / 内容（服饰/道具/家居/食物/歌曲/舞蹈/成就/好感度）/ 用户与权限 / 系统设置等模块的管理能力。

## 核心价值

**运营者能基于真实角色权限，安全且无障碍地管理后端各业务模块的数据**——`lib/permissions.ts` 中的 RBAC 矩阵 + `qy_lty` 后端服务端校验必须始终配套生效。一旦权限校验链路断裂（前端伪造角色或后端漏校验），整个管理后台就从"运营工具"退化为"任意操作面板"，其余所有 UI / UX 优化都无法弥补这种安全风险。

## 本期 Milestone：v1.0 通用凭据槽位前端集成

**启动日期**：2026-05-07
**联动**：与 qy_lty 后端 Milestone v1.0「通用凭据槽位」并行启动；前端集成测试需等后端 Phase 2（管理端读写接口）落地后才能跑通端到端。
**目标**：在 `/ai-model` 大模型管理页面增加 APP ID + Access Token 录入/编辑窗口，调用后端 `/api/v1/admin/credential-slot/` 完成读写。

**目标能力**：
- API 客户端层：`lib/api/credential-slot.ts` 封装 GET/PUT + 类型 + 后端→前端适配器（沿用 `mapBackend*` 模式）
- 页面入口：在 `/ai-model` 页面合适位置加入"凭据槽位"按钮/卡片，点击打开编辑对话框
- 编辑对话框：`app_id` 明文预填、`access_token` 仅显示末 4 位脱敏掩码并提示"如需更新请重新输入"、`updated_at` 只读显示；表单 React Hook Form + Zod 校验非空；提交触发 PUT
- RBAC 收敛：在 `lib/permissions.ts` 新增 `credential-slot` 模块 key，分配给"超级管理员" + "AI模型管理员"两个角色；`/ai-model` 页面入口与对话框入口都用 `hasPermission()` 收敛
- 提交反馈：成功 Sonner toast，失败走 `lib/api/error-handler.ts` 统一错误处理

**关键约束**：
- API 契约由后端 v1.0 锁定（GET 返回脱敏 + PUT 全字段覆写），前端**不要**在响应中把脱敏掩码当作真值再回写 PUT；改为"留空保留旧值 / 重新输入才覆写"的表单语义，避免回写假值
- Token 体系不变：调用 `/api/v1/admin/credential-slot/` 走 admin token（与现有 `apiClient` 一致）
- 复用现有适配器约定：后端字段 snake_case，前端类型 camelCase（如 `updated_at` → `updatedAt`），通过 adapter 转换
- 修改记录：每个 phase 的代码改动**必须**追加到 `docs/修改记录.md` 顶部；与 qy_lty 同期 commit 互相引用条目

## 需求清单

### 已交付

<!-- 已交付且在生产链路上跑通的能力，从 .planning/codebase/ 推断 -->

**鉴权与会话（AUTH）**
- ✓ **AUTH-01** 邮箱 + 密码登录页（`app/login/page.tsx` + `lib/api/auth.ts:emailLogin`）— existing
- ✓ **AUTH-02** 注册 / 找回密码占位页（`app/register/`、`app/forgot-password/`）— existing
- ✓ **AUTH-03** Bearer token 拦截器自动注入（`lib/api/client.ts` 请求拦截器从 localStorage 读 `auth_token`）— existing
- ✓ **AUTH-04** 401 响应统一处理（响应拦截器清空 token + 重定向 `/login`）— existing
- ✓ **AUTH-05** Cookie 镜像 token（`js-cookie`，7 天有效期，供 `middleware.ts` 路由保护使用）— existing
- ✓ **AUTH-06** 退出登录调用后端 logout 接口并清空双存储 — existing

**RBAC 权限体系（PERM）**
- ✓ **PERM-01** 5 角色 × 13 模块的 `PERMISSION_MATRIX`（`lib/permissions.ts`）— existing
- ✓ **PERM-02** `hasPermission(module)` / `hasPathPermission(pathname)` / `getModuleFromPath()` 工具集 — existing
- ✓ **PERM-03** `DashboardShell` 挂载时按路径校验权限并渲染访问拒绝 UI（`components/dashboard-shell.tsx`）— existing
- ✓ **PERM-04** `Sidebar` 按角色过滤可见菜单项（`components/sidebar.tsx`）— existing
- ✓ **PERM-05** `middleware.ts` 在受保护路径前校验 cookie token 并重定向未鉴权访问 — existing

**仪表盘（DASH）**
- ✓ **DASH-01** 仪表盘首页（`app/page.tsx`）含 KPI 统计卡片、概览图表、最近活动 Feed — existing
- ✓ **DASH-02** Recharts 数据可视化集成 — existing

**AI 管理（AI）**
- ✓ **AI-01** AI 模型 / Bot 管理（`app/ai-model/page.tsx` + `lib/api/ai-models.ts`）— existing

**内容管理（CONT）**
- ✓ **CONT-01** 服饰模块 CRUD（列表 / 详情 / 编辑 / 创建对话框，`app/outfits/` + `lib/api/outfits.ts`）— existing
- ✓ **CONT-02** 道具模块 CRUD（`app/props/`）— existing
- ✓ **CONT-03** 家居装饰模块 CRUD（`app/home-decor/`）— existing
- ✓ **CONT-04** 食物模块 CRUD（`app/food/`）— existing
- ✓ **CONT-05** 歌曲模块 CRUD（`app/songs/` + `lib/api/songs.ts`）— existing
- ✓ **CONT-06** 舞蹈模块 CRUD（`app/dances/` + `lib/api/dances.ts`）— existing
- ✓ **CONT-07** 成就模块管理（`app/achievements/` + `lib/api/achievements.ts`）— existing
- ✓ **CONT-08** 好感度系统管理页（`app/affinity/page.tsx`，1005 行单文件，规则/等级/互动 三段）— existing
- ✓ **CONT-09** 后端响应到前端类型的适配器层（`lib/api/adapters.ts` + 各模块 `mapBackend*` 函数）— existing

**系统管理（SYS）**
- ✓ **SYS-01** 用户管理模块（`app/users/` + `lib/api/users.ts`）— existing
- ✓ **SYS-02** 权限/角色管理模块（`app/permissions/` + `lib/api/roles.ts`）— existing
- ✓ **SYS-03** 系统设置页（`app/settings/`）— existing

**文件上传（UPL）**
- ✓ **UPL-01** 后端代理上传接口封装（`lib/api/upload.ts`，支持 image / avatar / audio / animation 类型 + 大小限制）— existing
- ✓ **UPL-02** 上传进度回调（Axios `onUploadProgress`）— existing

**通用 UI 基础设施（UI）**
- ✓ **UI-01** shadcn 风格原子组件库（`components/ui/`，30+ 组件，复制粘贴模式可直接修改）— existing
- ✓ **UI-02** 表单层（React Hook Form + Zod + `@hookform/resolvers`）— existing
- ✓ **UI-03** Toast 通知（Sonner + Radix Toast，封装为 `hooks/use-toast.ts`）— existing
- ✓ **UI-04** 暗黑/明亮主题切换（`next-themes` + Tailwind CSS 变量）— existing
- ✓ **UI-05** 移动端断点检测 hook（`hooks/use-mobile.tsx`）— existing
- ✓ **UI-06** 删除 / 发布二次确认对话框（`components/delete-confirmation-dialog.tsx`、`publish-confirmation-dialog.tsx`）— existing

**部署（DEP）**
- ✓ **DEP-01** Docker 多阶段构建（builder + runner，runner 镜像 < 200MB）— existing
- ✓ **DEP-02** Next.js standalone 输出（`.next/standalone` + `public/`）— existing
- ✓ **DEP-03** Yarn + 淘宝镜像源（`registry.npmmirror.com`，仅 Dockerfile 内）— existing
- ✓ **DEP-04** 端口 3000 暴露 + `yarn start` 入口 — existing

### 进行中

<!-- 当前正在建设的目标。GSD 通过 phase 推动这一段；移到 Validated 才算完成 -->

**Milestone v1.0 通用凭据槽位前端集成**（启动 2026-05-07）：

- [ ] **CRED-FE-01** API 客户端 `lib/api/credential-slot.ts`：导出 `getCredentialSlot()` / `updateCredentialSlot(payload)`，含响应适配器 `mapBackendCredentialSlot()`、共享类型定义 `CredentialSlot`
- [ ] **CRED-FE-02** RBAC 模块声明：`lib/permissions.ts` 加入 `credential-slot` 模块 key；`PERMISSION_MATRIX` 中分配给"超级管理员"和"AI模型管理员"
- [ ] **CRED-FE-03** `/ai-model` 页面入口：在合适位置渲染"凭据槽位"按钮/卡片，仅当 `hasPermission('credential-slot')` 为 true 时可见
- [ ] **CRED-FE-04** 编辑对话框组件 `components/ai-model/CredentialSlotDialog.tsx`：基于 `components/ui/dialog.tsx`，React Hook Form + Zod 校验（app_id / access_token 非空）；预填态 `access_token` 显示脱敏掩码 + 提示重输；提交触发 PUT；空字段语义"留空保留旧值"避免回写脱敏假值
- [ ] **CRED-FE-05** 提交反馈：成功 Sonner toast（`useToast`）+ 失败走 `lib/api/error-handler.ts` 统一 toast 错误信息；对话框成功后自动关闭并刷新预填数据

### 范围外

<!-- 明确排除项 + 理由。防止后续被无意识地拉回来 -->

- **后端实现** — 在独立项目 [qy_lty](../qy_lty/) 中维护（Django + DRF + Channels）。本仓库**只**作为消费方调用 `/api/v1/admin/` REST 接口，不编写任何服务端代码。
- **Unity 客户端业务逻辑** — 在 `C:\Unity2022project\LTY_App_Project_URP`（手机 App）和 `C:\Unity2022project\LTY_Project`（设备端）独立维护。本管理后台不与 Unity 客户端直接通信。
- **跨项目混合修改记录** — 服务端 / 管理后台改动**各自记录**到对方仓库的 `docs/修改记录.md`，跨项目联动**两端各写一条互相引用**，不混在同一条记录里（CLAUDE.md 显式规定）。
- **国际化（i18n）** — UI 文案当前硬编码中文，未引入 i18n 库；管理后台用户群体仅运营人员，不在本周期范畴。
- **移动端原生体验** — 仅响应式 Web；移动 App 在 `LTY_App_Project_URP` 独立维护。
- **真正意义的测试套件** — 当前未配置 Jest/Vitest，无任何测试文件。CONCERNS.md 标为 MEDIUM 工程债，需独立 milestone 系统性补齐。
- **客户端错误追踪服务（如 Sentry）** — 仅依赖 Sonner toast + console，未引入第三方监控；如需要再开 milestone 评估。

## 背景上下文

**生态位**：本项目是「设备—App—管理端」三角中的 **管理端** 节点，是运营者操作 qy_lty 后端业务数据的唯一图形化入口。

**对接服务清单**（以 CLAUDE.md 为准）：

| 服务 | 路径 | 通讯方式 |
|------|------|---------|
| qy_lty 后端 Django | `../qy_lty/`（`C:\Users\admin\Desktop\Lila-Server\qy_lty`） | HTTP REST `/api/v1/admin/`、`/card/category/*`、`/music/`、`/dances/`、`/affinity/`、`/common/upload/` 等 |

**最近活跃工作**（git log + 修改记录推断）：
- 2026-04 ~ 至今：好感度系统管理页（`affinity/page.tsx`，1005 行）建设完成，与 qy_lty 后端 P1 数据层联动
- 2026-05-07：完成 brownfield 代码库映射（commit `a85b6a7`）
- 持续在内容模块（outfits / songs / dances 等）补充 CRUD 表单与详情视图

**已知工程现状**（详见 `.planning/codebase/`）：
- 整体规模约 27K LOC TypeScript / TSX
- 测试覆盖**零**（无 Jest/Vitest 配置，无任何测试文件）— 是最大的工程债
- `next.config.mjs` 配置 `eslint.ignoreDuringBuilds: true` 与 `typescript.ignoreBuildErrors: true` — 构建期不阻断错误
- 多锁文件并存（`package-lock.json` + `pnpm-lock.yaml` + `yarn.lock`）— 本地与 Docker 容易拉到不同版本
- `lib/api/client.ts` 含大量 `console.log/warn/error` 调试残留，会暴露 token 前缀
- 部分依赖钉为 `"latest"`（`@hookform/resolvers`、`react-hook-form`、`recharts`、`zod`）— 升级不可控
- `app/affinity/page.tsx`（1005 行）、`components/ui/sidebar.tsx`（763 行）等单文件偏大，需要拆分
- 权限校验**仅在客户端**（localStorage.user_role 可篡改）— 后端必须重检每个 admin 接口才能闭环
- Token 存于 localStorage（XSS 暴露面）+ 7 天有效期不轮换 — CONCERNS.md 标 HIGH

## 约束

- **技术栈**：Next.js 15.2.4（App Router）+ React 19 + TypeScript 5 — 已锁定，迁移成本高
- **技术栈**：Tailwind CSS 3.4 + Radix UI + shadcn 风格组件 — 不替换为其他设计系统
- **运行时**：Node.js 22.10.0 Alpine（Docker 镜像锁定，CI/Local 应保持版本一致）
- **HTTP 客户端**：Axios（已配置请求/响应拦截器） — 不改用 fetch / SWR / React Query（避免重写大量适配器）
- **后端契约**：所有请求走 `NEXT_PUBLIC_API_BASE_URL + /api/v1/admin/...`；响应结构 `{ success, code, message, data }`（由 qy_lty 的 `StandardResponseMiddleware` 保证）— 整体壳层不可改
- **Token 协议**：`Authorization: Bearer {token}`，token 同时存 localStorage（API 客户端读取）与 cookie（middleware 读取）— 双存储必须保持同步
- **角色命名**：5 个角色（超级管理员 / 内容管理员 / AI模型管理员 / 卡牌管理员 / 查看者 / 管理员），中文名硬编码，与 qy_lty 后端必须一致
- **包管理器**：项目并存三种 lockfile，**Dockerfile 用 yarn**，本地任选其一但**不要混用**（CLAUDE.md 警告）
- **文档规范**：每次代码改动**必须**追加到 `docs/修改记录.md` 顶部（CLAUDE.md 强制规则，结构性文档变更同样适用）
- **沟通语言**：与用户沟通使用中文（CLAUDE.md「沟通语言」节，只能因用户显式要求才切换）
- **项目独立**：`qy-lty-admin` 与 `qy_lty` 独立维护，**修改记录、planning 工件不混合**
- **跨仓库联动**：任何 `/api/v1/admin/` 接口契约改动**两端各写一条 `docs/修改记录.md` 并互相引用**

## 关键决策

| 决策 | 理由 | 结果 |
|------|------|------|
| 权限矩阵硬编码在前端（`lib/permissions.ts`） | 5 角色固定、变更频率低，不必引入额外配置中心 | ⚠️ Revisit — 客户端权限**仅是 UI 礼貌**；后端必须独立校验。CONCERNS.md 标"信任边界违规"为极高优先级，需明文写进 CLAUDE.md |
| Token 同时写 localStorage + cookie | API 客户端需 JS 可读，middleware 需 SSR 可读 | ⚠️ Revisit — 双存储扩大了 XSS 暴露面，理想形态是后端 HttpOnly cookie；当前形态是与后端契约的妥协 |
| shadcn 风格复制粘贴组件（不是 npm 包） | 可直接修改源码而不被 npm 升级覆盖 | ✓ Good — 已稳定使用，30+ 组件 |
| Axios + 拦截器（不是 fetch / SWR / React Query） | 拦截器统一处理 token 注入与 401，迁移到 hook 形态会重写大量代码 | ✓ Good — 当前形态稳定 |
| Next.js standalone 输出（Docker） | 镜像体积约 100MB（vs 标准 500MB） | ✓ Good — Dockerfile 已正确处理 public + next.config.mjs 复制 |
| 构建期忽略 TS / ESLint 错误（`ignoreBuildErrors: true`） | 早期开发不阻断构建 | ⚠️ Revisit — 应在生产配置中关闭并修干净；属技术债 |
| API 适配器层（`mapBackend*`） | 后端字段命名与前端类型契约不一致，集中映射避免散落 | ✓ Good — 已成为约定，新模块沿用即可 |
| `.planning/` 锚定在 `qy-lty-admin\`（不是 `Lila-Server\`） | `qy_lty` 与 `qy-lty-admin` 是独立项目，CLAUDE.md 规定各自维护 | ✓ Good — 2026-05-07 通过预创建空目录强制锚定生效 |

## 演进规则

本文档在 phase 切换与 milestone 边界处更新。

**每次 phase 切换后**（通过 `/gsd-transition`）：
1. 需求被推翻？→ 移到「范围外」并说明理由
2. 需求已交付？→ 移到「已交付」并标注 phase 引用
3. 出现新需求？→ 加到「进行中」
4. 有决策需要记录？→ 加到「关键决策」
5. 「项目简介」是否仍然准确？→ 如有偏移则更新

**每个 milestone 完成后**（通过 `/gsd-complete-milestone`）：
1. 全面回顾所有章节
2. 复核「核心价值」—— 是否仍是当前最高优先级？
3. 审视「范围外」—— 排除理由是否仍然成立？
4. 用当前状态更新「背景上下文」

---
*最后更新：2026-05-07，启动 Milestone v1.0「通用凭据槽位前端集成」（联动 qy_lty 后端 v1.0）*
