Files
WonderQ-Project/docs/superpowers/plans/2026-08-26-admin-vue-formalization.md
T
duanshuwen efab6d1398 feat(admin): 新增全套运营管理功能,修复核心路由bug及后端seed问题
修复Vue Admin布局中RouterView未绑定唯一key的问题,解决RBAC系统资源页面切换后内容不刷新的ISSUE-001故障,新增routeViewKey工具函数区分路由实例
完善路由配置,新增admin空路径路由,修复路由命名空路径告警
新增媒体管理、运维发布/重置页面,补全全站路由注册与动态路由过滤逻辑
新增玩法、线索、管家、系统资源管理的全套工具函数、业务组件及单元测试用例
修复后端app/seed.py中媒体资源重复检测未考虑会话内pending资源的bug,新增对应测试验证修复逻辑
修正alembic迁移版本0015的依赖关系,删除废弃的旧迁移文件
优化后端系统接口的字典序列化逻辑,简化重复代码
添加QA验收报告与相关测试截图,完善项目测试覆盖
2026-08-26 18:48:48 +08:00

469 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WonderQ Admin Vue 正式化实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use `subagent-driven-development` or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在不删除 React 过渡端的前提下,将四组首页 CRUD 接入 Vue,补齐管理员踢下线、显式 403、五级数据范围和 `/admin`、`/admin-legacy` 正式代理,并完成可重复的三端验收。
**Architecture:** 保持 FastAPI + PostgreSQL + Redis 的现有模块化单体。后端继续以既有 Admin API 和 `{code,msg,data}` 契约为边界;Vue 通过独立的 API client、页面组件和静态路由注册实现新管理端;React 继续由独立 legacy 静态入口提供回滚能力。Redis 只保存会话记录、用户会话索引和权限缓存,不新增会话数据库表。
**Tech Stack:** Python 3、FastAPI、SQLAlchemy 2、Alembic、PostgreSQL、Redis、JWT、Vue 3、Element Plus、TypeScript、Pinia、Vue Router 4、React、Vite、Nginx、Docker Compose、pytest、Vitest。
---
## 文件边界与责任
### 后端
- 修改 `WonderQ-Admin/app/redis_session.py`:扩展会话存储协议,实现按用户索引、列出和批量撤销会话;不改变 Token 明文存储策略。
- 修改 `WonderQ-Admin/app/auth.py`:在登录创建和刷新轮换时维护用户会话索引;保留旧登录响应字段。
- 修改 `WonderQ-Admin/app/routers/system.py`:新增踢下线接口,执行权限、数据范围、当前会话保护和审计。
- 修改 `WonderQ-Admin/app/routers/admin.py`:为四组首页资源接入现有权限码和数据范围检查,保持既有路径与返回字段。
- 修改 `WonderQ-Admin/app/rbac.py`:固定数据范围和权限缓存故障的安全行为,确保资源列表与单条写入使用同一范围规则。
- 修改 `WonderQ-Admin/tests/test_home.py`、`tests/test_admin_rbac.py`:补充首页权限、范围和契约回归。
- 新建 `WonderQ-Admin/tests/test_admin_sessions.py`:覆盖 Redis 会话索引、刷新轮换、批量撤销和故障行为。
- 新建 `WonderQ-Admin/tests/test_data_scope_matrix.py`:覆盖五种数据范围的跨部门矩阵。
### Vue 管理端
- 修改 `WonderQ-Admin-UI-Vue/src/types.ts`:增加四组首页资源类型、403 错误类型、会话撤销结果类型。
- 修改 `WonderQ-Admin-UI-Vue/src/api/client.ts`:增加首页 CRUD、踢下线请求和显式 403 错误;不改变 API base、Cookie 或 Access Token 键。
- 新建 `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`:验证四组首页 API 方法、HTTP 方法和路径。
- 修改 `WonderQ-Admin-UI-Vue/src/api/client-system.test.ts`:验证踢下线 API。
- 新建 `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudEditor.vue`:共享首页卡片表单、校验、图片 URL/状态字段和提交态。
- 新建 `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudTable.vue`:共享列表、空态、错误态、重试、排序和权限按钮。
- 新建 `WonderQ-Admin-UI-Vue/src/lib/home-content.ts`:定义四组资源的字段元数据、默认值和 payload 归一化。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/HomeContentPage.vue`:体验、团建、野奢三组 CRUD 页面。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/HomeWanfaRecommendationPage.vue`:玩法推荐关联 CRUD 页面。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/ForbiddenPage.vue`:登录态下的 403 页面。
- 修改 `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`、`src/router/index.ts`:把首页站点配置与四组业务入口拆开,并注册 403 和首页业务路由。
- 修改 `WonderQ-Admin-UI-Vue/src/pages/SystemResourcePage.vue`:在用户列表增加踢下线操作。
- 新建 `WonderQ-Admin-UI-Vue/src/router/router.test.ts`:验证 401、403、无权限页面和未知路径的分流。
### 部署与契约
- 修改 `docs/home-api.md`:补充 Vue 对接字段、统一错误和四组后台验收矩阵。
- 修改 `docs/admin-api-requirements.md`:补充踢下线、403、数据范围和 Redis 故障契约。
- 修改 `docs/integration-workflow.md`:补充双管理端、正式代理和回滚命令。
- 修改 `docs/admin-business-function-matrix.md`:记录每个切片的实际测试和人工验收结果。
- 新建 `WonderQ-Admin-UI-Vue/Dockerfile`、`WonderQ-Admin-UI-Vue/nginx.conf`:构建并提供 `/admin/` 静态资源。
- 修改 `WonderQ-Admin-UI/vite.config.ts`、`WonderQ-Admin-UI/nginx.conf`、`WonderQ-Admin-UI/Dockerfile`:支持 `/admin-legacy/` base 和 legacy 静态入口。
- 新建 `deploy/nginx/wonderq-admin.conf`:统一代理 `/admin/`、`/admin-legacy/` 和 `/api/`;不写入任何真实环境变量。
- 新建 `scripts/verify-admin-proxy.ps1`:检查两个构建产物和统一 Nginx 配置的入口、深链与 API 隔离。
- 修改 `docker-compose.yml`(仅在现有 Compose 确认包含前端服务时):加入两个前端构建入口和健康检查;若现有 Compose 不负责前端,则只增加部署文档,不扩大 Compose 责任。
## Task 1: 建立契约基线与失败测试入口
**Files:**
- Modify: `docs/home-api.md`
- Modify: `docs/admin-api-requirements.md`
- Modify: `docs/integration-workflow.md`
- Test: `WonderQ-Admin/tests/test_home.py`
- Test: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- [ ] **Step 1: 记录四组首页的现有字段与接口响应**
从 `WonderQ-Admin/app/schemas.py`、`WonderQ-Admin/app/routers/admin.py`、`WonderQ-Admin-UI/src/api.ts` 和 `docs/home-api.md` 对照四组资源。契约表必须明确 `GET/POST/PATCH/DELETE/PATCH reorder`、成功状态码、空数组、排序完整 ID 列表和 403 响应,不改现有公共字段名。
- [ ] **Step 2: 先写 Vue API 失败测试**
在 `client-home.test.ts` 中为四组资源各调用一次列表、创建、更新、删除、排序方法,断言 `fetch` 收到以下请求:
```ts
expect(fetchMock.mock.calls.map(([url, options]) => [url, options?.method])).toEqual([
["/api/admin/home/experiences", undefined],
["/api/admin/home/experiences", "POST"],
["/api/admin/home/experiences/experience-1", "PATCH"],
["/api/admin/home/experiences/experience-1", "DELETE"],
["/api/admin/home/experiences/reorder", "PATCH"],
["/api/admin/home/team-buildings", undefined],
["/api/admin/home/team-buildings", "POST"],
["/api/admin/home/team-buildings/team-1", "PATCH"],
["/api/admin/home/team-buildings/team-1", "DELETE"],
["/api/admin/home/team-buildings/reorder", "PATCH"],
["/api/admin/home/wild-archives", undefined],
["/api/admin/home/wild-archives", "POST"],
["/api/admin/home/wild-archives/archive-1", "PATCH"],
["/api/admin/home/wild-archives/archive-1", "DELETE"],
["/api/admin/home/wild-archives/reorder", "PATCH"],
["/api/admin/home/play-recommendations", undefined],
["/api/admin/home/play-recommendations", "POST"],
["/api/admin/home/play-recommendations/recommendation-1", "PATCH"],
["/api/admin/home/play-recommendations/recommendation-1", "DELETE"],
["/api/admin/home/play-recommendations/reorder", "PATCH"],
]);
```
- [ ] **Step 3: 运行失败测试,确认缺口真实存在**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: FAIL because the four Vue client methods and corresponding types do not exist yet.
- [ ] **Step 4: 同步契约文档并检查差异**
Run: `git diff --check -- docs/home-api.md docs/admin-api-requirements.md docs/integration-workflow.md`
Expected: no whitespace errors;文档中没有真实 Token、密码、OSS 凭证或生产地址。
## Task 2: 接入四组首页 API 与后端权限/范围
**Files:**
- Modify: `WonderQ-Admin-UI-Vue/src/types.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Modify: `WonderQ-Admin/app/routers/admin.py`
- Modify: `WonderQ-Admin/tests/test_home.py`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- [ ] **Step 1: 写后端首页鉴权和范围失败测试**
为四组列表和至少一个更新接口增加测试:普通管理员没有 `admin:site-config` 时返回 403;有该权限时只能读取或更新 `is_within_data_scope` 为真的记录;完整排序请求缺 ID 或重复 ID 时返回 400。测试继续使用现有依赖覆盖方式,不创建真实生产数据。
- [ ] **Step 2: 运行后端失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Expected: new permission/scope assertions fail while existing home CRUD compatibility tests remain visible in the output。
- [ ] **Step 3: 增加 TypeScript 资源类型和 API 方法**
在 `src/types.ts` 中以 React 已有类型和后端 schema 为准定义 `HomeExperience`、`HomeTeamBuilding`、`HomeWildArchive`、`HomeWanfaRecommendation` 及对应 Create/Patch 类型;在 `src/api/client.ts` 中提供以下函数:
```ts
getHomeExperiences(); createHomeExperience(input); updateHomeExperience(id, input);
deleteHomeExperience(id); reorderHomeExperiences(itemIds);
getHomeTeamBuildings(); createHomeTeamBuilding(input); updateHomeTeamBuilding(id, input);
deleteHomeTeamBuilding(id); reorderHomeTeamBuildings(itemIds);
getHomeWildArchives(); createHomeWildArchive(input); updateHomeWildArchive(id, input);
deleteHomeWildArchive(id); reorderHomeWildArchives(itemIds);
getHomePlayRecommendations(); createHomePlayRecommendation(input);
updateHomePlayRecommendation(id, input); deleteHomePlayRecommendation(id);
reorderHomePlayRecommendations(itemIds);
```
每个方法只调用既有 `/api/admin/home/...` 路径,并由 `request<T>` 统一解包响应。
- [ ] **Step 4: 将后端四组路由切换到已有权限依赖并复用数据范围**
把当前四组首页路由的 `Depends(require_admin)` 替换为 `Depends(require_admin_permission("admin:site-config"))`,列表通过 `apply_data_scope`,单条更新、删除和排序前通过 `is_within_data_scope`;保留超级管理员兼容行为、既有审计事件和返回结构。写入记录继续使用现有创建人/部门回填逻辑。
- [ ] **Step 5: 运行后端与 Vue API 测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: new assertions PASS;如全量后端仍出现已知 Alembic 兼容失败,记录其具体测试名,不将其归因于首页切片。
## Task 3: 迁移首页三组内容 CRUD 页面
**Files:**
- Create: `WonderQ-Admin-UI-Vue/src/lib/home-content.ts`
- Create: `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudEditor.vue`
- Create: `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudTable.vue`
- Create: `WonderQ-Admin-UI-Vue/src/pages/HomeContentPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`
- Test: `WonderQ-Admin-UI-Vue/src/lib/home-content.test.ts`
- [ ] **Step 1: 写字段元数据和表单归一化失败测试**
测试必须分别检查三组资源的必填字段、空字符串裁剪、数字字段转换、`sortOrder` 默认值、`isActive` 默认值和错误文案。测试输入只使用测试文本和本地图片 URL。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/lib/home-content.test.ts`
Expected: FAIL because the metadata module does not exist。
- [ ] **Step 3: 实现元数据模块**
`home-content.ts` 导出资源联合类型、资源定义、`createEmptyHomeDraft`、`toHomePayload` 和 `validateHomePayload`。所有字段名必须与 `docs/home-api.md` 和后端 Pydantic schema 一致;不把 base64 文件内容写入 CRUD payload,只保存媒体接口返回的 URL。
- [ ] **Step 4: 实现共享表格与编辑器**
`HomeCrudTable.vue` 接收 `items/loading/error/permission`,提供空态、错误重试、编辑、删除、上下移和提交中禁用;`HomeCrudEditor.vue` 接收资源定义和 draft,提供 Element Plus 表单校验、取消、提交中状态和 `save` 事件。按钮使用 `v-permission="'admin:site-config'"`,后端 403 仍由页面捕获显示错误。
- [ ] **Step 5: 实现三组 Vue 页面**
`HomeContentPage.vue` 根据路由参数选择 `experiences`、`team-buildings` 或 `wild-archives`,加载、创建、更新、删除和排序均调用 Task 2 的 API 方法。列表、编辑器和请求状态按资源隔离,避免修改一组数据时污染另外两组。页面提供加载骨架、空态、错误重试和窄屏表格滚动。
- [ ] **Step 6: 将首页入口与新页面注册到静态路由**
保留 `/admin/home` 作为站点配置页,在该页增加三个明确入口;在 `router/index.ts` 增加 `/home/content/:resource` 静态路由并绑定 `HomeContentPage`,权限使用 `admin:site-config`。组件仍必须来自本地预注册表,不接受后端任意组件路径。
- [ ] **Step 7: 运行 Vue 单元、类型和构建验证**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/lib/home-content.test.ts src/api/client-home.test.ts`
Run: `yarn build`
Expected: tests and TypeScript build PASS;Vite 已有 chunk 大小或 `__dirname` warning 可记录,但不得出现编译错误。
## Task 4: 迁移玩法推荐关联 CRUD
**Files:**
- Create: `WonderQ-Admin-UI-Vue/src/pages/HomeWanfaRecommendationPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`
- Test: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- Test: `WonderQ-Admin/tests/test_home.py`
- [ ] **Step 1: 写玩法推荐列表、关联和排序失败测试**
后端测试验证关联不存在的玩法分类返回 404/400、删除关联不删除分类、状态更新只改变关联、排序必须包含当前全部关联 ID。Vue API 测试验证 `play-recommendations` 的五个方法路径。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: new Vue methods and missing edge-case assertions fail。
- [ ] **Step 3: 实现 Vue 玩法推荐页面**
页面加载现有玩法分类作为选择项,单独显示已关联推荐;创建和编辑只提交后端允许的分类 ID、状态和排序字段;删除操作明确提示“只移除首页关联,不删除玩法分类”;复用 `HomeCrudTable` 的空态、错误态、排序和权限按钮。
- [ ] **Step 4: 运行玩法推荐切片验收**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Expected: 后端首页测试、Vue 现有 25 项基线测试和构建全部通过;失败项记录到业务矩阵后再进入下一阶段。
## Task 5: 实现管理员踢下线
**Files:**
- Modify: `WonderQ-Admin/app/redis_session.py`
- Modify: `WonderQ-Admin/app/auth.py`
- Modify: `WonderQ-Admin/app/routers/system.py`
- Modify: `WonderQ-Admin/tests/test_admin_rbac.py`
- Create: `WonderQ-Admin/tests/test_admin_sessions.py`
- Modify: `WonderQ-Admin-UI-Vue/src/types.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client-system.test.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/SystemResourcePage.vue`
- [ ] **Step 1: 写会话索引失败测试**
在 `test_admin_sessions.py` 验证:
```python
store = InMemoryAdminSessionStore()
store.create(session_id="s-1", user_id="u-1", access_jti="a-1", refresh_hash="r-1", access_expires_at=future, refresh_expires_at=future)
store.create(session_id="s-2", user_id="u-1", access_jti="a-2", refresh_hash="r-2", access_expires_at=future, refresh_expires_at=future)
assert store.revoke_user_sessions("u-1", except_session_id="s-1") == 1
assert store.is_access_active("s-1", "a-1")
assert not store.is_access_active("s-2", "a-2")
```
同时验证 rotate 保留同一会话索引、revoke 删除索引、重复批量撤销返回 0、Redis 异常抛出 `RedisUnavailableError`。
- [ ] **Step 2: 运行会话失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py -q`
Expected: FAIL because the protocol and stores do not expose `revoke_user_sessions`。
- [ ] **Step 3: 扩展协议和两种存储实现**
在 `AdminSessionStore` 增加 `list_user_sessions(user_id)` 和 `revoke_user_sessions(user_id, except_session_id=None)`。InMemory 使用 `dict[user_id, set[session_id]]` 或从现有记录派生并在 create/rotate/revoke 中保持一致;Redis 使用 `wonderq:admin:user-sessions:{user_id}` Set,成员仅为 session ID,批量删除 session key、refresh key 和 Set 成员。所有 Redis 客户端异常统一为 `RedisUnavailableError`。
- [ ] **Step 4: 运行会话存储测试并修复轮换回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py tests/test_admin_rbac.py -q`
Expected: session index, JWT rotation, logout and existing legacy login fields all PASS。
- [ ] **Step 5: 写踢下线路由失败测试**
使用 FastAPI `TestClient` 和依赖覆盖验证:没有 `system:user:update` 返回 403;目标用户不存在返回 404;目标为当前用户返回 400;成功时排除当前 session、返回撤销数量并写入审计记录;Redis 不可用返回服务错误而不是继续放行。
- [ ] **Step 6: 实现 system 路由与前端 API**
新增 `POST /api/admin/system/users/{user_id}/sessions/revoke`,依赖 `require_permission("system:user:update")`,使用当前 JWT 的 `sid` 作为排除项,调用 `revoke_user_sessions`,审计内容只记录操作者、目标用户和数量。Vue 增加 `revokeSystemUserSessions(userId)`,系统用户行增加“踢下线”确认按钮、提交中禁用和结果提示。
- [ ] **Step 7: 运行踢下线全链路测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py tests/test_admin_rbac.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-system.test.ts`
Expected: backend and API client tests PASS;浏览器验收时用两个登录会话确认被踢会话的下一次请求收到 401 并回到登录页。
## Task 6: 增加显式 403 页面与错误分流
**Files:**
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Create: `WonderQ-Admin-UI-Vue/src/pages/ForbiddenPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Create: `WonderQ-Admin-UI-Vue/src/router/router.test.ts`
- [ ] **Step 1: 写 403 与 401 分流失败测试**
测试 `request` 收到 403 时抛出 `PermissionDeniedError`,不调用 `/auth/refresh`;收到 401 时仍只刷新一次;路由守卫对已登录无权限页面返回 `forbidden`,未登录返回 `login`,未知路径返回 `not-found`。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/router/router.test.ts`
Expected: FAIL because 403 error class、forbidden route and explicit guard branch do not exist。
- [ ] **Step 3: 实现错误类与 403 页面**
新增 `PermissionDeniedError extends ApiRequestError`,状态码固定为 403;`request` 保持 403 原样解包并抛出该错误。新增 `/forbidden` 公共渲染页面,显示无权限、返回上一页和回到首页操作,不清除 Pinia 登录态。
- [ ] **Step 4: 修改路由守卫**
把当前 `return { name: "not-found" }` 的权限分支改为 `return { name: "forbidden", query: { from: to.fullPath } }`;保留动态路由只从 `componentRegistry` 注册。系统资源路由仍根据用户、角色、菜单、部门分别检查权限码。
- [ ] **Step 5: 运行 403、现有测试和构建**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Expected: 403 不触发刷新、不误导为 404,现有测试和构建 PASS。
## Task 7: 完成五级数据范围自动化矩阵
**Files:**
- Modify: `WonderQ-Admin/app/rbac.py`
- Modify: `WonderQ-Admin/app/routers/admin.py`
- Modify: `WonderQ-Admin/tests/test_data_scope_matrix.py`
- Modify: `WonderQ-Admin/tests/test_admin_rbac.py`
- Modify: `docs/admin-api-requirements.md`
- [ ] **Step 1: 建立隔离测试数据**
测试 fixture 创建部门 `dept-a`、子部门 `dept-a-child`、`dept-b`,管理员 `user-a`、`user-b`,并创建带 `deptId` 和 `createdById` 的线索、玩法详情、管家、媒体和首页资源。fixture 每次测试独立回滚,不使用开发数据库真实业务数据。
- [ ] **Step 2: 写五种范围失败测试**
分别为 `all`、`dept`、`dept_and_children`、`custom_dept`、`self` 配置角色,断言列表只返回预期 ID;同时调用详情、更新、删除或状态写入,确认跨范围资源返回 404/403,不能只在前端隐藏。
- [ ] **Step 3: 运行范围矩阵失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_data_scope_matrix.py -q`
Expected: 对未覆盖资源或错误的异常状态出现失败,输出具体资源和范围编码。
- [ ] **Step 4: 统一范围过滤与写入检查**
确保 `apply_data_scope` 对目录列表使用部门/创建人 OR 规则;`is_within_data_scope` 用于详情、更新、删除、状态和排序;缺少归属字段的旧记录由既有回填规则处理,不把无归属资源错误暴露给非超级管理员。保留 `admin/super_admin` 的全量兼容行为。
- [ ] **Step 5: 固定 Redis 权限缓存故障语义**
权限缓存读取或写入失败时,允许数据库重新计算权限但不得把异常转换成放行;需要 Redis 会话校验的接口继续拒绝。为 `RedisUnavailableError` 增加测试,确认不能因 Redis 断开返回全量菜单或全量业务数据。
- [ ] **Step 6: 运行范围和后端回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_data_scope_matrix.py tests/test_admin_rbac.py tests/test_home.py -q`
Expected: five-level matrix and affected business tests PASS;与本切片无关的既有 Alembic bootstrap 失败单独记录,不跳过新测试。
## Task 8: 建立 Docker 与正式双入口代理
**Files:**
- Create: `WonderQ-Admin-UI-Vue/Dockerfile`
- Create: `WonderQ-Admin-UI-Vue/nginx.conf`
- Modify: `WonderQ-Admin-UI/Dockerfile`
- Modify: `WonderQ-Admin-UI/nginx.conf`
- Modify: `WonderQ-Admin-UI/vite.config.ts`
- Create: `deploy/nginx/wonderq-admin.conf`
- Modify: `docs/integration-workflow.md`
- [ ] **Step 1: 写代理静态检查**
新建 `scripts/verify-admin-proxy.ps1`,断言 Vue 构建产物引用 `/admin/`,React legacy 构建产物引用 `/admin-legacy/`,Nginx 配置包含 `/api/` proxy、两个 SPA fallback 和独立 `/healthz`,且不存在把 `/api/` fallback 到前端 index 的规则。
- [ ] **Step 2: 运行代理失败检查**
Run: `rg -n "admin-legacy|/admin/|proxy_pass|try_files|location /api" WonderQ-Admin-UI WonderQ-Admin-UI-Vue deploy/nginx`
Expected: 当前 React 入口和 Vue Docker 入口缺少至少一项,检查失败并列出缺失路径。
- [ ] **Step 3: 配置 Vue 静态镜像**
Vue Dockerfile 使用 Node 构建 `yarn build`,Nginx 配置把 `/` 的容器根目录作为 Vue `/admin/` 资源根,并对 `/admin/` history fallback 到 `/admin/index.html`。健康检查使用 `/healthz`,不暴露环境变量。
- [ ] **Step 4: 配置 React legacy base 和镜像**
在 React Vite 配置中读取 `VITE_BASE_PATH`,默认开发仍为 `/`,正式 legacy 构建传入 `/admin-legacy/`;React Nginx 资源根、history fallback 和静态资源链接与该 base 一致。原有 API proxy 开发行为保持不变。
- [ ] **Step 5: 编写统一部署 Nginx 配置**
统一配置明确以下映射:
```nginx
location /api/ { proxy_pass http://wonderq-api:4000; }
location /admin/ { alias /srv/wonderq-vue/; try_files $uri $uri/ /admin/index.html; }
location /admin-legacy/ { alias /srv/wonderq-react/; try_files $uri $uri/ /admin-legacy/index.html; }
```
实际文件路径按镜像挂载目录调整,但必须保留三条逻辑边界,且 `/api/` 位于 SPA fallback 之前并单独转发。
- [ ] **Step 6: 构建并验证容器**
Run: `docker compose config`
Run: `docker build -t wonderq-admin-ui-vue:acceptance .\WonderQ-Admin-UI-Vue`
Run: `docker build --build-arg VITE_BASE_PATH=/admin-legacy/ -t wonderq-admin-ui-react-legacy:acceptance .\WonderQ-Admin-UI`
Run: `docker compose up -d postgres redis`
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m alembic upgrade head`
Expected: 两个前端镜像、PostgreSQL、Redis 构建/启动成功;`http://localhost:4000/health` 和两个静态入口可访问。若当前 Compose 不包含 API 服务,只执行现有 API 启动命令并记录边界。
## Task 9: 三端回归、人工验收与 React 删除闸门
**Files:**
- Modify: `docs/admin-business-function-matrix.md`
- Modify: `docs/integration-workflow.md`
- Read-only verification: `WonderQ-MiniAPP/src/lib/api.ts`, `WonderQ-Admin-UI/src/api.ts`
- [ ] **Step 1: 运行后端完整回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest -q`
Expected: 新增切片全部通过;既有 Alembic 兼容失败必须保留完整测试名和原因,不能用“部分通过”掩盖。
- [ ] **Step 2: 运行两套管理端回归**
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Run: `Set-Location ..\WonderQ-Admin-UI; yarn build`
Expected: Vue 测试/构建和 React 构建通过;旧 React API 路径、登录响应字段和刷新逻辑没有被删改。
- [ ] **Step 3: 做 MiniAPP Public API 兼容检查**
确认 `WonderQ-MiniAPP/src/lib/api.ts` 仍只访问 `/api/public/...`,首页 `experiences`、团队共创和极境视界字段与 `docs/public-api.md` 一致。只有 Public API 或字段发生变化时才运行 MiniAPP 类型检查和测试。
- [ ] **Step 4: 人工验收顺序**
使用独立测试账号逐项完成:
1. 四组首页新建、编辑、删除、排序、启停和错误重试;
2. 两个登录会话验证踢下线;
3. 无页面权限 403、无按钮权限和直接 API 403;
4. 五种数据范围跨部门列表与写入;
5. Redis 异常、Docker、`/admin/`、`/admin-legacy/` 和深链刷新;
6. Public API/MiniAPP 回归;
7. 最后进行 OSS/媒体上传验收。
每项将实际 URL、操作结果和失败证据写入 `docs/admin-business-function-matrix.md`,不记录账号密码、Token 或密钥。
- [ ] **Step 5: 判断 React 删除闸门**
只有 Vue 功能矩阵、Public/Admin API 回归、RBAC 五级范围、会话/Redis、Docker/代理、OSS/媒体和人工验收全部通过,并收到用户明确“删除旧 React”确认,才另开删除计划。当前计划结束时保留 React 源码、依赖、构建入口和 `/admin-legacy/`。
## 验证与交付规则
- 每个 Task 完成后先运行其局部测试,再运行受影响端的构建;失败时先定位根因,不以扩大改动规避测试。
- 不执行 `git reset`、`git checkout --`、批量删除或覆盖用户现有改动。
- 不修改 `.env`、`.env.local`、真实 Docker secrets、OSS 凭证、生产数据库连接或生产部署变量。
- 不新增 Alembic 迁移;会话索引只使用 Redis。若实现过程中发现确需数据库结构变化,暂停并单独征求确认。
- 不自动提交或删除 React;阶段结果以 `git diff --check`、测试输出和业务矩阵记录作为检查点。