diff --git a/.gitignore b/.gitignore index 876f4b4..da79d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ admin/_tmp_test.txt # 本地工具元数据 / 运行产物 .serena/ +.workbuddy/ /data/alerts/ /data/portal_uploads/ /=0.0.20, @@ -86,8 +87,10 @@ node_modules/ .pnp/ .pnp.js .yarn/ +package-lock.json +npm-shrinkwrap.json -# Next.js 构建产物 +# 前端构建产物 .next/ out/ next-env.d.ts diff --git a/.workbuddy/artifacts/EXECUTION_ROADMAP_V2_2026-07-02.md b/.workbuddy/artifacts/EXECUTION_ROADMAP_V2_2026-07-02.md deleted file mode 100644 index 4e96aa4..0000000 --- a/.workbuddy/artifacts/EXECUTION_ROADMAP_V2_2026-07-02.md +++ /dev/null @@ -1,268 +0,0 @@ -# V2 任务清单执行路线图 — 92 人天 / 8 Sprint 拆分 - -> 配套文档:`FRONTEND_TASK_LIST_2026-07-02_V2.md`(136 任务 / 92 人天) -> 编制日期:2026-07-02 22:55 -> 编制人:Senior Developer(执行规划) -> 批准状态:PM 已批准追加 14 人天预算 - -## 1. 路线图设计原则 - -| 原则 | 落地方式 | -|---|---| -| **垂直切片** | 每 sprint 都有可演示产物,不做"建库 2 周后再做页面" | -| **风险前置** | B 阶段(最大风险 38 人天)放第 2-3 sprint,D 阶段视觉与 B 阶段并跑 | -| **依赖显式化** | 跨 sprint 任务标 ❗,并行任务标 ⚡ | -| **可中断性** | 每 sprint 末交付"Demoable Build",PM 可中止而不丢已投入工作 | -| **能力对齐** | 每 sprint 必跑 Lighthouse / axe / Playwright 三件套 | - -## 2. 8-Sprint 路线图(4 周一档,共 16 周 ≈ 4 个月) - -> 假设:1 个前端主程 + 1 个后端联调支持 + 0.5 个设计 review -> 单 sprint 容量:~12 人天(按 5 天 × 0.85 效率 × 2.5 人 = 10.6 人天取整) - -| Sprint | 周次 | 阶段 | 任务数 | 人天 | 关键交付 | Demoable 产物 | -|---|---|---|---|---|---|---| -| **S1** | W1 | A 前半 | 8 | 11 | monorepo + 5 组件 + CI | 5 页面原貌复刻 + CI 绿灯 | -| **S2** | W2-3 | A 后半 + B 启动 | 17 | 18 | OpenAPI codegen + 8 核心 hook | 30+ 端点 TypeScript 类型 + chat 流式 e2e | -| **S3** | W4-5 | B 主体 | 22 | 22 | 真实对接 + LLM fallback + Share | 完整 chat/plan 真实流,含 4 模 fallback | -| **S4** | W6-7 | B 收口 + C 启动 | 17 | 14 | Lighthouse ≥90 + Poster CLI 接入 | 性能报告 + Sentry + CSP 通过 | -| **S5** | W8-9 | C 收口 + D 启动 | 18 | 12 | 独立部署 + SharePanel | 灰度环境可访问 + Share 弹窗 3 状态 | -| **S6** | W10-11 | D 主体 | 22 | 11 | 18 组件 + DataQuery + Review UI | 全部页面视觉与 a11y 验收 | -| **S7** | W12-13 | D 收口 + E 启动 | 16 | 10 | axe 0 critical + Review 页 4 按钮 | 3 个 portal 页面 React 化 | -| **S8** | W14-16 | E 主体 | 14 | 8 | 6 个内部页 + Policy + Poster 渲染 | 全量运营后台可访问 | -| **合计** | 16 周 | — | **136** | **92 + 14 缓冲** | — | 生产可发版 | - -> 8 sprint 总容量 92 人天 + 14 缓冲 = 106 人天。按 4 人/16 周实际可投 = 64 人天 → **结论:单兵执行需 22 周,多人 16 周可完成。** - -## 3. Sprint 详图 - -### S1(W1)— 11 人天 · 阶段 A 前半 - -**入口条件**:PM 批准 14 人天预算完成 -**目标**:让原型在 monorepo 里能跑起来,CI 灯绿 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-A-01 monorepo 创建 | 0.5 | — | pnpm + Turborepo | -| T-A-02 收编前端原型 | 0.5 | — | 软链接 + tsconfig | -| T-A-03 共享包骨架 | 0.5 | — | @app/ui / @app/lib / @app/api | -| T-A-06 设计 token 提取 | 0.5 | ⚡ | -| T-A-07 组件库首批 5 组件 | 1.0 | — | -| T-A-21 OpenAPI Codegen 接入 | 1.0 | — | V2 跑出 30+ 端点类型 | -| T-A-23 CI 工作流 | 1.5 | — | lint + test + build | -| T-A-04 tsconfig 共享 | 0.5 | ⚡ | -| T-A-14 Vitest 骨架 | 0.5 | ⚡ | -| T-A-18 Playwright 骨架 | 0.5 | ⚡ | - -**退出条件**: -- [ ] `pnpm dev` 可启动 `apps/web` 5 个页面 -- [ ] `pnpm test` 跑通 -- [ ] `pnpm build` 通过 -- [ ] GitHub Actions 绿 -- [ ] **无 mock 数据残留**(原型中的 `chat-fixtures.ts` 移走) - ---- - -### S2(W2-3)— 18 人天 · A 后半 + B 启动 - -**入口条件**:S1 退出 -**目标**:8 个核心 hook 真实化,OpenAPI 类型贯通 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-A-22 OpenAPI 类型生成完成 | 1.0 | — | 30+ 端点全部生成 | -| T-A-08-T-A-13 6 组件扩展 | 3.0 | ⚡ | 12 组件总量 | -| T-A-15 单元测试 50% | 1.0 | ⚡ | -| T-A-19 Playwright 首批 3 e2e | 1.0 | ⚡ | -| T-A-16 集成测试 30% | 1.0 | ⚡ | -| T-A-20 Storybook | 1.0 | ⚡ | -| **T-B-01~08** chat 流式对接 | 4.0 | — | SSE + 重连 + 取消 | -| **T-B-09~14** plan/audit 真实化 | 3.0 | — | -| T-A-05 ESLint/Prettier 收口 | 0.5 | ⚡ | -| T-A-17 性能预算 CI | 0.5 | ⚡ | - -**退出条件**: -- [ ] Chat 流式 e2e 真实后端可跑通 -- [ ] Plan/Audit 8 hook 全部对接真实 API -- [ ] OpenAPI 30+ 端点无 `any` 残留 -- [ ] 单元测试覆盖率 ≥50% - ---- - -### S3(W4-5)— 22 人天 · B 主体 - -**入口条件**:S2 退出 -**目标**:全部 35 端点对接完成,含 5 大 V2 新模块 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| **T-B-15~18** Share Link API 对接 | 3.0 | — | 4 端点 + QR 渲染 ❗ | -| **T-B-19~22** Data Query 4 端点 | 3.0 | — | -| **T-B-23~26** Review Flow 4 端点 | 3.0 | — | step1 + 冲稳保按钮 | -| **T-B-27~30** LLM 增强 + 多模型 fallback | 4.0 | — | 4 模 + SSE 重连 ❗ | -| **T-B-31~35** Poster 渲染 + 政策页 | 3.0 | — | -| T-B-11 错误边界全局化 | 1.0 | ⚡ | -| T-B-12 Loading/Toast 统一 | 1.0 | ⚡ | -| T-B-13 离线兜底策略 | 1.0 | ⚡ | -| T-B-14 表单 RHF+Zod 接入 | 2.0 | ⚡ | -| T-B-07 mock 数据清理 | 0.5 | ⚡ | -| T-B-08 端到端联调 | 0.5 | ⚡ | - -**退出条件**: -- [ ] 35 端点 e2e 全绿 -- [ ] LLM fallback 4 模模拟可切换 -- [ ] Share link 生成/打开可闭环 -- [ ] 全部 mock 移除 -- [ ] **风险闸门**:R-NEW-7(LLM 超时)实测 < 30s - ---- - -### S4(W6-7)— 14 人天 · B 收口 + C 启动 - -**入口条件**:S3 退出 -**目标**:性能达标,监控接入 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-B 收尾 + Lighthouse 调优 | 3.0 | — | ≥90 性能分 | -| **T-C-01~04** 独立部署 + Sentry | 2.0 | — | -| **T-C-05~07** CSP + 安全头 | 1.5 | ⚡ | -| **T-C-08~10** Poster CLI 接入 | 2.0 | — | 风险 R-NEW-8 ❗ | -| **T-C-11~12** 灰度发布 + 灰度路由 | 1.5 | ⚡ | -| T-C-13 性能监控 RUM | 1.0 | ⚡ | -| T-C-14 错误追踪 | 1.0 | ⚡ | -| T-C-15 部署文档 | 1.0 | ⚡ | -| D 阶段预研:design pass (R-NEW-9) | 1.0 | — | 风险闸门 | - -**退出条件**: -- [ ] Lighthouse Performance ≥90 / A11y ≥95 -- [ ] Sentry 接收到测试事件 -- [ ] CSP 报告 0 violation -- [ ] 灰度发布可路由 -- [ ] **风险闸门**:R-NEW-8(Poster CLI 镜像验证)通过 - ---- - -### S5(W8-9)— 12 人天 · C 收口 + D 启动 - -**入口条件**:S4 退出 + Design pass 完成 -**目标**:18 组件 + SharePanel 基础 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-C 收尾 2 任务 | 1.0 | — | -| **T-D-01~05** 5 组件 design system | 2.5 | ⚡ | -| **T-D-06~10** 5 业务组件 | 2.5 | ⚡ | -| **T-D-11~14** 4 反馈组件 | 2.0 | ⚡ | -| **T-D-15~18** 4 容器组件 | 2.0 | ⚡ | -| T-D-19 SharePanel 弹窗 | 2.0 | — | 3 状态 (生成/复制/失效) | - -**退出条件**: -- [ ] 18 组件 Storybook 全部可查 -- [ ] SharePanel 弹窗 3 状态 e2e 通过 -- [ ] **风险闸门**:R-NEW-9(Portal 视觉一致性)已对齐 - ---- - -### S6(W10-11)— 11 人天 · D 主体 - -**入口条件**:S5 退出 -**目标**:DataQuery + Review UI 全部页面 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| **T-D-20~23** DataQuery 4 页面 | 2.0 | — | -| **T-D-24~27** Review UI 4 状态 | 2.0 | — | step1 + 冲稳保 + token 加载 | -| T-D-28 主题切换持久化 | 0.5 | ⚡ | -| T-D-29 暗色模式适配 | 0.5 | ⚡ | -| T-D-30 焦点环统一 | 0.5 | ⚡ | -| T-D-31 prefers-reduced-motion | 0.5 | ⚡ | -| T-D-32 键盘导航 | 0.5 | ⚡ | -| T-D-33~35 视觉 QA 3 轮 | 1.5 | ⚡ | -| T-D-36 风险面板 | 1.0 | ⚡ | -| T-D-37 LLM 失败兜底 UI | 1.0 | ⚡ | -| T-D-38 错误页统一 | 1.0 | ⚡ | - -**退出条件**: -- [ ] DataQuery 4 页面真实数据流通 -- [ ] Review 4 状态全跑通 -- [ ] axe-core 0 critical issue -- [ ] prefers-reduced-motion 全部组件支持 - ---- - -### S7(W12-13)— 10 人天 · D 收口 + E 启动 - -**入口条件**:S6 退出 -**目标**:a11y 完整合规,3 个 portal 页面 React 化 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-D 收尾 3 任务 (axe 0 critical / 屏幕阅读器测试 / WCAG 报告) | 1.5 | — | -| **T-E-01~04** 后台登录 + 导航 | 2.0 | — | -| **T-E-05~08** Share 管理 + Data Query 后台 | 2.0 | — | -| **T-E-09~12** Review 后台 + Poster 渲染 | 2.0 | — | - -**退出条件**: -- [ ] axe-core 0 critical,0 serious -- [ ] 3 个 portal 页面(运营 / Share / Data Query)可访问 -- [ ] 后台登录 + RBAC 工作 - ---- - -### S8(W14-16)— 8 人天 · E 主体 - -**入口条件**:S7 退出 -**目标**:6 个内部页 + Policy + Poster 完整 - -| 任务 | 人天 | 并行 | 备注 | -|---|---|---|---| -| T-E-13~16 我的订单 + 我的报告 | 1.5 | — | -| T-E-17~20 Policy 5 页 (服务/隐私/退订/合规/关于) | 2.0 | — | -| T-E-21~23 Poster 渲染页 + 下载 | 1.5 | — | -| T-E-24 联调收口 + 灰度上线 | 3.0 | — | - -**退出条件**: -- [ ] 6 个内部页全部可访问 -- [ ] 5 个 Policy 页发布 -- [ ] Poster 可生成可下载 -- [ ] **生产可发版** ✅ - -## 4. 关键风险闸门(不能跳过) - -| 闸门 | 位置 | 失败动作 | -|---|---|---| -| **G1**: OpenAPI 类型生成无 `any` | S2 末 | 暂停 S3,补 codegen 配置 | -| **G2**: LLM fallback 4 模实测 | S3 末 | 暂停 S4,与后端对齐超时策略 | -| **G3**: Lighthouse ≥90 | S4 末 | 暂停 S5,先做性能调优 | -| **G4**: R-NEW-8 Poster CLI 镜像验证 | S4 末 | 暂停 S5,修正 Dockerfile | -| **G5**: axe 0 critical | S7 末 | 暂停 S8,先做 a11y | -| **G6**: R-NEW-9 视觉一致 | S5 末 | 暂停 S6,先做 design pass | - -## 5. 团队配置推荐 - -| 角色 | 数量 | 占比 | 主要 sprint | -|---|---|---|---| -| 前端主程(Next.js + TS) | 1 | 60% | 全程 | -| UI/交互设计师 | 0.3 | 15% | S1 / S4-S6 | -| 后端联调 | 0.3 | 15% | S2-S4 | -| QA / a11y | 0.2 | 10% | S4-S7 | -| DevOps | 0.1 | 5% | S4 | - -## 6. 监控与汇报机制 - -- **每日**:sprint board 更新(按任务 ID 标 in_progress / done) -- **每 sprint 末**:Demoable Build + 风险闸门报告(30 分钟会议) -- **S2/S4/S6 末**:里程碑汇报(含 Lighthouse / axe / e2e 截图) -- **S8 末**:上线发布 + 复盘文档 - -## 7. 与上游文档的对齐 - -- 任务清单基线:`FRONTEND_TASK_LIST_2026-07-02_V2.md` -- 技术决策依据:`FRONTEND_REFACTOR_PLAN_2026-07-02.md` -- 质量基线:`REVIEW_REPORT_2026-07-02_SENIOR_DEVELOPER.md` -- 历史对照:`docs/REVIEW_REPORT_2026-06-19_SYSTEMATIC_PRODUCTION_READINESS_REVIEW.md` - ---- - -**路线图编制完成。请确认是否进入 S1 启动。** diff --git a/.workbuddy/artifacts/FRONTEND_REFACTOR_PLAN_2026-07-02.md b/.workbuddy/artifacts/FRONTEND_REFACTOR_PLAN_2026-07-02.md deleted file mode 100644 index 1937358..0000000 --- a/.workbuddy/artifacts/FRONTEND_REFACTOR_PLAN_2026-07-02.md +++ /dev/null @@ -1,905 +0,0 @@ -# 前端重构方案 · Frontend Refactoring Plan - -> 评审人:**Frontend Developer (资深前端开发工程师)** -> 日期:2026-07-02 -> 输入约束: -> - **UI 与交互**:严格采用 `前端原型代码/` 中已落地的设计(不容许外观/行为回归) -> - **技术栈**:由前端专家决定(推荐方案附后) -> 上游依据:`REVIEW_REPORT_2026-07-02_SENIOR_DEVELOPER.md`(V2 全面版) -> 上游审查:4,114 行 TS/TSX、8 页 / 7 组件 / 8 hooks / 247 行 design-system.css 已扫读 - ---- - -## 0. TL;DR - -**核心判断:原型代码的 UI 与交互已经过一轮有意识的设计打磨(247 行 design-system、三态主题、消息路由模式、信息收集进度条、冲稳保三 Tab、风险徽章、紧凑移动端),可以直接收编为 monorepo 中的 `apps/web` 子项目,并补齐三件缺失物:测试基础设施 / 真实后端对接 / 可维护的 UI 组件库。** - -**技术栈决策(前端专家决定)** - -| 层 | 选型 | 理由 | -|---|---|---| -| 框架 | **Next.js 16.x(App Router)** | 沿用原型,避免重新评估 | -| UI 运行时 | **React 19.x** | 沿用原型,与 Next 16 协同验证 | -| 语言 | **TypeScript 5.x(strict)** | 沿用原型 | -| 样式 | **Tailwind CSS 4 + design-system.css tokens** | 沿用原型 + CSS 变量映射 | -| 客户端状态 | **Zustand(4.x)** | hook 数量从 8 → 20+ 时避免 prop drilling | -| 服务端状态 | **TanStack Query 5.x** | 替换 localStorage 缓存 + 真实 API 同步 | -| 表单 | **React Hook Form + Zod** | FormCard 已 374 行,缺类型安全 | -| API 客户端 | **openapi-typescript-codegen** | 基于 `admin/app.py` OpenAPI 自动生成 | -| Markdown | **react-markdown 10 + rehype-sanitize 6 + remark-gfm 4** | 沿用(XSS 安全) | -| 单元测试 | **Vitest 2.x + React Testing Library** | 与 Vite 生态契合 | -| E2E 测试 | **Playwright 1.5x** | 跨浏览器,移动/桌面双视图 | -| 视觉回归 | **Chromatic / Storybook Test** | design token 改动安全网 | -| 国际化 | **next-intl 3.x** | `lang="zh-CN"` 起步,预留 en-US | -| 监控 | **Sentry(前端) + Vercel Analytics / Web Vitals** | Core Web Vitals 预算 | -| 构建 | **Turborepo + pnpm workspaces** | monorepo 编排 | -| 包管理 | **pnpm 9.x** | 节省磁盘,提升 monorepo 性能 | - -**为什么不用 Vite + React Router 替代 Next.js?** ① 沿用原型决策可避免一轮"无收益的技术换血";② Next 16 的 RSC + Server Actions 对未来**公开门户 SEO 优化**(`/pricing` `/privacy`)有结构性优势;③ 部署可走 CloudStudio 预览或自托管,对小团队更轻。 - -**为什么加 Zustand?** ① 当前 8 个 hook 已经够用,但 B 阶段接入真实后端后会膨胀;② TanStack Query 已覆盖 90% 服务端状态,Zustand 只承担"跨页 UI 状态"(主题、侧边栏折叠、对比选择);③ 比 Redux Toolkit 少 70% 模板代码。 - -**为什么加 TanStack Query 而非 SWR?** ① mutation / optimistic update / infinite query 能力更全;② DevTools 体验对调试 `useChat` 状态机更友好。 - ---- - -## 1. UI / 交互不变量(强约束) - -> **本节是迁移的"宪法"——所有后续 PR 必须满足这些不变量,不允许任何"看起来更优"的越权修改。** - -### 1.1 视觉设计系统(来自 `前端原型代码/src/styles/design-system.css`) - -#### 1.1.1 品牌色(9 阶) -``` ---brand-50 #eff6ff --brand-500 #3b82f6 --brand-900 #1e3a8a ---brand-gradient: linear-gradient(135deg, --brand-600 → #7c3aed) -``` -**保留**:原型已有 `bg-gradient-to-br from-blue-600 to-purple-600`(Logo / 头像)—— 不替换为单色。 - -#### 1.1.2 业务语义色 -``` -录取概率: rush #f97316 / stable #3b82f6 / safe #22c55e -风险等级: high #ef4444 / medium #eab308 / low #22c55e -风险背景: high-bg #fef2f2 / medium-bg #fefce8 / low-bg #f0fdf4 -风险边框: high-border #fecaca / medium-border #fef08a / low-border #bbf7d0 -``` -**保留**:暗色主题下需调整 `--color-risk-*-bg` 为 `rgba(..., 0.15)` 透明度(原型已做,迁移时不动)。 - -#### 1.1.3 排印 / 间距 / 圆角 / 动效 -``` -font-sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei"… -text: 7 阶(xs 0.75 / sm 0.875 / base / lg / xl / 2xl / 3xl) -space: 11 阶(0 ~ 16),4px 基准 -radius: 6 阶(sm 0.375 / md 0.5 / lg 0.75 / xl 1 / 2xl 1.25 / full 9999) -transition: 4 阶(fast 150ms / normal 250ms / slow 350ms / spring cubic-bezier(0.34, 1.56, 0.64, 1)) -``` -**保留**:原型已有"圆角 2xl = 卡片","full = 胶囊徽章"—— 不替换为 shadcn 的 `rounded-md` 默认值。 - -### 1.2 三态主题系统(来自 `lib/theme.ts` + `components/shared/ThemeToggle.tsx`) - -| 模式 | 行为 | 持久化 | 触发 | -|---|---|---|---| -| `light` | `data-theme="light"` | localStorage `theme=light` | 用户点击 ☀️ | -| `dark` | `data-theme="dark"` | localStorage `theme=dark` | 用户点击 🌙 | -| `system` | 移除 `data-theme` 属性,由 `@media (prefers-color-scheme: dark)` 接管 | localStorage 移除 key | 用户点击 💻 | - -**关键不变量**: -- ✅ **`initThemeScript()` 必须在 `
` 内联同步执行**(防 FOUC 闪白)—— `app/layout.tsx:20` 已挂载 -- ✅ **ThemeToggle 是 `role="radiogroup"`,三个按钮 `role="radio"` `aria-checked`** —— 不能改成下拉框 -- ❌ **不允许"跟随系统"自动覆盖 localStorage** —— 用户的显式选择必须保留 -- ❌ **不允许去掉 `suppressHydrationWarning`** —— 这是闪白防护的必要条件 - -### 1.3 消息路由模式(来自 `components/ChatMessage.tsx`) - -```ts -type MessageType = 'text' | 'form_card' | 'plan_card' | 'career_card' | 'audit_report' | 'file_upload_prompt' | 'system'; -``` - -**ChatMessage 不允许变成"自由渲染"** —— 必须按 `message.type` 路由到对应子组件(PlanCard / CareerCard / AuditReportCard / FormCard / FileUploadPrompt / SafeMarkdown)。 - -**不变量**: -- ✅ 用户消息:`flex justify-end` + `bg-blue-600 text-white` + 圆角 `rounded-2xl rounded-br-md` -- ✅ AI 消息:`flex items-start gap-3` + AI 头像(蓝→紫渐变) + 圆角 `rounded-2xl rounded-tl-md` -- ✅ 系统消息:居中灰底胶囊 -- ❌ **不允许出现"消息卡片之间不留 16px 间距"** —— `mb-4 px-4` - -### 1.4 模式指示器(来自 `components/navigation/ModeIndicator.tsx`) - -```ts -type ChatMode = 'explore' | 'generating' | 'auditing' | 'adjusting'; -``` - -**`deriveMode(profile, currentPlan, isAuditActive)` 决策树**: -``` -isAuditActive → 'auditing' ✅ -currentPlan → 'adjusting' 🔄 -profile.province && profile.score → 'generating' 📊 -其他 → 'explore' 🔍 -``` - -**不变量**: -- ✅ 4 个模式 4 套配色(蓝/紫/橙/绿) -- ✅ 必须放在聊天区 header 左侧(`Sidebar` 之后),移动端可见 -- ❌ **不允许合并为单一 status bar** —— 4 个模式对应 4 类 AI 行为,用户需要可观察的提示 - -### 1.5 信息收集进度条(来自 `components/shared/ProgressSteps.tsx`) - -```ts -steps = [ - { key: 'province', label: '省份' }, - { key: 'score', label: '分数' }, - { key: 'subjects', label: '选科' }, -]; -``` - -**触发条件**:`!hasCoreInfo && hasAnyInfo`(部分信息已收齐时显示,引导用户继续)。 -**不变量**: -- ✅ 3 个圆点 + 连接线(`bg-green-500` / `bg-brand-500 ring-2 ring-brand-200` / `bg-gray-200`) -- ✅ 进度容器:`bg-white/90 border border-gray-100 rounded-full px-3 py-1.5 shadow-sm` -- ❌ **不允许换成 stepper 横向长条** —— 紧凑胶囊是关键 - -### 1.6 PlanCard 三 Tab(来自 `components/PlanCard.tsx`) - -**结构**(**不可破坏**): -1. Header:标题 `📊 你的志愿方案` + 用户画像摘要(省份·选科·分数·位次) -2. **TABS:冲刺/稳妥/保底**(mobile 用单字 `冲/稳/保`,desktop 用全名) -3. School List:每行展开概率条 + 风险徽章 + 预估分数 -4. Footer:保存 / 导出 / 调整 三按钮 - -**不变量**: -- ✅ Tabs 用 `role="tablist" aria-label="志愿方案分类"` -- ✅ 当前 Tab 文字色 = TABS 配置色(`text-orange-600` / `text-blue-600` / `text-green-600`),下方有 0.5 高亮线 -- ✅ 概率条 `role="progressbar" aria-valuenow aria-valuemin aria-valuemax aria-label="录取概率 X%"` -- ✅ 颜色映射:`p >= 80` 绿、`50 ≤ p < 80` 蓝、`30 ≤ p < 50` 黄、`p < 30` 橙 -- ✅ 风险徽章三色:`低` 绿、`中` 黄、`高/较高` 红 -- ❌ **不允许把"冲刺/稳妥/保底"合并为一个 list** —— 这是用户认知的核心分组 -- ❌ **不允许去掉"调整"徽章动画**(`animate-pulse` 表示"已更新") - -### 1.7 移动端断点(来自 `app/page.tsx` + `Sidebar.tsx` + `MobileNav.tsx`) - -| 视口 | 行为 | -|---|---| -| `< 1024px` | 显示 `MobileNav`(底部 3 Tab:对话/方案/记录),隐藏 `Sidebar`,Logo 移动端可见 | -| `>= 1024px` | 显示 `Sidebar`(280px 宽,左侧固定),隐藏 `MobileNav`,导航链接在 header | -| `>= 1024px` header | 右侧导航 `💬 咨询记录` `📋 我的方案` | -| `< 1024px` header | 右侧新建对话按钮 ✨ | - -**不变量**: -- ✅ `Sidebar` 仅在 `lg:flex` 时渲染(不是 `hidden`,避免 SSR 闪烁) -- ✅ `MobileNav` 底部 padding 使用 `env(safe-area-inset-bottom, 0px)`(iPhone 安全区) -- ✅ `MobileNav` Tab 高度 ≥ 48px(min-h-[48px])—— WCAG 2.5.5 触控目标 - -### 1.8 输入区交互(来自 `app/page.tsx:215-255`) - -**不变量**: -- ✅ 文本域:自动撑高,最大 180px(`scrollHeight` 监听) -- ✅ Enter 发送 / Shift+Enter 换行 -- ✅ 发送按钮:空内容时灰底禁用,有内容时蓝底阴影 -- ✅ 附件按钮:折叠时灰、展开时蓝底高亮 -- ✅ 底部小字 "AI辅助决策,请以官方信息为准" 仅桌面端可见(`hidden lg:block`) -- ❌ **不允许换成"按下回车不换行直接发送"** 的非 textarea 实现 - -### 1.9 快速提示(来自 `app/page.tsx:60-67`) - -**三态动态推荐**: -- `hasCoreInfo`(省份+分数+选科都有)→ `['生成志愿方案', '审核我的方案', '调整方案(只看珠三角)', '了解人工智能工程师']` -- `hasAnyInfo`(部分有)→ `['我是广东省的', '物理类考生', '了解一下平行志愿', '审核我的方案']` -- `!hasAnyInfo`(全无)→ `['了解人工智能工程师', '平行志愿怎么录取', '广东省物理类考生', '审核我的方案']` - -**不变量**: -- ✅ 水平滚动 `overflow-x-auto scrollbar-none` -- ✅ 胶囊样式 `bg-gray-50 border border-gray-200 rounded-full` -- ✅ 点击 → 写入 input 并 focus(不直接发送) - -### 1.10 表单分步(来自 `components/FormCard.tsx`) - -**3 步**:`basic` → `subjects` → `prefs` -**不变量**: -- ✅ 步骤按钮:当前 active 蓝底,未完成灰色禁用,已完成绿底可回退 -- ✅ 不允许跳过未完成步骤(`goToStep` 守卫) -- ✅ 字段触摸后才显示验证(`touched` 状态) -- ✅ 顶部"更喜欢直接聊?也可以在对话框里说…"提示条 -- ✅ 省份下拉:31 个省级行政单位(香港/澳门/台湾按国家级处理为缺省"广东"映射) -- ❌ **不允许改成单页长表单** —— 三步法是用户认知负荷的关键保护 - -### 1.11 上传条(来自 `components/UploadBar.tsx`) - -**4 种模式**: -| 模式 | 接受类型 | 大小限制 | 颜色 | -|---|---|---|---| -| Excel | `.xlsx, .xls` | 5 MB | 绿 | -| 图片 | `image/*` | 10 MB | 紫 | -| PDF | `.pdf` | 10 MB | 橙 | -| 粘贴 | 无 | 无 | 蓝(切换为 textarea) | - -**不变量**: -- ✅ 默认折叠,点击"展开 ▼"展开网格 -- ✅ 粘贴模式:textarea + 取消按钮 + 提交按钮 -- ✅ 文件大小超限:`alert` 弹出错误(**保留**,与现有 7-7-7 规则一致——不要换 toast) -- ❌ **不允许使用 modal**(用户已在聊天上下文,弹窗打断对话) - -### 1.12 无障碍(必须保留) - -| 项 | 实现 | 不可移除原因 | -|---|---|---| -| 全局焦点指示器 | `*:focus-visible` `outline: 2px solid var(--brand-500)` | 键盘导航唯一可见反馈 | -| 减少动画偏好 | `@media (prefers-reduced-motion: reduce)` 覆盖所有 `animation-duration` | 前庭功能障碍用户 | -| 移动端 Tab 高度 | `min-h-[48px]` | WCAG 2.5.5 触控目标 | -| 概率条 ARIA | `role="progressbar" aria-valuenow` | 屏幕阅读器可读 | -| 风险徽章 ARIA | `role="alert"` | 风险即时播报 | -| Tabs ARIA | `role="tablist" / "tab" aria-selected` | 屏幕阅读器路由 | - ---- - -## 2. 现状盘点 - -### 2.1 文件清单(`前端原型代码/src/`) - -``` -src/ 4114 行(已扫读) -├── app/ 8 页面 -│ ├── layout.tsx 33 RootLayout + theme init -│ ├── page.tsx 270 聊天对话 -│ ├── globals.css 90 全局基础样式 -│ ├── assessment/page.tsx 350 Holland RIAS 测试 -│ ├── consultations/page.tsx 253 咨询记录 -│ ├── plans/page.tsx 308 我的方案 -│ ├── plans/[id]/page.tsx 167 方案详情 -│ ├── plans/compare/page.tsx 154 方案对比 -│ └── about/page.tsx 232 关于/帮助 -├── components/ 7 组件 -│ ├── FormCard.tsx 374 分步表单 -│ ├── AuditReportCard.tsx 159 审核报告卡 -│ ├── PlanCard.tsx 192 方案卡 -│ ├── ChatMessage.tsx 93 消息路由 -│ ├── UploadBar.tsx 141 上传条 -│ ├── CareerCard.tsx 70 职业卡 -│ ├── FileUploadPrompt.tsx 36 上传提示 -│ ├── navigation/ 3 -│ │ ├── Sidebar.tsx 115 -│ │ ├── MobileNav.tsx 75 -│ │ └── ModeIndicator.tsx 74 + deriveMode -│ └── shared/ 3 -│ ├── ThemeToggle.tsx 50 -│ ├── SafeMarkdown.tsx 103 -│ └── ProgressSteps.tsx 89 -├── lib/ 8 hooks + 1 utility -│ ├── theme.ts 68 -│ ├── useChat.ts 543 编排 6 个子 hook -│ ├── useConsultation.ts 167 -│ ├── useMessages.ts 88 -│ ├── usePlan.ts 89 -│ ├── useProfile.ts 87 -│ ├── useAudit.ts ? -│ └── useSimulation.ts 43 -└── styles/ - └── design-system.css 247 设计系统 token -``` - -### 2.2 强项(不可破坏) - -1. **完整 design system**(247 行 CSS 变量 + Tailwind 4 @theme 映射) -2. **三态主题**(`initThemeScript` 同步注入 + `data-theme` 属性 + `prefers-color-scheme` 兜底) -3. **消息路由模式**(`ChatMessage` 按 `message.type` 派发,避免单组件膨胀) -4. **XSS 防护**(`SafeMarkdown` + `rehype-sanitize`,`dangerouslySetInnerHTML` 仅 1 处受控字符串) -5. **WCAG 起步**(`focus-visible` + `prefers-reduced-motion` + ARIA roles) -6. **`tsconfig.strict`** + `moduleResolution: "bundler"` + `@/*` 路径别名 -7. **桌面/移动 双视图**(`Sidebar` + `MobileNav` + header 响应式导航) - -### 2.3 弱项(必须修复) - -| 弱项 | 影响 | 严重度 | -|---|---|---| -| **零测试覆盖** | 重构无安全网 | **P0** | -| **零后端对接**(`grep fetch/axios` 0 命中) | 迁到生产后所有 hook 要重写 | **P0** | -| **localStorage 存敏感信息**(`useConsultation` / `plans/page.tsx`) | 跨设备不同步 / 用户清除数据丢失 | **P1** | -| **组件库不完整**(无 Button / Input / Dialog / Toast) | 重复实现、风格漂移 | **P1** | -| **mock 数据硬编码**(`useChat.ts:42-90` 广东省 620 物理类) | 无法演示其他场景 | **P2** | -| **无 i18n 框架**(文案硬编码中文) | 海外/多语种扩展困难 | **P2** | -| **无 Zustand / Jotai**(8 hook 还撑得住,20+ 时混乱) | 状态管理可维护性 | **P2** | -| **无 CI**(`typecheck / lint / build` 失败无门禁) | 协作摩擦 | **P0** | - ---- - -## 3. 目标架构 - -### 3.1 Monorepo 结构 - -``` -gaokao-volunteer-system/ -├── apps/ -│ ├── admin/ # FastAPI 后端(不变) -│ └── web/ # Next.js 前端(收编自 前端原型代码/) -├── packages/ -│ ├── ui/ # 设计系统 + 组件库 -│ │ ├── tokens/ # design-system.css + theme.ts -│ │ ├── components/ # Button/Input/Card/Dialog/Toast/... -│ │ ├── navigation/ # Sidebar/MobileNav/ModeIndicator -│ │ ├── chat/ # ChatMessage/PlanCard/CareerCard/... -│ │ └── forms/ # FormCard -│ ├── api-client/ # OpenAPI 生成的 TS 客户端 -│ │ ├── src/ -│ │ ├── openapi.json # 来自 FastAPI /openapi.json -│ │ └── codegen.config.ts -│ ├── hooks/ # 跨页业务 hook(useChat/useProfile/usePlan/...) -│ ├── store/ # Zustand stores(theme/ui/compare) -│ ├── i18n/ # next-intl 配置 + 词条 -│ ├── test-utils/ # Vitest 配置 + RTL 包装 + MSW handlers -│ └── tsconfig/ # 共享 tsconfig.base.json -├── turbo.json -├── pnpm-workspace.yaml -└── package.json -``` - -**关键决策**: -- ✅ 收编 `前端原型代码/` 为 `apps/web/`(删 `前端原型代码/` 目录) -- ✅ design-system.css 提到 `packages/ui/tokens/`,被 `apps/web` + 未来 `apps/admin-panel` 共享 -- ✅ 业务 hook(useChat 编排的 6 个子 hook)放进 `packages/hooks/` -- ❌ **不**用 nx/lerna —— Turborepo 任务编排已够用 - -### 3.2 数据流(真实后端对接后) - -``` -┌──────────────┐ fetch ┌──────────────────┐ TanStack ┌────────────┐ -│ React 组件 │ ──────→ │ packages/api- │ Query │ FastAPI │ -│ (apps/web) │ ←────── │ client (类型化) │ ←────────→ │ (admin/) │ -└──────────────┘ 缓存 └──────────────────┘ mutation └────────────┘ - │ │ - │ localStorage │ - ▼ (offline cache only) ▼ -┌──────────────┐ ┌────────────┐ -│ Zustand UI │ │ SQLite / │ -│ Store │ │ Postgres │ -└──────────────┘ └────────────┘ -``` - -**关键原则**: -- 服务端数据**单一真相源** = FastAPI DB -- localStorage 仅作"乐观更新回滚"和"离线浏览缓存" -- Zustand 只承担"跨页 UI 状态"(主题 / 侧边栏折叠 / 方案对比选择) -- TanStack Query 管理"服务端状态的客户端缓存" - -### 3.3 路由对照表(前端原型 vs 后端现状) - -| 前端原型路由 | 对应后端 | 优先级 | -|---|---|---| -| `/`(对话) | **新增** `POST /api/chat/send` `GET /api/chat/history` | **P0**(首屏) | -| `/assessment`(Holland 测试) | **新增** `POST /api/assessment` | P2 | -| `/consultations` | **新增** `GET /api/consultations` | P1 | -| `/plans` | **新增** `GET /api/plans` | P1 | -| `/plans/[id]` | **新增** `GET /api/plans/{id}` | P1 | -| `/plans/compare` | 客户端计算(不调后端) | P1 | -| `/about` | 静态文案 | P2 | -| **(后端已有)`/pricing`** | `GET /api/public/services` | P1 | -| **(后端已有)`/checkout/{v}`** | `POST /api/public/orders` `POST /api/public/payments/*` | P1 | -| **(后端已有)`/portal/{token}/info`** | `POST /portal/{token}/info` | P1 | -| **(后端已有)`/portal/{token}/status`** | `GET /portal/{token}/status` | P1 | -| **(后端已有)`/portal/{token}/report`** | `GET /portal/{token}/report` | P1 | - -**说明**:后端已有但前端原型没有的页面(portal 流程),**不**在第一阶段收编——属于商业化产品,不属于"AI 志愿助手"主轴。 - ---- - -## 4. 5 阶段实施路径(与 V2 报告衔接) - -> 本节是对 `REVIEW_REPORT_2026-07-02_SENIOR_DEVELOPER.md` §9 5 阶段方案的前端**细化版**。所有"页面/组件/hook"粒度任务都已落到本方案。 - -### 4.1 阶段 A — 基础设施(10-12 人天) - -#### A.1 Monorepo 收编(1 人天) -- 创建 `apps/web/` 目录,把 `前端原型代码/src/` 整目录移入 -- 根目录加 `pnpm-workspace.yaml` + `turbo.json` -- 加根 `package.json`(devDependencies: turbo, prettier, eslint-config-prettier) -- 验证:`pnpm --filter web dev` 启动原 dev server 无错 - -#### A.2 共享 tsconfig(0.5 人天) -- 创建 `packages/tsconfig/base.json`(strict + bundler resolution + paths) -- `apps/web/tsconfig.json` extends base -- `packages/ui/tsconfig.json` extends base(再加 `composite: true`) -- 加 `tsc --noEmit` 到 web 的 lint 脚本 - -#### A.3 设计系统提取(2 人天) -- 把 `src/styles/design-system.css` 移到 `packages/ui/tokens/design-system.css` -- 把 `src/lib/theme.ts` 移到 `packages/ui/tokens/theme.ts` -- 在 `packages/ui/package.json` 暴露 `tokens.css` + `theme.ts` -- 在 `apps/web/app/globals.css` `@import "@gaokao/ui/tokens.css"` -- 在 `apps/web/app/layout.tsx` 调用 `import { initThemeScript } from "@gaokao/ui/tokens/theme"` - -#### A.4 UI 组件库起步(2.5 人天) -新增 5 个基础组件(**不**改外观,仅抽公共样式): - -| 组件 | 来源 | 说明 | -|---|---|---| -| `Button` | 原型分散在多处 | primary/secondary/ghost/danger 四种 variant | -| `Input` | FormCard 文本框 | 含 error/helpText 状态 | -| `Select` | FormCard 下拉 | 同上 | -| `Card` | PlanCard/AuditReportCard/CareerCard 共享 | 圆角 2xl + 阴影 sm + border | -| `Badge` | 风险徽章 | 三色 + 三等级 | -| `Tabs` | PlanCard Tab | 复用 role="tablist" | - -每个组件必须配套:① Vitest 单测;② Storybook story(CSF 3.0);③ tsdoc 注释。 - -#### A.5 测试基础设施(2 人天) -- `pnpm add -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom` -- 创建 `packages/test-utils/`: - - `vitest.config.ts`(jsdom + globals + setup) - - `setup.ts`(jest-dom matchers + MSW server) - - `renderWithProviders.tsx`(QueryClient + Theme + Router 包装) -- 加 3 个示范单测: - - `ModeIndicator.test.tsx`(4 模式渲染) - - `deriveMode.test.ts`(决策树全覆盖) - - `SafeMarkdown.test.tsx`(XSS 注入:`` 不执行) - -#### A.6 Playwright e2e 骨架(1.5 人天) -- `pnpm add -D @playwright/test` -- 创建 `apps/web/e2e/`: - - `playwright.config.ts`(chromium + webkit + 移动 viewport) - - `chat.spec.ts`(**示范用例**:发送消息 → 收到 AI 回复 → 切换主题) - - `theme.spec.ts`(验证三种模式切换无闪白) - - `navigation.spec.ts`(桌面 Sidebar / 移动 MobileNav 切换) - -#### A.7 OpenAPI 类型化(1.5 人天) -- `pnpm add -D openapi-typescript-codegen` -- 创建 `packages/api-client/codegen.config.ts` -- 流程:FastAPI 启动 → `GET /openapi.json` → 写入 `packages/api-client/openapi.json` → codegen -- `turbo.json` 加 `generate-api-client` 任务,依赖 `^build-admin` -- 加 1 个示范调用:`apps/web/lib/api/chat.ts` 用生成的 client 调 `POST /api/chat/send`(**先用 mock server,阶段 B 切真实**) - -#### A.8 CI 工作流(1 人天) -- `.github/workflows/web-ci.yml`: - - pnpm install - - turbo run lint typecheck test - - turbo run e2e(Playwright) - - turbo run build(Next.js) -- 加 Codecov 前端 target -- 加 bundle size 预算(`@next/bundle-analyzer`,> 200KB warn) - -**A 阶段交付物**: -- ✅ monorepo 可 `pnpm install && pnpm dev` 一键启动 -- ✅ 设计系统 + 5 个基础组件 + 8 hooks + 8 页面 + 247 行 CSS 全部就位 -- ✅ 测试覆盖率:原型组件 ≥ 60% line coverage -- ✅ CI 红绿信号在 PR 上可见 - -### 4.2 阶段 B — 真实后端对接(25-35 人天) - -> **核心目标**:把 prototype 的 8 个 mock hook 全部替换为真实 API 调用,UI/UX **零变化**。 - -#### B.1 后端 API 补全(5 人天,先于 B.2) -后端需要新增以下端点(**FastAPI 后端**任务,前端不实现): - -| 端点 | 方法 | 入参 | 出参 | -|---|---|---|---| -| `/api/chat/send` | POST | `{message, consultation_id?}` | `{user_message, assistant_message, consultation_id}` | -| `/api/chat/history` | GET | `?consultation_id=...` | `{messages: [...]}` | -| `/api/consultations` | GET | - | `{consultations: [{id, title, updated_at, profile}]}` | -| `/api/consultations/{id}` | GET | - | `{id, messages, profile, plan, audit}` | -| `/api/consultations/{id}` | DELETE | - | `{ok: true}` | -| `/api/plans` | GET | - | `{plans: [{id, name, created_at, rush_count, ...}]}` | -| `/api/plans/{id}` | GET | - | `{id, name, plan, profile, created_at}` | -| `/api/plans/{id}` | PATCH | `{name}` | `{ok: true}` | -| `/api/plans/{id}` | DELETE | - | `{ok: true}` | -| `/api/assessment` | POST | `{answers: [...]}` | `{riasec: {R, I, A, S, E, C}, recommendations: [...]}` | -| `/api/audit/upload` | POST | `multipart/form-data` | `{report_id, overall_score, risk_items}` | - -**注意**:所有端点**必须**走 OpenAPI 文档 + 类型化错误响应(沿用 `admin/errors/registry.py`)。 - -#### B.2 useChat 真实化(4 人天) -把 `useChat.ts`(543 行)的 mock 拆解为: - -```ts -// packages/hooks/useChat.ts -export function useChat(consultationId?: string) { - const queryClient = useQueryClient(); - - // 1. 历史查询 - const { data: messages = [], isLoading } = useQuery({ - queryKey: ['chat', consultationId], - queryFn: () => apiClient.chat.getHistory(consultationId), - staleTime: 30_000, - }); - - // 2. 发送 mutation - const sendMutation = useMutation({ - mutationFn: (text: string) => apiClient.chat.send(consultationId, { message: text }), - onMutate: async (text) => { - // 乐观更新 - await queryClient.cancelQueries({ queryKey: ['chat', consultationId] }); - const previous = queryClient.getQueryData