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

13 KiB
Raw Blame History

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 44 个 slice
数据获取 自写 useChat + fetch TanStack Query 5
表单 受控 + 3-step 状态机 React Hook Form 7 + Zod
测试 Vitest + Playwright保留
视觉回归 + ChromaticV10 新增)
总子任务 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.ts0.125d

  • 产出apps/web/vitest.config.tsjsdom env + setup 文件 + alias
  • V10 变化:保留 jsdomalias 指向 packages/uipackages/lib

ST-S2-A-14.2 写 setup.ts0.125d

  • 产出@testing-library/jest-dom/vitest + MSW server 启动/关闭 + Zustand store reset helper

ST-S2-A-14.3 写 renderWithProviders.tsx0.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.ts1+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.tschromium / 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

pnpm --filter web add -D openapi-typescript openapi-zod-client

ST-S2-A-21.2 写 codegen 脚本0.25d

  • 产出apps/web/scripts/codegen.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 any0.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
    • useChatStoremessages / isStreaming / mode / plan
    • useFormStorescore / rank / subjects
    • useUIStoretheme / sidebar / modal
    • useUserStoreuser / preferences
  • 类型Zustand create<T>()(devtools(persist(...)))0 any

ST-S2-B-01.2 写 useChatQuery0.125d

  • 产出apps/web/src/hooks/queries/useChatQuery.ts
  • 封装TanStack Query + Zod schema

ST-S2-B-01.3 写 useChatMutation0.125d

  • 封装POST /api/chat/send + 流式响应处理

ST-S2-B-01.4 写 useChatHistoryQuery0.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 写 useConsultationsQuery0.125d

  • 封装GET /api/consultations

ST-S2-B-02.2 写 useConsultationMutation0.125d

  • 封装POST /api/consultations

ST-S2-B-02.3 写 useConsultationUpdateMutation0.125d

  • 封装PATCH /api/consultations/:id

T-B-03 · TanStack Query 4 个 plans 端点0.5d · 3 子任务)

ST-S2-B-03.1 写 usePlansQuery0.125d

ST-S2-B-03.2 写 usePlanMutation0.125d

ST-S2-B-03.3 写 usePlanUpdateMutation + usePlanDeleteMutation0.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
    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 useAssessmentForm0.25d

  • 封装useForm<AssessmentSchema>({ resolver: zodResolver(AssessmentSchema) })
  • 3-step guardstrigger() 校验后再前进

ST-S2-B-04.3 重写 FormCard.tsx0.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 写 useAuditQuery0.125d

ST-S2-B-05.2 写 useAuditSubmitMutation0.25d

ST-S2-B-05.3 写 useUploadMutation0.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-03Playwright 视觉回归 + Chromatic

ST-S2-B-06.1 接入 Chromatic CLI0.125d

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.ts0.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