Files
gaokao-volunteer-system/SPRINT_2_S2_API_HOOKS.md
Frontend Developer e8b8ad079e feat(sprint-2): V10 选项 B 完整切换 + G1 闸门通过
框架切换:Next.js 16 → Vite 5 + React 19 + React Router 7
状态管理:7 手写 hook → 4 Zustand slice
数据获取:直接 fetch → TanStack Query 5 (15 hooks)
表单:3-step 状态机 → RHF 7 + Zod
测试:0 → 25 单测 + 20 e2e (4 浏览器)
Bundle:192 KB gzip (目标 < 300 KB)
Lint:49 warning → 0 warning
Type:33 any → 0 any

S2-T-01 切 Vite 5 + React 19 框架
S2-T-02 Zustand 4 slice 替代 7 手写 hook
S2-T-03 TanStack Query 5 + 15 hooks
S2-T-04 Vitest + RTL + MSW + Chromatic 配置
S2-T-05 OpenAPI Codegen + Zod schema 复用
S2-T-06 RHF 7 + Zod 重写 FormCard
S2-T-07 Playwright 视觉基线 4 浏览器 5 spec
S2-T-08 Vite build + bundle < 300KB gzip

G1 闸门 (全部通过):
- typecheck: 0 error
- lint: 0 error 0 warning
- test (Vitest): 25/25 passed
- test:e2e (Playwright): 20/20 passed
- build (Vite): 326 modules 4.14s
- codegen:check: OpenAPI types 非占位

Sprint 2 收口报告:SPRINT_2_CLOSEOUT_2026-07-03.md
2026-07-03 15:51:03 +08:00

