## Work Log - 日记管理按 quiz 表与逻辑重构(2026-04-23) - 背景:按“用老工程表,参考老工程逻辑改造当前工程日记管理功能”要求,将 `fquiz` 的 `/admin/diary` 从系统日志复用页切换为独立 Diary 模块。 - 本次改动(最小闭环): - 后端模型与接口落地(对齐 quiz 口径): - `api/app/models/diary.py` - 新增主表 `diary`,字段: - `id/title/content/diary_date/mood/weather/archived/create_date/create_user/update_date/update_user` - 索引:`idx_diary_create_user/idx_diary_diary_date/idx_diary_mood/idx_diary_archived`。 - `api/app/schemas/diary.py` - 新增 `DiaryMood` 枚举:`HAPPY/CALM/SAD/ANGRY/TIRED/EXCITED`。 - 新增 `DiaryQueryRequest/DiaryCreateRequest/DiaryUpdateRequest/DiarySummary/DiaryPageResponse`。 - `api/app/services/diary_service.py` - 新增 create/search/get/update/delete/archive 逻辑。 - 查询逻辑对齐 quiz:按 `create_user` 隔离数据,支持 `title/mood/diary_date_start/diary_date_end/archived` 过滤,排序 `diary_date DESC, create_date DESC`。 - 归档逻辑对齐 quiz:`archive` 仅修改 `archived` 状态,保留原记录。 - `api/app/api/v1/diary.py` - 新增接口: - `POST /api/v1/diary/search` - `GET /api/v1/diary/get/{id}` - `POST /api/v1/diary/create` - `PUT /api/v1/diary/update` - `DELETE /api/v1/diary/delete/{id}` - `POST /api/v1/diary/{id}/archive?archived=...` - 权限口径: - 读:`menu.read | menu.manage` - 写:`menu.manage` - 路由与建表注册: - `api/app/api/router.py` 挂载 `diary` 路由。 - `api/app/models/__init__.py`、`api/app/core/database.py` 注册 `diary` 模型,确保 `create_all` 生效。 - 前端页面改造(替换 syslog 复用): - `web/src/app/admin/diary/page.tsx` - 改为独立 Diary 页面: - 列表分页 - 条件查询(标题、心情、日期范围、归档状态) - 新增 / 编辑 / 删除 - 归档 / 取消归档 - 详情弹窗 - API 调整到新后端接口 `/api/v1/diary/*`。 - `web/src/types/auth.ts` - 新增 `DiaryMood/DiarySummary/DiaryListResponse` 类型。 - `web/src/app/admin/page.tsx` - 首页卡片文案从“上帝视角”调整为“日记管理”。 - `api/app/services/seed_service.py` - 菜单 `admin.diary` 名称更新为“日记管理”(路径保持 `/admin/diary`)。 - 验证: - `python3 -m py_compile api/app/models/diary.py api/app/schemas/diary.py api/app/services/diary_service.py api/app/api/v1/diary.py api/app/api/router.py api/app/models/__init__.py api/app/core/database.py api/app/services/seed_service.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - 前端 ESLint 未完成(环境依赖缺失): - `npm run lint` 提示 `eslint: not found`; - 直接执行 eslint 后提示缺少 `typescript` 依赖(`Cannot find module 'typescript'`)。 - 风险与影响: - `admin.diary` 保留 `menu.read/menu.manage` 权限体系(未引入独立 `diary.read/diary.manage`),属于兼容方案;后续若需要更细权限边界,可单独拆分权限码。 - 本次仅对齐了老工程 diary 主链路,不包含分组/tag 维度扩展(老工程该模块本身也未强依赖)。 ## Work Log - 登录链路切换为 quiz 老表与老逻辑兼容(2026-04-23) - 背景:按“改用老工程 `/root/.openclaw/workspace/quiz` 中的表,参照老工程登录逻辑改造”要求,当前轮以“登录可用 + 会话鉴权可用 + 菜单可拉取”为最小闭环。 - 本次改动(最小闭环): - 登录请求契约切换: - `api/app/schemas/auth.py` - `LoginRequest` 从 `email + password` 改为 `user_id + password`(密码最小长度放宽为 1,兼容老库短密码)。 - `web/src/components/auth-provider.tsx` - `login(...)` 传参改为 `user_id + password`,请求体同步改为 `{ user_id, password }`。 - `web/src/app/page.tsx` - 登录表单输入从 `Email` 改为 `User ID`,记住密码缓存字段同步改为 `userId`。 - 密码与状态兼容: - `api/app/core/security.py` - `verify_password` 增加 `BCrypt` 兼容分支(老工程口令),保留现有 `Argon2` 支持。 - `api/app/services/legacy_authz_service.py`(新增) - 新增用户状态归一:`ENABLED/ACTIVE/1/TRUE -> active`,`DISABLED/... -> disabled`。 - `api/app/models/user.py` - 用户默认状态改为 `ENABLED`;默认主键改为 `uuid4().hex`(32 位,兼容老表长度习惯)。 - 旧表角色/权限装配兼容层(核心): - `api/app/services/legacy_authz_service.py`(新增) - 从老表链路读取授权信息: - `user_role_rela -> user_role` 计算 `role_codes` - `role_menu_rela -> menu` 映射 `permission_codes` - 管理员识别兼容:`admin/sys_mgr/administrator` 或角色名含“管理员”,统一附加 `admin` 角色别名。 - 提供 `build_legacy_menu_tree(...)`,按老 `menu` 表生成 `/admin/me/menus` 所需菜单树。 - 后端鉴权链路接入兼容层: - `api/app/services/auth_service.py` - 登录改为按 `user_id` 查询。 - Access Token 的 `role_codes/permission_codes` 改为通过兼容层计算(不再依赖新 RBAC 表)。 - `api/app/core/dependencies.py` - `get_current_user` 改为用户基础信息 + 兼容层授权计算。 - `api/app/api/v1/ws.py` - WS 鉴权改为复用兼容层 `role_codes/permission_codes`。 - `api/app/services/jwt_generator_service.py` - JWT 生成与用户列表角色展示改为兼容层口径,状态过滤兼容 `ENABLED`。 - `api/app/services/user_service.py` - `serialize_user/queue_user_auth_refresh` 改为兼容层计算授权信息。 - 用户状态写入统一落库存储值 `ENABLED/DISABLED`。 - 用户角色更新改为直连老表 `user_role_rela` 写入(`rela_id + user_id + role_id`)。 - `api/app/services/admin_service.py` - `build_menu_tree` 优先走老表菜单树构建,失败时回退现有实现。 - 验证: - 后端编译: - `python3 -m py_compile api/app/services/legacy_authz_service.py api/app/core/security.py api/app/schemas/auth.py api/app/schemas/user.py api/app/services/auth_service.py api/app/core/dependencies.py api/app/services/user_service.py api/app/api/v1/ws.py api/app/services/jwt_generator_service.py api/app/services/admin_service.py api/app/models/user.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - 前端构建: - `npm run build:web` -> 失败(既有类型问题,非本轮新增): - `web/src/app/admin/layout.tsx:155`,`Card` 组件 `children` 类型不匹配(`ui-antd` 兼容层类型定义问题)。 - 风险与影响: - 本轮优先闭环“登录 + 鉴权 + 菜单树”,未对 `admin/roles`、`admin/menus` 全量 CRUD 全面切换到老表语义;这些接口仍存在新旧口径混用风险。 - 旧表无独立 `permissions` 表,当前 `permission_codes` 为“角色菜单到权限码”的兼容映射;若老库菜单编码与映射不一致,可能出现权限显隐偏差。 - 注册接口当前保留并做了缺省兜底,但老库角色初始化策略(如默认 `user` 角色)依赖目标库实际数据,需联调确认。 ## Work Log - 第二轮:角色/菜单管理切换 quiz 老表口径(2026-04-23) - 背景:按“继续做第二轮”要求,将后台角色/菜单管理从现有 RBAC ORM 表切换为 quiz 老表链路,并同步前后端 ID 类型契约。 - 本次改动(最小闭环): - 后端接口与服务切换: - `api/app/services/legacy_admin_rbac_service.py` - 完成并启用老表 CRUD: - 角色:`user_role`、`user_role_rela`、`role_menu_rela` - 菜单:`menu`、`role_menu_rela` - 支持:`list/get/create/update/delete role`、`list/get/create/update/delete menu`、`list/replace role menus`、`list permissions`。 - 保护项兼容:保留受保护角色与菜单编码删除拦截。 - 修复更新边界:`menu.parent_id` 支持显式清空;角色/菜单名称做 `strip` 空值校验。 - `api/app/api/v1/admin.py` - 角色/菜单相关路由改为调用 `legacy_admin_rbac_service`。 - 角色/菜单 path 参数改为字符串:`role_id: str`、`menu_id: str`。 - `GET /admin/roles/{role_id}/menus` 返回改为 `list[str]`。 - `api/app/schemas/admin.py` - 角色/菜单契约改为字符串 ID: - `RolePublic.id`、`RolePublic.menu_ids` - `RoleCreateRequest.menu_ids`、`RoleUpdateRequest.menu_ids` - `MenuPublic.id`、`MenuPublic.parent_id` - `MenuCreateRequest.parent_id`、`MenuUpdateRequest.parent_id` - `RoleMenuUpdateRequest.menu_ids` - 鉴权菜单树 ID 口径对齐: - `api/app/services/legacy_authz_service.py` - `build_legacy_menu_tree` 改为直接使用老表 `menu.menu_id/parent_id` 作为菜单树 ID,不再生成临时递增 ID。 - 前端类型与页面对齐: - `web/src/types/auth.ts` - `RoleItem.id/menu_ids`、`MenuItem.id/parent_id` 改为字符串类型。 - `web/src/app/admin/roles/page.tsx` - 角色编辑状态与 `menu_ids` 改为字符串口径。 - `web/src/app/admin/menus/page.tsx` - 菜单编辑状态与 `parent_id` 改为字符串口径; - ID 排序改为兼容字符串(纯数字优先数值比较,否则字典序)。 - `web/src/app/admin/layout.tsx` - 菜单路径项 `id` 改为字符串。 - `web/src/app/admin/users/page.tsx` - 角色兜底项 `id` 改为字符串(`fallback-*`)。 - 验证: - 后端编译: - `python3 -m py_compile api/app/schemas/admin.py api/app/api/v1/admin.py api/app/services/legacy_admin_rbac_service.py api/app/services/legacy_authz_service.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - 前端构建: - `npm run build:web` -> 失败,仍为既有问题: - `web/src/app/admin/layout.tsx:155`,`Card` 组件 `children` 类型不匹配(`ui-antd` 兼容层类型定义问题,非本轮新增)。 - 风险与影响: - 老表 `menu` 字段语义与当前前端展示字段并非一一对应(如 `component/cacheable`),当前仍为兼容映射;如需完全对齐 quiz UI 语义,后续需补充映射规则。 - `permissions` 仍是基于菜单编码映射的兼容推导;若线上老表菜单编码偏差,权限显示与鉴权可能出现边缘不一致,需要联调验证。 ## Work Log - 第三轮:前端构建门禁打通(2026-04-23) - 背景:第二轮后 `npm run build:web` 仍被前端类型问题阻断,影响发布门禁。 - 本次改动(最小闭环): - `antd Card` 类型兼容修复: - `web/src/components/ui-antd.tsx` - `Card` 包装器改用 `antd` 官方 `CardProps` 类型,不再依赖 `ComponentProps` 推导。 - 对 `AntCard` 做显式可渲染组件收敛(`AntCardComponent`),避免 `JSX element type 'AntCard' does not have construct or call signatures`。 - 页面层统一复用兼容层 `Card`: - `web/src/app/admin/mermaid-mgr/_components/mermaid-editor.tsx` - `web/src/app/admin/mindmap/_components/mindmap-editor.tsx` - 从 `antd` 直引 `Card` 改为 `@/components/ui-antd` 的 `Card`。 - 严格 TS 隐式 any 清理(与现有 strict 配置对齐): - `web/src/app/admin/mermaid-mgr/_components/mermaid-editor.tsx` - `web/src/app/admin/mermaid-mgr/page.tsx` - `web/src/app/admin/mindmap/_components/mindmap-editor.tsx` - `web/src/app/admin/schedule/page.tsx` - `web/src/app/admin/todos/page.tsx` - 补全 `onChange/onClick/onFinish/showTotal/footer` 等回调参数类型标注,消除 `noImplicitAny` 阻断。 - 验证: - `npm run build:web` -> 通过(TypeScript 检查通过 + 54 个页面静态生成完成)。 - 风险与影响: - 本轮仅做类型与组件兼容修复,未改业务逻辑与接口契约。 - 后续若继续在 `web/src/app/admin/**` 新增 AntD 回调,仍需显式参数类型以避免 `strict` 下再次触发 `implicit any`。 ## Work Log - 第四轮:类型门禁巡检与基线固化(2026-04-23) - 背景:第三轮已打通 `build:web`,第四轮目标是做“全量巡检”防止同类问题回归。 - 本次动作: - 代码扫描: - `web/src` 内 `import { Card } from "antd"`(避免回到不稳定类型入口); - `onFinish/onChange/onClick/onPressEnter` 中常见未标注参数模式(`event/value/values/total/_`)。 - 类型基线检查: - `npm --workspace web exec tsc --noEmit --pretty false`。 - 结果: - 扫描未发现新的高风险命中。 - `tsc --noEmit` 全量通过,`web` 当前类型门禁基线稳定。 - 影响: - 第四轮未新增业务逻辑改动,属于稳定性巡检与基线确认。 ## Work Log - 第五轮:后端联调冒烟与运行时修复(2026-04-23) - 背景:按“继续第五轮”要求,对登录与后台角色/菜单主链路做联调冒烟,验证第二轮老表改造在运行容器中的真实表现。 - 本次动作与发现: - 容器代码加载校验: - 初次冒烟发现运行中的 `api` 仍走旧登录契约(`email + password`),因此执行 `docker compose up --build -d api` 重新加载最新代码。 - 运行时阻断修复 1(启动失败): - 重建后 `api` 启动报错:`ModuleNotFoundError: No module named 'bcrypt'`。 - 处理:`api/requirements.txt` 新增 `bcrypt==4.2.1`,重建 `api` 后恢复健康。 - 运行时阻断修复 2(`/admin/me/menus` 500): - 冒烟命中 `500`,日志定位到 `legacy_authz_service.build_legacy_menu_tree` 使用了未定义变量 `node_id`。 - 处理:`api/app/services/legacy_authz_service.py` 将 `nodes[node_id]` 修正为 `nodes[legacy_id]`,重建后恢复。 - 冒烟验证(只读,不写库): - 登录契约校验: - `POST /api/v1/auth/login` 传 `{user_id, password}` -> 401(凭证错误,说明契约已生效); - 传 `{email, password}` -> 422(缺少 `user_id`)。 - 管理后台主链路: - `GET /api/v1/admin/roles` -> 200,`RolePublic.id/menu_ids` 为字符串; - `GET /api/v1/admin/menus` -> 200,`MenuPublic.id/parent_id` 为字符串口径; - `GET /api/v1/admin/me/menus` -> 200,菜单树根节点 `id` 为字符串; - `GET /api/v1/admin/roles/sys_mgr/menus` -> 200,`menu_ids` 为字符串数组; - `GET /api/v1/admin/permissions` -> 200。 - 相关文件: - `api/requirements.txt` - `api/app/services/legacy_authz_service.py` - 风险与影响: - 本轮未执行角色/菜单写接口(create/update/delete),避免对外部数据库产生变更;当前验证覆盖“登录契约 + 权限读取 + 菜单读取”读路径。 ## Work Log - 第六轮:修复日程页 Modal.footer 类型不兼容(2026-04-23) - 背景:`docker compose build web` 在 `web/src/app/admin/schedule/page.tsx:747` 失败,报错为 `Modal.footer` 回调签名不匹配(`OkBtn/CancelBtn` 被错误标注为 `() => ReactElement`)。 - 本次改动(最小闭环): - `web/src/app/admin/schedule/page.tsx` - 补充 React 类型导入:`FC`、`ReactNode`。 - `Modal.footer` 回调类型从: - `_origin: unknown` - `{ OkBtn: () => ReactElement; CancelBtn: () => ReactElement }` 调整为: - `_origin: ReactNode` - `{ OkBtn: FC; CancelBtn: FC }` - 保持现有按钮渲染与删除/保存逻辑不变,仅修复类型契约。 - 验证: - `npm run build:web` -> 通过(Next.js 编译、TypeScript 检查、静态页面生成全部完成)。 - 风险与影响: - 仅影响前端类型声明,不涉及接口契约、请求参数、业务分支和数据写入行为。 ## Work Log - 第七轮:后台系统菜单改用 AntD Menu 组件(2026-04-23) - 背景:当前后台左侧“系统菜单”是递归 `Button + Link`,移动端是 `DropdownMenu`。按要求切换为 Ant Design `Menu` 组件承载菜单树。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - 新增 `Menu as AntMenu` 与 `MenuProps` 引入。 - 移除递归渲染函数 `renderMenuNodes` 与移动端 `flattenMenuPaths` 下拉菜单构建逻辑。 - 新增 `buildMenuItems`:把后端返回的 `MenuTreeItem[]` 转为 `AntMenu` 的 `items` 结构(支持嵌套)。 - 新增 `collectSubmenuKeys`:收集所有带子节点菜单并展开,保持与旧实现一致的“默认全展开”体验。 - 新增 `findActiveMenuState`:基于 `pathname` 计算 `selectedKeys/openKeys`,保证当前路由高亮准确。 - 桌面端左侧“系统菜单”改为 `AntMenu mode=\"inline\"` 渲染。 - 移动端菜单入口改为在卡片内直接渲染 `AntMenu mode=\"inline\"`(不再使用下拉菜单承载)。 - 顶部“账号”菜单保持原 `DropdownMenu` 方案不变。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。 - 风险与影响: - 移动端菜单交互由“点击按钮弹出”改为“卡片内直接展示”,视觉高度会增加,但菜单可见性更高。 - 本次只调整菜单承载组件,不涉及菜单接口、权限判断和路由结构变更。 ## Work Log - 第八轮:菜单导航切回左侧布局(2026-04-23) - 背景:收到“menu 导航放到左侧”的要求,当前后台壳层为顶部横向 `Menu`,与期望不一致。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - 移除顶部横向 `AntMenu(mode=\"horizontal\")` 导航区。 - 新增左侧栏布局(`md` 及以上):`AntMenu(mode=\"inline\")` 承载后台菜单树。 - 移动端菜单改为左侧 `Drawer`(`placement=\"left\"`)承载 `AntMenu`,顶部仅保留“菜单”按钮触发抽屉。 - 保留现有菜单数据来源(`/api/v1/admin/me/menus`)、路由高亮逻辑(`selectedKeys`)与账号区交互不变。 - 菜单展开状态统一为 `menuOpenKeys`,桌面侧栏与移动抽屉共用,保证一致性。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。 - 风险与影响: - 布局从“顶部导航”改为“左侧导航”,横向空间分配会变化,但仅影响后台壳层,不涉及页面业务逻辑与接口契约。 ## Work Log - 第八轮:后台壳层改为顶部固定导航(2026-04-23) - 背景:按“参考 Ant Design 文档站顶部固定导航(左侧 Logo + 主导航)”要求,重构当前后台壳层布局。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - 布局从“左侧固定菜单 + 内容头卡片”调整为“顶部固定导航 + 内容区”。 - 顶栏结构改为:左侧品牌 Logo(`Q + fquiz`)+ 中部主导航(`AntMenu mode=horizontal`)+ 右侧账号区。 - 保留现有菜单数据链路:继续通过 `/api/v1/admin/me/menus` 拉取并渲染树形菜单。 - 保留路由高亮逻辑,并补充 `openKeys` 计算用于移动端内嵌菜单展开。 - 新增移动端菜单折叠入口(`菜单/收起菜单`),避免小屏导航不可达。 - 保留 `menuError` 提示、登录态判断与账号退出逻辑,不改接口契约。 - 验证: - `npm run build:web` -> 通过(Next.js 编译、TypeScript 检查、静态页面生成全部完成)。 - 风险与影响: - 桌面端导航入口从左侧改为顶部,用户需要适应新的操作路径。 - 当顶级菜单数量较多时,水平菜单会进入 AntD 的溢出折叠(`...`)交互,属于组件默认行为。 ## Work Log - 第九轮:84 库用户/角色/菜单数据导出并初始化本地库(2026-04-23) - 背景:按“查询 84 的用户表、角色表、菜单表、菜单角色关系数据,然后初始化本地库”要求执行。 - 本次动作(最小闭环): - 远端 84 库(`223.109.142.84:5432/postgres`)连通与数据核验: - 表存在:`users / user_role / menu / role_menu_rela`。 - 行数:`users=5`、`user_role=6`、`menu=64`、`role_menu_rela=123`。 - 数据导出留档: - CSV 导出文件: - `tmp/84-export/users_84.csv` - `tmp/84-export/user_role_84.csv` - `tmp/84-export/menu_84.csv` - `tmp/84-export/role_menu_rela_84.csv` - 初始化脚本导出: - `tmp/84-export/legacy_auth_schema.sql` - `tmp/84-export/legacy_auth_data.sql` - `tmp/84-export/legacy_auth_data_wrapped.sql` - 本地库初始化: - 目标库:本地 `fquiz-db` 的 `postgres` 数据库(用户 `fquiz`)。 - 先 `DROP TABLE IF EXISTS public.role_menu_rela, public.menu, public.user_role, public.users CASCADE`。 - 回放 schema + data 完成建表和数据导入。 - 由于 `menu.parent_id` 自关联外键,导入数据时在同一会话启用 `session_replication_role=replica` 后回放,导入成功。 - 验证: - 远端与本地(postgres)按表对比 `count + md5(signature)` 全量一致: - `users`: `5` / `a50fedde66f156b0442d792b42c355b7` - `user_role`: `6` / `7fb7d2520f44efe25e8be15762a3bd3d` - `menu`: `64` / `094b533fe91b853868a0f9e4356da49a` - `role_menu_rela`: `123` / `3c752bb95375e80d835b198831d44535` - 风险与影响: - 本次初始化目标是本地 `postgres` 库,不影响本地 `fquiz` 库现有表数据。 - `users` 表为老工程结构(`user_id/user_name/password/state...`),与新表结构不同;后续若切回新 RBAC 表需区分数据库与表口径。 ## Work Log - 第十轮:数据库默认目标切换到本地并修复启动兼容(2026-04-23) - 背景:按“现在切到本地库,不用84了”要求,将默认数据库目标从 `223.109.142.84` 切到本地 `fquiz-db`。 - 本次改动(最小闭环): - `docker-compose.yml` - `db` 服务移除 `local-db` profile,改为默认启动。 - `api` 新增 `depends_on: db (service_healthy)`。 - `api` 的默认 DB 参数切到本地: - `DB_HOST=db` - `DB_PORT=5432` - `DB_NAME=postgres` - `DB_USERNAME=fquiz` - `DB_PASSWORD=fquiz` - `api/app/core/config.py` - 非 Docker 默认 DB 参数改为本机本地库: - `db_host=127.0.0.1` - `db_port=5433` - `db_name=postgres` - `db_username=fquiz` - `db_password=fquiz` - `.env.example` - 同步改为本机本地库默认值(不再出现 84 默认地址)。 - `api/app/services/seed_service.py` - `_seed_initial_admin` 中初始管理员状态由 `active` 调整为 `ENABLED`,兼容老 `users.state` 约束,解决本地库启动时 `users_state_check` 报错。 - 验证: - `python3 -m py_compile api/app/services/seed_service.py` -> 通过。 - `docker compose up -d --build api` -> 通过,`fquiz-api` 正常启动。 - `docker compose ps` -> `api/db/web` 全部 `healthy/up`。 - `GET /health` -> 200。 - `POST /api/v1/auth/login`(错误密码探测)-> 401(说明 API 可用且查询链路正常)。 - 风险与影响: - 默认目标已改为本地库;若后续要临时连外部库,需显式设置 `DATABASE_URL` 或覆盖 `DB_HOST` 等环境变量。 ## Work Log - 第十一轮:后台菜单改为左侧内嵌并支持右上角显隐(2026-04-23) - 背景:按“改成内嵌菜单,放在页面左侧,通过右上角菜单按钮隐藏/显示”要求调整后台壳层交互。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - 移除移动端 `Drawer` 侧滑菜单方案,统一为页面内嵌左侧菜单。 - 新增状态 `menuVisible`,默认 `true`,控制左侧菜单区域显示/隐藏。 - 顶栏右上角“菜单”按钮改为全端可见,并切换文案: - 菜单显示时:`隐藏菜单` - 菜单隐藏时:`显示菜单` - 内容区网格列根据 `menuVisible` 动态切换: - 显示:`md:grid-cols-[280px_minmax(0,1fr)]` - 隐藏:`md:grid-cols-1` - 左侧菜单保留 `AntMenu inline` + `openKeys/selectedKeys` 逻辑,保持当前路由高亮与展开行为。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `next build` 多次在当前环境命中 `.next` 产物文件 `ENOENT`(非本次业务逻辑错误): - Turbopack 路径:`_buildManifest.js.tmp` 丢失; - Webpack 路径:`edge-runtime-webpack.js` copyfile 丢失。 - 风险与影响: - 本次仅改后台壳层菜单交互,不涉及 API 契约和业务数据写入。 - 当前构建环境存在 `.next` 产物落盘异常,影响 `build:web` 门禁稳定性(与菜单逻辑无直接耦合)。 ## Work Log - 第十二轮:支持主题色切换并持久化(2026-04-23) - 背景:按“支持主题色切换”要求,增加可视化主题色切换入口,并要求刷新后保持。 - 本次改动(最小闭环): - `web/src/components/ui-antd.tsx` - 新增主题外观上下文 `ThemeAppearanceContext`。 - 新增 `useThemeAppearance()`,对外暴露: - `accentColor` - `setAccentColor(...)` - 新增 `THEME_ACCENT_OPTIONS`(靛蓝/蓝色/青色/绿色/橙色/红色/粉色/紫色)。 - `Theme` 组件增加主题色状态与持久化: - 启动时读取 `localStorage["fquiz:theme:accent-color"]` - 切换后写回 localStorage - 切换即更新 AntD `ConfigProvider` 的 `colorPrimary`,全局生效。 - `web/src/app/admin/layout.tsx` - 顶栏右侧新增主题色选择器(`Select`),使用 `THEME_ACCENT_OPTIONS` 渲染。 - 选择器值绑定 `useThemeAppearance().accentColor`,变更时调用 `setAccentColor`。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。 - 风险与影响: - 主题色选择属于前端 UI 层改动,不影响后端接口与数据模型。 ## Work Log - 登录页怪兽主视觉改为“毛怪 + 大眼仔”双角色(2026-04-23) - 背景:按“把登录页面的怪兽换成毛怪和大眼仔”要求,保留登录/注册链路不变,仅重做首页登录视觉。 - 本次改动(最小闭环): - `web/src/app/page.tsx` - 保留 `useAuth` 登录/注册/退出与表单提交流程不变。 - 将单怪兽舞台替换为双角色舞台:`sulley + mike`。 - 保留“眼睛跟随鼠标”交互,并新增大眼仔眼球偏移参数(`MIKE_GAZE_MAX_X/Y`)。 - 保留“密码输入时挪开视线”交互:毛怪转头避视、大眼仔轻微眯眼。 - 替换旧 `.monster-*` 样式为 `.duo-* / .sulley-* / .mike-*` 样式,维持响应式布局与背景动效。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。 - 风险与影响: - 本次为前端视觉层改动,不涉及 API 契约、鉴权逻辑或后端数据结构。 - 角色形象为页面内 CSS 卡通实现,不依赖外部图片资源。 ## Work Log - 第九轮:按 AntD 配色收口全局变量(2026-04-23) - 背景:当前前端虽然已切到 AntD 组件栈,但大量页面仍使用历史 `--gray-* / --accent-*` 变量与 Tailwind 类,导致视觉不完全跟随 AntD 主题色。 - 本次改动(最小闭环): - `web/src/components/ui-antd.tsx` - 在 `Theme` 内新增 `ThemeCssVarsScope`,通过 `antdTheme.useToken()` 把旧语义变量映射到 AntD token。 - 覆盖变量包括: - AntD 直连变量:`--ant-color-primary`、`--ant-color-bg-layout`、`--ant-color-border-secondary`、`--ant-color-text-secondary`、`--ant-color-text` - 旧语义变量:`--gray-*`、`--accent-*`、`--red-*`、`--green-*`、`--orange-*`、`--indigo-*`、`--color-panel-solid`、`--border` - 通过 `display: contents` 避免新增布局层级影响页面结构。 - `web/src/app/globals.css` - 新增上述变量的静态兜底值,避免主题注入前变量缺失。 - `body` 背景与文字颜色改为走 AntD 变量(`--ant-color-bg-layout`、`--ant-color-text`)。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。 - 风险与影响: - 本次不改接口与业务逻辑,仅调整前端主题变量层与全局视觉基线。 - 旧页面里保留的 `var(--gray/* --accent/*)` 写法将统一跟随 AntD token,后续可按需渐进式替换为原生 AntD token class/style。 ## Work Log - 第十轮:去掉前端 URL 的 `/admin` 前缀(2026-04-23) - 背景:希望用户侧访问地址不再出现 `/admin`,但保留现有 `app/admin/**` 页面实现与权限链路。 - 本次改动(最小闭环): - `web/src/middleware.ts`(新增) - 新地址(不含 `/admin`)统一 rewrite 到现有 `/admin/**` 页面路由。 - 旧地址 `/admin/**` 自动 redirect 到无前缀地址,兼容历史链接与书签。 - 根路径 `/`、`/api`、`/_next`、常见静态文件请求走 bypass,不参与改写。 - 约定:原 `/admin` 首页映射为 `/dashboard`。 - `web/src/app/admin/layout.tsx` - 菜单数据加载后统一把 `path` 从 `/admin/**` 规范化为无前缀地址(`/admin` -> `/dashboard`)。 - 顶部 Logo 跳转从 `/admin` 改为 `/dashboard`。 - `web/src/app/page.tsx` - 首页快捷入口改为无前缀地址:`/dashboard`、`/users`、`/requirements`。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过(含 Proxy/Middleware 生效,编译与静态生成完成)。 - 风险与影响: - 现有硬编码 `/admin/**` 链接仍可用,但会发生一次 30x 重定向到无前缀地址。 - Next.js 16 对 `middleware` 命名提示迁移到 `proxy`,当前功能正常;后续可按官方建议改名以消除提示。 ## Work Log - 第十轮:下线 8 个后台功能(2026-04-23) - 背景:按“删除生命倒计时、密钥管理、价格监控、历史答卷、诗词本、日记管理、家庭作业、试题管理功能”要求,执行最小闭环下线。 - 本次改动(最小闭环): - 菜单与权限口径收敛: - `api/app/services/seed_service.py` - 移除默认菜单: - `admin.life_countdown` - `admin.password` - `admin.token_usage` - `admin.history` - `admin.vocabulary` - `admin.diary` - `admin.homework` - `admin.question_bank` - `ROLE_MENU_BINDINGS["admin"]` 同步移除上述菜单。 - 移除默认权限 `life_countdown.read/manage`。 - `api/app/services/legacy_authz_service.py` - 将上述 8 个菜单加入 `DISABLED_MENU_CODES`,保证老库已有记录也不会再出现在 `/api/v1/admin/me/menus`。 - 权限映射移除对应菜单码;补充 `admin.job_mgr -> question_bank.read/manage`,确保“作业监控”权限链路不受影响。 - `api/app/services/legacy_admin_rbac_service.py` - 增加 `REMOVED_MENU_CODES` 过滤:菜单列表、角色菜单返回、权限推导、菜单可用性校验均排除已下线菜单。 - `api/app/services/admin_service.py` - 受保护菜单集合同步移除上述 8 个菜单码(兼容回退路径)。 - `web/src/app/admin/menus/page.tsx` - 前端受保护菜单集合同步移除上述 8 个菜单码。 - 后端接口下线: - `api/app/api/router.py` - 不再挂载 `diary_router` 与 `life_countdown_router`。 - `api/app/api/v1/admin.py` - 删除密钥管理专用接口: - `GET /api/v1/admin/password/models` - `GET /api/v1/admin/password/models/{model_id}/keys` - `POST /api/v1/admin/password/models/{model_id}/rotate-key` - 前端页面下线: - 删除路由页面: - `web/src/app/admin/life-countdown/page.tsx` - `web/src/app/admin/password/page.tsx` - `web/src/app/admin/price-monitor/page.tsx` - `web/src/app/admin/token-usage/page.tsx` - `web/src/app/admin/history/page.tsx` - `web/src/app/admin/poetry/page.tsx` - `web/src/app/admin/diary/page.tsx` - `web/src/app/admin/homework/page.tsx` - `web/src/app/admin/question-bank/page.tsx` - `web/src/app/admin/vocabulary/page.tsx` - `web/src/app/admin/page.tsx` 移除上述功能卡片入口。 - `web/src/app/admin/vocabulary-proficiency/page.tsx` 移除“进入诗词本”跳转按钮。 - 保留“作业监控”功能: - 将原 `question-bank` 页面实现迁移到 `web/src/app/admin/job/_components/job-question-bank-page.tsx`, - `web/src/app/admin/job/page.tsx` 改为引用该实现。 - 验证: - 后端语法校验: - `python3 -m py_compile api/app/services/seed_service.py api/app/services/legacy_authz_service.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py api/app/api/v1/admin.py api/app/api/router.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - 前端构建: - `npm run build:web` -> 通过。 - 输出路由确认已不再生成: - `/admin/life-countdown`、`/admin/password`、`/admin/price-monitor`、`/admin/token-usage`、`/admin/history`、`/admin/poetry`、`/admin/diary`、`/admin/homework`、`/admin/question-bank`、`/admin/vocabulary`。 - 风险与影响: - 本次下线为“菜单 + 路由 + 部分接口”闭环;`question_bank` 与 `vocabulary` 底层 API 仍保留,供“作业监控/分组管理/知识点管理/单词统计”等保留模块复用。 - 若线上老库仍有已下线菜单记录,当前会被服务层过滤,不再对外展示。 ## Work Log - 下线 8 个后台功能模块(2026-04-23) - 背景:按要求删除系统中的以下功能: - 微信小程序(`admin.wxapp`) - MD解析(`admin.mdresolve`) - 数据查询(`admin.data_query`) - 热搜(`admin.hot_search`) - 文件识别(`admin.filedetector`) - 百度网盘(`admin.baidu_pan`) - 分组管理(`admin.tag`) - 知识点管理(`admin.knowledge_point_mgr`) - 本次改动(最小闭环): - 前端:删除 8 个页面路由文件(访问即 404): - `web/src/app/admin/wxapp/page.tsx` - `web/src/app/admin/mdresolve/page.tsx` - `web/src/app/admin/data-query/page.tsx` - `web/src/app/admin/hot-search/page.tsx` - `web/src/app/admin/filedetector/page.tsx` - `web/src/app/admin/baidu-pan/page.tsx` - `web/src/app/admin/group/page.tsx` - `web/src/app/admin/knowledge/page.tsx` - 前端:移除后台首页入口卡片: - `web/src/app/admin/page.tsx` - 前端:菜单管理页取消上述 8 个菜单码的“受保护菜单”限制(允许删除历史残留菜单): - `web/src/app/admin/menus/page.tsx` - 后端:菜单树硬过滤(即使老库仍有菜单记录,也不会下发给前端导航): - `api/app/services/legacy_authz_service.py` - 新增 `DISABLED_MENU_CODES` 并在菜单树构建、权限推导时过滤。 - `api/app/services/admin_service.py` - 新增 `REMOVED_MENU_CODES` 并在 `list_menus/get_menu_by_id/get_menu_by_code/create_menu/build_menu_tree/_load_menus_by_ids` 过滤。 - `api/app/services/legacy_admin_rbac_service.py` - 将 8 个菜单码加入 `REMOVED_MENU_CODES`,统一影响菜单列表、角色菜单关联、权限推导。 - 后端:停用对应接口挂载与默认种子: - `api/app/api/router.py` - 移除 `hot_search_router` 与 `mdresolve_router` 挂载。 - `api/app/services/topic_registry.py` - 移除 `admin.hot_search` 与 `admin.hot_search.follow_topics` topic 规则。 - `api/app/services/seed_service.py` - 移除 8 个菜单定义与 admin 默认菜单绑定。 - 移除 `seed_hot_search_defaults` 导入与调用。 - 验证: - `python3 -m py_compile api/app/services/legacy_authz_service.py api/app/services/seed_service.py api/app/api/router.py api/app/services/topic_registry.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm run build:web` -> 通过;后台静态路由从 54 个下降到 36 个,已不再包含上述 8 个页面。 - 风险与影响: - 本次不做数据库物理删行迁移;若库内仍有历史菜单记录,将被后端过滤而不可见。 - `api/app/api/v1/hot_search.py` 与 `api/app/api/v1/mdresolve.py` 文件仍保留在仓库,但未挂载到 `api_router`,外部不可访问。 ## Work Log - 下线功能补充收口(2026-04-23) - 补充改动: - 删除历史别名页面 `web/src/app/admin/tag/page.tsx`,彻底下线“分组管理”的旧路由入口。 - 验证补充: - 因删除页面后 `.next` 残留类型缓存导致 `tsc` 引用旧路径,执行 `rm -rf web/.next` 后重跑: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过 - `npm run build:web` -> 通过 - 产物路由确认不再包含 `/admin/tag` 及本轮下线的 8 个功能页面。 ## Work Log - 下线 4 个后台功能模块(2026-04-23) - 背景:按要求删除以下功能: - 脚本管理(`admin.cron_task_mgr`) - 待办管理(`admin.todos`) - 作业监控(`admin.job_mgr`) - JWT 生成器(`admin.jwt_generator`) - 本次改动(最小闭环): - 前端下线: - 删除路由页面: - `web/src/app/admin/cron/page.tsx` - `web/src/app/admin/todos/page.tsx` - `web/src/app/admin/job/page.tsx` - `web/src/app/admin/jwt-generator/page.tsx` - 后台首页移除 4 个入口卡片: - `web/src/app/admin/page.tsx` - 菜单管理页移除 4 个菜单码受保护限制(允许清理历史残留): - `web/src/app/admin/menus/page.tsx` - 为保留“队列管理”能力,将原待办页面实现迁移到: - `web/src/app/admin/jobqueue/_components/jobqueue-todo-page.tsx` - `web/src/app/admin/jobqueue/page.tsx` 改为引用新组件。 - 后端菜单/权限链路下线: - `api/app/services/seed_service.py` - 移除 4 个菜单定义及 admin 默认菜单绑定。 - 移除 `jwt_generator.read/jwt_generator.manage` 默认权限与 admin 角色默认绑定。 - `api/app/services/legacy_authz_service.py` - 将 4 个菜单码加入 `DISABLED_MENU_CODES`,并移除对应菜单权限映射。 - 移除 `DEFAULT_ADMIN_PERMISSION_CODES` 中 `jwt_generator.*`。 - `api/app/services/legacy_admin_rbac_service.py` - 将 4 个菜单码加入 `REMOVED_MENU_CODES`。 - `api/app/services/admin_service.py` - 将 4 个菜单码加入 `REMOVED_MENU_CODES`,并从受保护删除集合中移除。 - 后端接口挂载下线: - `api/app/api/router.py` - 移除 `jwt_generator_router` import 与 `include_router`,JWT 生成器 API 不再对外挂载。 - 验证: - `python3 -m py_compile api/app/services/seed_service.py api/app/services/legacy_authz_service.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py api/app/api/router.py` -> 通过。 - `python3 -m compileall api/app` -> 通过。 - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - `npm --workspace web exec next build --webpack` -> 通过;产物路由确认不再包含: - `/admin/cron` - `/admin/todos` - `/admin/job` - `/admin/jwt-generator` - 风险与影响: - `api/app/api/v1/jwt_generator.py` 文件仍保留在仓库,但已从路由汇总中移除,不可外部访问。 - 当前环境下 Next 构建偶发 `.next` 产物拷贝 `ENOENT` 警告(非本次改动引入,存在环境不稳定性);本轮最终构建已成功产出路由清单。 ## Work Log - 修复 `/users` 首屏卡在 Loading(2026-04-23) - 背景:访问 `http://localhost:3000/users` 时,后台壳层页面一直停留在 `Loading admin workspace...`。 - 根因: - `web/src/app/admin/layout.tsx` 的 `loadMenus` 仅处理了接口返回 `!ok` 场景; - 当 `fetchWithAuth("/api/v1/admin/me/menus")` 发生网络异常(例如 API 未启动、连接失败)抛出异常时,没有兜底 `catch/finally`,导致 `loadingMenus` 一直为 `true`。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - `loadMenus` 增加 `try/catch/finally`。 - 异常场景下会: - 清空 `menuTree` - 设置 `menuError`(显示可见错误) - 在 `finally` 中统一 `setLoadingMenus(false)`,避免页面卡死在 loading。 - 无用户场景补充 `setMenuError("")`,避免遗留错误文案。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - 风险与影响: - 仅涉及后台壳层菜单加载状态管理,不改接口契约与业务数据。 ## Work Log - 默认 admin 视为全权限(2026-04-23) - 背景:要求“默认 admin 有全部权限”。 - 现状定位: - 后端接口鉴权已具备 `admin` 角色兜底(`require_permission/require_any_permission` 中 `if "admin" in role_codes: allow`)。 - 前端权限判定 `hasPermission` 仅判断 `permission_codes.includes(...)`,未对 `admin` 角色做兜底,可能导致 admin 页面按钮/入口被误隐藏。 - 本次改动(最小闭环): - `web/src/components/auth-provider.tsx` - `hasPermission` 调整为: - 若用户角色包含 `admin`,直接返回 `true`; - 否则按原逻辑判断 `permission_codes`。 - 验证: - 先清理 Next 类型缓存:`rm -rf web/.next` - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - 风险与影响: - 仅影响前端可见性与交互开关判定,不改后端鉴权规则。 - 后端仍保持真实权限校验,前端放行不会绕过服务端安全边界。 ## Work Log - 去掉登录后首页,直接进入后台(2026-04-23) - 背景:要求“去掉首页,登录完成直接进入后台”。 - 本次改动(最小闭环): - `web/src/app/page.tsx` - 移除已登录态的首页欢迎面板(欢迎文案 / Ping / 快捷入口)。 - 新增登录态跳转:当 `!initializing && user` 时执行 `router.replace("/dashboard")`。 - 已登录时仅短暂显示“正在进入后台...”,避免闪现旧首页内容。 - `web/src/app/admin/layout.tsx` - 未登录访问后台时按钮文案从“返回首页”调整为“前往登录”(`/`)。 - 账号下拉中的“返回首页”调整为“后台首页”(`/dashboard`)。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - 风险与影响: - 仅涉及前端路由与文案,不改后端接口与鉴权逻辑。 ## Work Log - 右上角账号入口改为 Avatar(2026-04-23) - 背景:要求“右上角账号改成 avatar 组件”。 - 本次改动(最小闭环): - `web/src/app/admin/layout.tsx` - 顶部账号下拉触发器由“账号”按钮改为 AntD `Avatar`。 - 头像文案取当前用户名首字母(大写),空值兜底 `U`。 - 保持原下拉菜单项不变(账号信息、后台首页、退出登录)。 - 验证: - `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。 - 风险与影响: - 仅涉及前端展示与交互入口样式,不改业务逻辑与接口调用。