From 860844a3990c23a34e3ae959dd2e56a7d573103b Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 2 Jun 2026 22:49:17 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BF=AE=E8=AE=A2=E8=AF=BE=E5=A0=82?= =?UTF-8?q?=E8=AF=95=E7=82=B9=E8=AE=BE=E8=AE=A1=E8=A7=84=E6=A0=BC=20v1.1?= =?UTF-8?q?=20=E2=80=94=20=E4=BF=AE=E6=AD=A3=20API=20=E8=B7=AF=E5=BE=84/?= =?UTF-8?q?=E5=B7=B2=E5=AD=98=E5=9C=A8=E5=8A=9F=E8=83=BD/=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=A0=87=E5=87=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-06-02-classroom-pilot-readiness-design.md | 232 ++++++++++-------- 1 file changed, 134 insertions(+), 98 deletions(-) diff --git a/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md b/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md index da56b4e..d795ae9 100644 --- a/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md +++ b/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md @@ -1,9 +1,10 @@ # 暖记课堂试点就绪 — 功能补全设计规格 -> **版本**: 1.0 +> **版本**: 1.1 (审查修订) > **日期**: 2026-06-02 > **基线**: main (7e928ae) > **目标**: 四角色闭环 + 跨角色链路全部打通,达到真实课堂试点就绪 +> **审计报告**: `docs/verification/full-system-audit-report.md` --- @@ -54,8 +55,7 @@ | 保存(本地+云端) | ✅ | SyncEngine 已接入 | | 心情记录/查看 | ✅ | — | | 日历回看 | ✅ | — | -| 搜索历史日记 | ❌ | **P2: Isar FTS 未实现** | -| 个人资料/成就 | ⚠️ | 数据流未验证 | +| 搜索历史日记 | ❌ | **P2: Isar FTS 未实现(当前用客户端过滤)** | | [可选] 加入班级 | ✅ | — | | [可选] 分享到班级 | ✅ | — | | [可选] 收到老师点评 | ⚠️ | 需验证通知→查看闭环 | @@ -72,12 +72,12 @@ | 步骤 | 当前状态 | 缺口 | |------|----------|------| | 注册+关联孩子 | ✅ | 多孩子选择器已修 | -| 查看孩子日记 | ⚠️ | 数据流未验证 | +| 查看孩子日记 | ⚠️ | 数据流未端到端验证 | | 查看心情趋势 | ⚠️ | MoodBloc 未验证 | | 查看老师评语 | ❌ | 未验证是否展示给家长 | -| **数据管理权**(查阅/更正/删除/导出) | ❌ | **P0: PIPL 合规必需** | +| **数据管理权**(查阅/更正/删除/导出) | ⚠️ | **UI 已存在(parent_page.dart),需端到端验证+补全文件下载** | -**关键缺口:PIPL 数据管理权(法律硬性要求)** +**关键缺口:PIPL 数据权利端到端验证 + 导出文件下载补全** ### 2.3 老师旅程(可选增强) @@ -169,40 +169,38 @@ Future _onLoadJournal(LoadJournal event, Emitter emit) async **触发时机:** `_EditorStack.initState` 中,当 `widget.journalId != null` 时 dispatch `LoadJournal`。 -**笔画反序列化:** `handwriting_ref` 元素的 `content.strokes` JSON → `Stroke` 对象列表,需要 `Stroke.fromJson()` 工厂方法。 +**笔画反序列化:** `Stroke.fromJson()` 工厂方法**已实现**(stroke_model.dart:97-110),无需额外开发。只需验证序列化/反序列化的一致性。 **约束:** - EditorBloc 当前不接受 JournalRepository 作为依赖。需要修改构造函数注入 `JournalRepository`,或在 `LoadJournal` event 中传入所需数据。 - 推荐:在 event 中传入 `JournalEntry` + `List`,由 EditorPage 的外层 widget 负责加载后传入。 -### 4.2 决策 2:PIPL 数据管理权 → 方案 B: 独立页面 +### 4.2 决策 2:PIPL 数据管理权 → 方案 B: 验证+补全现有实现 -**问题:** PIPL 要求家长有查阅、更正、删除、导出孩子数据的权利。后端 API 完整但 Flutter 端未接入。 +**问题:** PIPL 要求家长有查阅、更正、删除、导出孩子数据的权利。 -**方案:** 新增独立路由 `/parent/data-rights`,设计为正式、清晰的法律合规页面。 +**现状(审查修正):** `parent_page.dart` 已实现大部分 PIPL 功能: +- 孩子选择器(多孩子切换)✅ +- 查看孩子日记列表(ParentJournalsLoaded state)✅ +- 导出数据(ParentDataExported state + UI)✅ +- 删除数据 + 确认对话框(parent_page.dart:160-200)✅ +- PIPL 合规说明文本(parent_page.dart:1113-1150)✅ +- ParentBloc 已有完整事件:ParentLoadChildren, ParentViewJournals, ParentExportData, ParentDeleteData ✅ -**页面结构:** +**方案:** 不需要新建页面。在现有 `/parent` 页面上**验证和补全缺口**。 -``` -ParentDataRightsPage -├── 孩子选择器(多孩子时切换) -├── 数据概览卡片 -│ ├── 日记总数 -│ ├── 最早/最近日记日期 -│ └── 关联班级 -├── 操作区 -│ ├── 「导出全部数据」— 调用后端 /diary/parent/export -│ ├── 「删除全部数据」— 二次确认 → 调用后端 /diary/parent/delete -│ └── 「查看单篇日记」— 跳转日记列表 → 详情 -└── 法律说明文本 - ├── 数据权利说明 - └── 30 天账号注销政策 -``` +**需要补全的具体缺口:** -**约束:** -- 后端 API 已有:`GET /diary/parent/children/:id/journals`、`POST /diary/parent/export`、`DELETE /diary/parent/children/:id/data` -- 删除操作需要**二次确认对话框** + 不可逆提示 -- 导出格式为 JSON(后端已实现) +1. **导出文件下载** — 当前导出可能仅显示 JSON 预览,需确认是否支持文件下载到本地 +2. **端到端数据流验证** — 确认 ParentBloc 事件链完整触发,后端返回正确数据 +3. **法律说明文本完善** — 确认 30 天账号注销政策文本存在且准确 + +**后端 API(实际路径,已验证):** +- `GET /diary/parent/journals?child_id=...` — 查看孩子日记(query parameter,非 path parameter) +- `GET /diary/parent/export?child_id=...` — 导出数据(GET 方法,非 POST) +- `DELETE /diary/parent/data` + JSON body `{child_id: ...}` — 删除数据 + +> ⚠️ 注意:以上路径与审计报告中的描述不同。Flutter 端 ParentBloc 已使用正确路径。 ### 4.3 决策 3:师生点评闭环 → 方案 B: 轮询拉取 @@ -221,68 +219,77 @@ final comments = await apiClient.get('/diary/journals/$journalId/comments'); // - 未读评论标记(对比本地最后查看时间) ``` -**展示位置选择:** -- 方案 a: 在 EditorPage 顶部添加评语入口(点击展开评语列表)— 推荐学生在"查看"模式看到 -- 方案 b: 日记详情独立页面 — 更适合阅读评语 +**展示位置:** EditorPage 顶栏添加评语图标,点击弹出评论列表。仅在 journalId != null(查看已有日记)时显示。 -**推荐方案 a** — 保持最小改动,EditorPage 的顶栏添加评语图标,点击弹出评论列表。 +**BLoC 架构决策:** 评论列表使用**独立 FutureBuilder widget**,不纳入 EditorBloc state。 +- 理由:评论是只读展示,不需要复杂的 state 管理(加载/错误/空三态 FutureBuilder 足够) +- 评论数据通过 `context.read().get(...)` 直接获取 +- 避免给已复杂的 EditorBloc 增加更多职责 + +**老师写评语位置:** 班级日记墙页面(class diary wall),在学生日记卡片上添加评语按钮。 +- 文件:`app/lib/features/class_/views/` 相关页面 +- 通过 ClassBloc 已有的 `_onCommentCreate` 事件处理 +- 需验证:评语提交后日记墙上的评语计数是否更新 **约束:** - 后端 API 已有:`GET /diary/journals/:id/comments`、`POST /diary/journals/:id/comments` - 评论需要区分"老师评语"和"同学评论"(通过角色判断) -- 内容安全过滤需要接入评论(审计报告 H4) +- 内容安全过滤:审查确认 `comment_service.rs` 已集成 ContentSafetyService(H4 实际已完成) --- ## 5. 实施路线图 -### Phase 1:孩子核心闭环(~3-4 天) +### Phase 1:孩子核心闭环(~2-3 天) > **验证标准:** 孩子能完成"写日记→保存→再打开→继续编辑→搜索找到→日历回看" +> **测试要求:** `flutter test` 通过 + EditorBloc LoadJournal 单元测试 | # | 任务 | 文件 | 优先级 | 依赖 | |---|------|------|--------|------| -| 1 | EditorBloc 添加 `LoadJournal` event + state 还原逻辑 | editor_bloc.dart, editor_event.dart, editor_state.dart | P0 | — | -| 2 | `Stroke.fromJson()` 笔画反序列化工厂方法 | stroke_model.dart | P0 | — | -| 3 | EditorPage `_EditorStack.initState` 触发 LoadJournal | editor_page.dart | P0 | 1, 2 | -| 4 | Isar FTS 全文搜索实现 | search_bloc.dart, journal_repository.dart | P2 | — | -| 5 | H5 剩余 catch_ 异常处理修复 | 多文件 | P3 | — | +| 1 | EditorBloc 添加 `LoadJournal` event + state 还原逻辑(注入 JournalEntry + List\) | editor_bloc.dart, editor_event.dart, editor_state.dart | P0 | — | +| 2 | ~~Stroke.fromJson()~~ ✅ 已实现,需验证序列化/反序列化一致性 | stroke_model.dart | — | — | +| 3 | EditorPage `_EditorStack.initState` 触发 LoadJournal(外层 widget 先加载数据再传入 event) | editor_page.dart | P0 | 1 | +| 4 | EditorBloc LoadJournal 单元测试 | test/features/editor/bloc/editor_bloc_test.dart | P0 | 1 | +| 5 | 搜索功能验证(当前客户端过滤可用性确认,Isar FTS 延后) | search_bloc.dart | P2 | — | +| 6 | H5 catch_ 异常处理:class_bloc.dart 6 处 catch(_)+5 处无日志 catch(e) | class_bloc.dart, weekly_page.dart, monthly_page.dart, profile_page.dart, parent_page.dart | P3 | — | -### Phase 2:家长合规闭环(~2-3 天) +### Phase 2:家长合规闭环(~1-2 天) -> **验证标准:** 家长能"关联孩子→查看日记/心情→行使数据权利(查阅/导出/删除)" +> **验证标准:** 家长能"关联孩子→查看日记→导出数据文件→删除数据+确认" +> **测试要求:** ParentBloc 端到端数据流验证通过 | # | 任务 | 文件 | 优先级 | 依赖 | |---|------|------|--------|------| -| 6 | 新增 `/parent/data-rights` 路由 + ParentDataRightsPage | parent_data_rights_page.dart, app_router.dart | P0 | — | -| 7 | 数据权利页:查看孩子日记列表 + 概览统计 | 同上 | P0 | 6 | -| 8 | 数据权利页:导出功能(JSON 下载) | 同上 + api_client | P0 | 6 | -| 9 | 数据权利页:删除功能(单篇+全部 + 确认对话框) | 同上 | P0 | 6 | -| 10 | 端到端验证:家长查看孩子日记+老师评语 | parent_page.dart | P1 | 7 | +| 7 | 端到端验证:ParentBloc 事件链(加载孩子→查看日记→导出→删除) | parent_bloc.dart, parent_page.dart | P0 | — | +| 8 | 补全:导出功能文件下载(确认 JSON 预览→实际文件下载) | parent_page.dart | P1 | 7 | +| 9 | 补全:PIPL 法律说明文本完善(30 天注销政策) | parent_page.dart | P1 | 7 | +| 10 | 端到端验证:家长查看老师评语(确认评论展示给家长) | parent_page.dart | P1 | 7 | ### Phase 3:师生互动闭环(~2-3 天) > **验证标准:** 老师能"布置主题→学生看到→写日记分享→老师写评语→学生下次打开看到鼓励" +> **测试要求:** ClassBloc 评论提交测试通过 + 手动端到端验证 | # | 任务 | 文件 | 优先级 | 依赖 | |---|------|------|--------|------| -| 11 | EditorPage 顶栏添加评语入口(图标+弹出评论列表) | editor_page.dart | P0 | — | -| 12 | 评论拉取:日记加载时从后端获取评论列表 | editor_bloc.dart 或独立 widget | P0 | — | -| 13 | 老师评论 UI 验证+修复(写评语→提交→显示) | class_bloc.dart, 日记墙相关 | P0 | — | -| 14 | 端到端验证:老师布置→学生写→老师评→学生看 | 全链路手动验证 | P0 | 11, 12, 13 | -| 15 | H4: 内容安全过滤接入评论服务 | comment_service.rs | P1 | — | +| 11 | EditorPage 顶栏添加评语图标 + FutureBuilder 评论弹出列表(独立 widget,不纳入 EditorBloc) | editor_page.dart, comment_list_sheet.dart(新建) | P0 | — | +| 12 | 班级日记墙:学生日记卡片添加评语按钮 + 提交框 | class_ 相关 views/ | P0 | — | +| 13 | ~~H4: 内容安全过滤接入评论~~ ✅ 审查确认 comment_service.rs 已集成 | — | — | — | +| 14 | 端到端验证:老师布置→学生写→老师评→学生看 | 全链路手动验证 | P0 | 11, 12 | ### Phase 4:管理端补全 + 全系统验证(~3-4 天) > **验证标准:** 管理员能"管理班级/审核内容/管理贴纸主题",四角色 × 跨角色链路全部通过 +> **测试要求:** `cargo test` 通过 + 管理端 `pnpm build` 无错误 + 全系统 E2E 场景走通 | # | 任务 | 文件 | 优先级 | 依赖 | |---|------|------|--------|------| -| 16 | C1: 后端新增 `list_all_classes` handler(需 diary.class.manage 权限) | class_handler.rs, class_service.rs, lib.rs | P1 | — | -| 17 | 管理端菜单补全(贴纸/主题/统计入口) | routeConfig.ts, MainLayout.tsx | P1 | — | -| 18 | H7: 贴纸 CRUD(后端 handler + 管理端 UI) | sticker_handler.rs, StickerPackList.tsx | P2 | — | -| 19 | H8: 主题编辑/停用 | topic_handler.rs, TopicList.tsx | P2 | — | -| 20 | 四角色 × 跨角色 端到端全面验证 | — | P0 | 1-19 | +| 15 | ~~C1: list_all_classes handler~~ ✅ 已实现(class_handler.rs + 管理端 classApi.listAll()) | — | — | — | +| 16 | 管理端菜单补全(贴纸/主题/统计入口) | routeConfig.ts, MainLayout.tsx | P1 | — | +| 17 | H7: 贴纸 CRUD(后端 handler + 管理端 UI) | sticker_handler.rs, StickerPackList.tsx | P2 | — | +| 18 | H8: 主题编辑/停用 | topic_handler.rs, TopicList.tsx | P2 | — | +| 19 | 四角色 × 跨角色 端到端全面验证 | — | P0 | 1-18 | ### 总时间线 @@ -298,36 +305,36 @@ Week 3: Phase 4 (管理端) + 全系统端到端验证 | 风险 | 概率 | 影响 | 缓解措施 | |------|------|------|----------| -| 笔画反序列化格式不匹配(Stroke 结构与存储 JSON 不同步) | 中 | Phase 1 阻塞 | Day 1 先验证序列化/反序列化一致性 | -| 后端 parent API 参数与 Flutter 调用不匹配 | 低 | Phase 2 阻塞 | Phase 2 启动时先用 curl/postman 验证 API | +| 笔画序列化/反序列化格式不匹配 | 低 | Phase 1 阻塞 | Stroke.fromJson 已实现,Day 1 做一致性验证 | | 管理端 React 改动涉及基座代码 | 低 | Phase 4 延期 | 只改 pages/diary/ 和路由配置,不动基座 | -| 评论内容安全遗漏(H4) | 低 | 合规风险 | Phase 3 同时修复 comment_service.rs | +| 家长端数据流实际未连通(API 调用失败) | 中 | Phase 2 阻塞 | Phase 2 启动先用 curl 验证 API,再验证 ParentBloc | --- ## 7. 审计报告状态追踪 -### 已完成(cuddly-munching-galaxy.md 计划中但实际已完成) +### 已完成(审查确认,无需开发) -| ID | 问题 | 状态 | -|----|------|------| -| C4 | SyncEngine 接入 EditorPage + app.dart | ✅ 已完成 | -| H1 | EditorPage authorId 从 AuthBloc 获取 | ✅ 已完成 | +| ID | 问题 | 状态 | 验证 | +|----|------|------|------| +| C4 | SyncEngine 接入 EditorPage + app.dart | ✅ 已完成 | app.dart:60-62, editor_page.dart:126-133 | +| H1 | EditorPage authorId 从 AuthBloc 获取 | ✅ 已完成 | editor_page.dart:59-64 | +| — | Stroke.fromJson() 笔画反序列化 | ✅ 已存在 | stroke_model.dart:97-110 | +| C1 | list_all_classes handler | ✅ 已存在 | class_handler.rs:208-220, lib.rs 注册 | +| H4 | 内容安全过滤接入评论 | ✅ 已存在 | comment_service.rs 已集成 | +| PIPL | 家长数据管理 UI | ⚠️ 大部分已存在 | parent_page.dart 有完整 UI,需验证数据流 | ### 本设计覆盖的修复 | ID | 问题 | 对应任务 | |----|------|----------| -| M4 | 编辑器不加载已有数据 | 任务 #1, #2, #3 | -| PIPL | 家长数据管理权 | 任务 #6, #7, #8, #9 | -| 点评闭环 | 师生评论链路 | 任务 #11, #12, #13, #14 | -| C1 | 管理端班级 API 不匹配 | 任务 #16 | -| H4 | 内容安全过滤接入评论 | 任务 #15 | -| H5 | catch_ 异常处理 | 任务 #5 | -| H6 | 班级编辑/停用/重置 | Phase 2 延后 | -| H7 | 贴纸 CRUD | 任务 #18 | -| H8 | 主题编辑/停用 | 任务 #19 | -| H2 | SSE 端口配置 | Phase 2 延后 | +| M4 | 编辑器不加载已有数据 | 任务 #1, #3, #4 | +| PIPL | 家长数据权利端到端验证+补全 | 任务 #7, #8, #9, #10 | +| 点评闭环 | 师生评论展示+写入闭环 | 任务 #11, #12, #14 | +| H5 | catch_ 异常处理 | 任务 #6 | +| H7 | 贴纸 CRUD | 任务 #17 | +| H8 | 主题编辑/停用 | 任务 #18 | +| 管理端 | 菜单补全 | 任务 #16 | ### 明确延后到 Phase 2 的功能 @@ -344,7 +351,7 @@ Week 3: Phase 4 (管理端) + 全系统端到端验证 ## 8. 验证方案 -### 8.1 编译验证 +### 8.1 编译验证(每个 Phase 提交前必须通过) ```bash # 后端 @@ -357,35 +364,64 @@ cd g:/nj/app && flutter analyze && flutter test cd g:/nj/apps/web && pnpm build ``` -### 8.2 功能验证(每个 Phase 结束时) +### 8.2 Phase 1 验证:孩子核心闭环 -**Phase 1 验证:** -1. 创建日记 → 保存 → 返回首页 → 点击日记 → 编辑器正确加载已有内容 -2. 搜索关键词 → 找到对应日记 -3. 日历点击某天 → 显示该天的日记 +**自动化测试:** +```bash +cd g:/nj/app +flutter test test/features/editor/bloc/editor_bloc_test.dart # LoadJournal event 测试 +``` -**Phase 2 验证:** -1. 家长账号登录 → 关联孩子 → 查看孩子日记列表 -2. 数据权利页 → 导出 JSON → 验证文件内容 -3. 数据权利页 → 删除单篇日记 → 确认对话框 → 验证已删除 +**手动验证:** +1. 创建日记(手写+贴纸+心情)→ 保存 → 返回首页 +2. 点击刚创建的日记 → 编辑器正确加载:标题、笔画、贴纸元素、心情 +3. 继续编辑 → 添加新内容 → 再次保存 → 验证更新成功 +4. 搜索关键词 → 找到对应日记 +5. 日历点击某天 → 显示该天的日记 -**Phase 3 验证:** -1. 老师布置主题 → 学生看到 → 写日记 → 分享到班级 -2. 老师查看日记墙 → 写评语 → 提交成功 -3. 学生再次打开该日记 → 看到老师评语 +### 8.3 Phase 2 验证:家长合规闭环 -**Phase 4 验证:** -1. 管理端 → 班级列表 → 显示所有班级(不只是自己的) +**手动验证:** +1. 家长账号登录 → 关联孩子 → 查看孩子日记列表(确认列表非空,内容正确) +2. 导出数据 → 验证文件可下载、JSON 格式正确、包含日记内容 +3. 删除单篇日记 → 确认对话框 → 验证日记从列表消失 + 后端 soft-delete +4. 验证 PIPL 法律说明文本显示正确 + +### 8.4 Phase 3 验证:师生互动闭环 + +**自动化测试:** +```bash +cd g:/nj/app +flutter test test/features/class_/bloc/class_bloc_test.dart # 评论提交测试(如存在) +``` + +**手动验证:** +1. 老师账号 → 创建班级 → 布置主题 +2. 学生账号 → 看到主题 → 写日记 → 分享到班级 +3. 老师账号 → 查看日记墙 → 点击评语按钮 → 输入评语 → 提交成功 +4. 学生账号 → 打开该日记 → 点击顶栏评语图标 → 看到老师评语 + +### 8.5 Phase 4 验证:管理端 + 全系统 + +**编译验证:** +```bash +cd g:/nj && cargo test # 后端测试通过 +cd g:/nj/app && flutter test # Flutter 测试通过 +cd g:/nj/apps/web && pnpm build # 管理端构建成功 +``` + +**手动验证:** +1. 管理端 → 菜单完整性:所有暖记功能有入口(班级/日记/贴纸/主题) 2. 管理端 → 贴纸管理 → 创建/编辑/删除贴纸 3. 管理端 → 主题管理 → 停用主题 → 学生端不再看到 -### 8.3 全系统端到端验证(Phase 4 任务 #20) +### 8.6 全系统端到端验证(Phase 4 任务 #19) -完整走通以下场景: -1. 新用户注册(学生)→ 写第一篇日记 → 分享到班级 -2. 老师创建班级 → 布置主题 → 查看学生作品 → 写评语 -3. 家长关联孩子 → 查看日记 → 导出数据 -4. 管理员审核内容 → 管理班级 → 管理贴纸/主题 +完整走通以下场景(四角色 × 跨角色): +1. **学生闭环**:新用户注册 → 写第一篇日记 → 分享到班级 → 收到老师评语 → 再次编辑 +2. **老师闭环**:创建班级 → 布置主题 → 查看学生作品 → 写评语 +3. **家长闭环**:关联孩子 → 查看日记+评语 → 导出数据 +4. **管理员闭环**:审核内容 → 管理班级 → 管理贴纸/主题 → 查看统计 ---