385 lines
13 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.
# Sprint 2 任务拆解W2-3 · 9 人天 · 56 子任务)— V10 选项 B
> **技术栈**Vite 5 + React 19 + TypeScript 5 + Tailwind 4 + **Zustand 4** + **TanStack Query 5** + **React Hook Form 7** + Vitest 2 + Playwright 1.55
> **目标**:整体重写 7 个手写 hook + 引入现代状态管理 + 后端 5 端点对接
> **闸门**G1OpenAPI 类型生成无 `any` + ESLint 0 warning
> **V10 变化**:从 18d 缩到 9d节省 9d = 删 33 any 修复 + 7 hook 重构 + 6 文件重写)
> **PM 决策2026-07-03**整体重写为新实现Playwright 视觉回归 + Chromatic 验收
---
## ⚠️ V10 关键变化(与 V2 对比)
| 维度 | V2 | V10 选项 B |
|---|---|---|
| 框架 | Next.js 16保留 | **Vite 5 + React 19**(重写) |
| 状态管理 | 自定义 7 个手写 hook | **Zustand 4**4 个 slice |
| 数据获取 | 自写 useChat + fetch | **TanStack Query 5** |
| 表单 | 受控 + 3-step 状态机 | **React Hook Form 7** + Zod |
| 测试 | Vitest + Playwright保留 | 同 |
| 视觉回归 | 无 | **+ Chromatic**V10 新增) |
| 总子任务 | 58 | 56少 2合并到 Zustand store |
| 总估时 | 18d | **9d** |
---
## 0. Sprint 2 概览
| 任务 | 主任务 | 估时 | 子任务数 |
|---|---|---|---|
| T-A-14 | Vitest + RTL + MSW + Chromatic 配置 | 0.5d | 4 |
| T-A-15 | 示范单测ModeIndicator | 0.25d | 3 |
| T-A-16 | 示范单测SafeMarkdown | 0.25d | 3 |
| T-A-17 | 示范单测FormCardRHF 版) | 0.5d | 4 |
| T-A-18 | Playwright + Chromatic 配置 | 0.5d | 3 |
| T-A-19 | 示范 e2etheme.spec | 0.5d | 3 |
| T-A-20 | 示范 e2enavigation.spec | 0.5d | 3 |
| T-A-21 | OpenAPI Codegen + Zod schema 复用 | 1.0d | 5 |
| T-A-22 | 示范调用MSW mock server | 0.5d | 4 |
| T-B-01 | **Zustand store** + 5 query hookschat | 1.0d | 5 |
| T-B-02 | TanStack Query 3 个 consultations 端点 | 0.5d | 3 |
| T-B-03 | TanStack Query 4 个 plans 端点 | 0.5d | 3 |
| T-B-04 | RHF 重写 FormCard + assessment | 1.0d | 5 |
| T-B-05 | audit/upload 端点V10 重写) | 1.0d | 4 |
| T-B-06 | **Chromatic 视觉基线**V10 新增) | 0.5d | 3 |
| T-B-07 | **Vite build + bundle 体积验证** | 0.5d | 4 |
| **合计** | **16 任务** | **9.5d** | **56** |
---
## T-A-14 · Vitest + RTL + MSW + Chromatic 配置0.5d · 4 子任务)
### ST-S2-A-14.1 写 `vitest.config.ts`0.125d
- **产出**`apps/web/vitest.config.ts`jsdom env + setup 文件 + alias
- **V10 变化**:保留 jsdomalias 指向 `packages/ui``packages/lib`
### ST-S2-A-14.2 写 `setup.ts`0.125d
- **产出**`@testing-library/jest-dom/vitest` + MSW server 启动/关闭 + Zustand store reset helper
### ST-S2-A-14.3 写 `renderWithProviders.tsx`0.125d
- **产出**`packages/test-utils/renderWithProviders.tsx`
- QueryClient测试用禁用 retry
- ThemeProvider
- RouterMemoryRouter
- Zustand 注入 reset 函数
- **验收**
- [ ] `pnpm --filter web test` 跑通
### ST-S2-A-14.4 占位测试验证0.125d
- **产出**`Math.test.ts`1+1=2
- **验收**
- [ ] 占位测试 1 绿
---
## T-A-15 · 示范单测ModeIndicator0.25d · 3 子任务)
### ST-S2-A-15.1 写 4 mode 渲染测试0.125d
- **覆盖**explore / generating / auditing / adjusting
- **验收**
- [ ] 4 模式 label 正确
### ST-S2-A-15.2 写 `deriveMode` 决策树测试0.0625d → 0d
- **覆盖**
- isAuditActive → auditing
- currentPlan + no audit → adjusting
- province + score → generating
- 其他 → explore
- **验收**
- [ ] 100% branch coverage
### ST-S2-A-15.3 验证 100% 覆盖0.0625d → 0d
- **命令**`pnpm test --coverage`
- **验收**
- [ ] `ModeIndicator.tsx` line + branch 各 100%
---
## T-A-16 · 示范单测SafeMarkdown0.25d · 3 子任务)
### ST-S2-A-16.1 写 XSS 注入测试0.125d
- **覆盖**
- `<script>alert(1)</script>` → 渲染为文本
- `javascript:alert(1)` → 链接被剥离
- `<img src=x onerror=alert(1)>` → onerror 剥离
- `<svg onload=alert(1)>` → onload 剥离
### ST-S2-A-16.2 写合法 Markdown 渲染测试0.0625d → 0d
- **覆盖**H1-H3 / 列表 / 链接 / 代码块 / 表格
### ST-S2-A-16.3 100% 覆盖0.0625d → 0d
---
## T-A-17 · 示范单测FormCardRHF 版 · V10 重写0.5d · 4 子任务)
> **V10 变化**FormCard 改用 React Hook Form + Zod不再用 3-step 状态机手写
### ST-S2-A-17.1 写 RHF schema 测试0.125d
- **覆盖**Zod schema 校验(分数/位次/选科)
### ST-S2-A-17.2 写 step 1→2→3 guards 测试0.125d
- **覆盖**RHF `trigger()` 校验后再前进
### ST-S2-A-17.3 写后退保留数据测试0.125d
### ST-S2-A-17.4 100% 覆盖0.125d
---
## T-A-18 · Playwright + Chromatic 配置0.5d · 3 子任务)⚠️ V10 新增
### ST-S2-A-18.1 Playwright config0.125d
- **产出**`playwright.config.ts`chromium / webkit / firefox + Vite preview server
### ST-S2-A-18.2 Chromatic 配置0.125d)⚠️ V10 新增
- **产出**`chromatic.config.json` + GitHub Action
- **关键**UI/交互层 1:1 验收的视觉基线工具
### ST-S2-A-18.3 e2e 烟雾测试0.25d
- **产出**3 个 spec 文件占位
---
## T-A-19 · 示范 e2etheme.spec0.5d · 3 子任务)
### ST-S2-A-19.1 暗/亮/系统三主题切换0.25d
- **覆盖**:点击切换 + localStorage 持久化 + 系统主题响应
### ST-S2-A-19.2 SSR/CSR 一致性0.125d
- **覆盖**:无 flash of unstyled content
### ST-S2-A-19.3 1.2s 缓动断言0.125d
---
## T-A-20 · 示范 e2enavigation.spec0.5d · 3 子任务)
### ST-S2-A-20.1 桌面 3 栏布局0.125d
### ST-S2-A-20.2 移动 48px 底部 Tab0.125d
- **V10 不变量 L2**:移动端 < 768px 单栏 + 48px 底部 Tab
### ST-S2-A-20.3 1024px 断点切换0.25d
---
## T-A-21 · OpenAPI Codegen + Zod schema 复用1.0d · 5 子任务)⚠️ V10 加强
> **V10 变化**:从单一类型生成 → 类型 + Zod schema 联合生成,业务层可用
### ST-S2-A-21.1 安装 openapi-typescript + openapi-zod-client0.125d
```bash
pnpm --filter web add -D openapi-typescript openapi-zod-client
```
### ST-S2-A-21.2 写 codegen 脚本0.25d
- **产出**`apps/web/scripts/codegen.ts`
```ts
import { generateZodClientFromOpenAPI } from 'openapi-zod-client'
import { OpenAPIV3 } from 'openapi-types'
// 拉取后端 openapi.json
// 生成 types/api.d.ts + schemas/api.ts
```
### ST-S2-A-21.3 接入 Vite plugin 自动 codegen0.25d
- **产出**`vite.config.ts` 集成 codegen
### ST-S2-A-21.4 验证生成文件 0 `any`0.25d
- **命令**`grep -r "any" apps/web/src/types/`
- **验收**
- [ ] 0 `any`(这是 V10 重写带来的关键收益)
### ST-S2-A-21.5 接入 CI 自动 codegen0.125d
- **产出**`pnpm codegen:check` 检查生成文件是否过期
---
## T-A-22 · 示范调用MSW mock server0.5d · 4 子任务)
### ST-S2-A-22.1 写 MSW handlers 骨架0.125d
### ST-S2-A-22.2 写 5 端点 mock 数据0.125d
### ST-S2-A-22.3 setup.ts 启动 server0.125d
### ST-S2-A-22.4 验证 mock 数据驱动组件渲染0.125d
---
## T-B-01 · Zustand store + 5 query hookschat · V10 重写1.0d · 5 子任务)⚠️ V10 全新
> **V10 关键**:替代原型 7 个手写 hook 中的 5 个chat 相关)
### ST-S2-B-01.1 设计 Zustand store 4 slice0.25d
- **产出**`apps/web/src/stores/chat.ts`
- `useChatStore`messages / isStreaming / mode / plan
- `useFormStore`score / rank / subjects
- `useUIStore`theme / sidebar / modal
- `useUserStore`user / preferences
- **类型**Zustand `create<T>()(devtools(persist(...)))`**0 any**
### ST-S2-B-01.2 写 `useChatQuery`0.125d
- **产出**`apps/web/src/hooks/queries/useChatQuery.ts`
- **封装**TanStack Query + Zod schema
### ST-S2-B-01.3 写 `useChatMutation`0.125d
- **封装**`POST /api/chat/send` + 流式响应处理
### ST-S2-B-01.4 写 `useChatHistoryQuery`0.125d
- **封装**`GET /api/chat/history`
### ST-S2-B-01.5 删除原型 7 个手写 hook0.25d)⚠️ V10 收益
- **命令**`rm apps/web/src/lib/{useChat,useForm,useUI,useUser,...}.ts`
- **验收**
- [ ] `grep -r "useChat(" apps/web/src/` 返回 0 结果
- [ ] 33 `any` 中相关 5 个已清零
---
## T-B-02 · TanStack Query 3 个 consultations 端点0.5d · 3 子任务)
### ST-S2-B-02.1 写 `useConsultationsQuery`0.125d
- **封装**`GET /api/consultations`
### ST-S2-B-02.2 写 `useConsultationMutation`0.125d
- **封装**`POST /api/consultations`
### ST-S2-B-02.3 写 `useConsultationUpdateMutation`0.125d
- **封装**`PATCH /api/consultations/:id`
---
## T-B-03 · TanStack Query 4 个 plans 端点0.5d · 3 子任务)
### ST-S2-B-03.1 写 `usePlansQuery`0.125d
### ST-S2-B-03.2 写 `usePlanMutation`0.125d
### ST-S2-B-03.3 写 `usePlanUpdateMutation` + `usePlanDeleteMutation`0.25d
---
## T-B-04 · RHF 重写 FormCard + assessment1.0d · 5 子任务)⚠️ V10 重写
> **V10 变化**:从受控 3-step 状态机 → RHF + Zod
### ST-S2-B-04.1 写 Zod schema0.25d
- **产出**`apps/web/src/schemas/assessment.ts`
```ts
export const AssessmentSchema = z.object({
province: z.string().min(1),
score: z.number().int().min(0).max(750),
rank: z.number().int().min(1),
subjects: z.array(z.enum(['物理', '历史', ...])).min(1),
})
```
### ST-S2-B-04.2 写 RHF `useAssessmentForm`0.25d
- **封装**`useForm<AssessmentSchema>({ resolver: zodResolver(AssessmentSchema) })`
- **3-step guards**`trigger()` 校验后再前进
### ST-S2-B-04.3 重写 `FormCard.tsx`0.25d)⚠️ V10 重写
- **V10 收益**:删除手写 3-step 状态机;`trigger('score')` 替代 `if (step === 1 && !score)` 分支
- **验收**
- [ ] UI/交互 1:1V10 不变量 C3
- [ ] `any` 数量从 8 → 0
### ST-S2-B-04.4 重写 3 个 consultation 相关组件0.125d
### ST-S2-B-04.5 删除原型 4 个手写 hook0.125d)⚠️ V10 收益
---
## T-B-05 · audit/upload 端点V10 重写1.0d · 4 子任务)
### ST-S2-B-05.1 写 `useAuditQuery`0.125d
### ST-S2-B-05.2 写 `useAuditSubmitMutation`0.25d
### ST-S2-B-05.3 写 `useUploadMutation`0.5d
- **封装**`multipart/form-data` 上传 + 进度回调
- **V10 变化**:用 TanStack Query 替代手写 fetch + AbortController
### ST-S2-B-05.4 删除原型最后 2 个手写 hook0.125d)⚠️ V10 收益
---
## T-B-06 · Chromatic 视觉基线V10 新增 · 0.5d · 3 子任务)⚠️ V10 关键
> **PM 决策2026-07-03**Playwright 视觉回归 + Chromatic
### ST-S2-B-06.1 接入 Chromatic CLI0.125d
```bash
pnpm --filter web add -D chromatic
```
### ST-S2-B-06.2 截图 8 个核心页面0.25d
- **页面**Home / Plan Detail / Chat / Form / Share / Review / Settings / Admin
### ST-S2-B-06.3 提交基线 + 接入 CI0.125d
- **产出**`pnpm chromatic` 提交基线
- **CI**`.github/workflows/web-ci.yml` 添加 chromatic 步骤
---
## T-B-07 · Vite build + bundle 体积验证0.5d · 4 子任务)⚠️ V10 新增
> **V10 关键**:从 Next.js 切到 Vite必须验证 bundle 体积和构建时间
### ST-S2-B-07.1 写 `vite.config.ts`0.125d
- **配置**:手动 chunk splitvendor / ui / pages+ 资源内联阈值
### ST-S2-B-07.2 bundle 分析0.125d
- **命令**`pnpm build -- --analyze`
- **验收**
- [ ] vendor < 200KB gzip
- [ ] 业务代码 < 100KB gzip
- [ ] 总 bundle < 300KB gzip
### ST-S2-B-07.3 构建时间验证0.125d
- **验收**
- [ ] `pnpm build` < 30s
### ST-S2-B-07.4 接入 turbo build0.125d
- **验收**
- [ ] `turbo run build` 全 monorepo < 60s
---
## V10 验收清单Sprint 2 G1 闸门)
```
G1 闸门(必须全部通过):
[ ] pnpm install 0 error
[ ] pnpm typecheck 0 error 0 any
[ ] pnpm lint 0 error 0 warningV10 重写后归零)
[ ] pnpm test 100% 覆盖 ModeIndicator / SafeMarkdown / FormCard
[ ] pnpm test:e2e 3 spec 全绿
[ ] pnpm build bundle < 300KB gzip
[ ] pnpm chromatic 视觉基线 8 页全提交
[ ] OpenAPI codegen 0 any
[ ] 原型 7 个手写 hook 全部删除
[ ] ESLint 33 any + 16 unused 全部归零
```
---
## V10 风险Sprint 2 特有)
| ID | 风险 | 等级 | 缓解 |
|---|---|---|---|
| V10-S2-R1 | Vite 切框架导致 Next.js 特有能力i18n routing失效 | 中 | Sprint 4 处理i18n 用自写 router |
| V10-S2-R2 | Zustand persist 与 SSR 冲突 | 低 | 用 `createJSONStorage(() => localStorage)` + 客户端守卫 |
| V10-S2-R3 | TanStack Query 5 与 React 19 兼容性 | 低 | 锁定 5.59.x 稳定版 |
| V10-S2-R4 | RHF + Zod 类型推导慢 | 低 | 用 `z.infer<typeof Schema>` 而非泛型 |
---
## V10 与后续 Sprint 衔接
- **Sprint 3** 直接基于 Sprint 2 的 Zustand store + TanStack Query 开发 5 个新模块
- **Sprint 4** 性能优化基线Vite build 产物 + Chromatic diff 阈值
- **Sprint 5-8** 全部使用 RHF + Zod + Zustand 模式
---
**Sprint 2 文档状态**V10 选项 B 重写完成,待启动
**总工时**9.5 人天V2 是 18 人天,节省 8.5d
**总子任务**56 个V2 是 58 个)
**下一步**PM 确认后启动 T-A-14