Files
nj/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md

429 lines
18 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.
# 暖记课堂试点就绪 — 功能补全设计规格
> **版本**: 1.1 (审查修订)
> **日期**: 2026-06-02
> **基线**: main (7e928ae)
> **目标**: 四角色闭环 + 跨角色链路全部打通,达到真实课堂试点就绪
> **审计报告**: `docs/verification/full-system-audit-report.md`
---
## 1. 产品定位
### 1.1 核心认知
暖记是**纯 C 端个人日记产品**,不是学校管理系统:
- **孩子是主动用户** — 产品价值在于"孩子愿不愿意每天打开写日记"
- **家长是守门人** — 14 岁以下必须家长授权,家长是法律上的责任主体
- **老师是可选增强层** — 班级功能让日记变得更有趣,但不是必需品
- **管理员是平台运营** — 管理内容和用户,不面向具体学校
### 1.2 角色优先级
```
孩子(绝对核心)> 家长(合规+决策)> 老师(增强)> 管理员(运营)
```
### 1.3 成功标准
试点阶段回答一个核心问题:**孩子会不会自发地、持续地使用暖记写日记?**
辅助指标:
- 家长是否感到安全PIPL 合规 + 数据管理权)
- 老师是否愿意持续参与(布置主题 + 写评语的流程是否顺畅)
- 管理员能否有效运营(审核内容 + 管理资源)
---
## 2. 四角色旅程地图
### 2.1 孩子旅程(绝对核心)
```
打开 app → 写日记(手写/贴纸/照片/心情)
→ 保存 → 回看历史 → 感受到"我在成长"
→ [可选] 加入班级 → 分享给同学 → 收到老师鼓励 → 更想写了
```
| 步骤 | 当前状态 | 缺口 |
|------|----------|------|
| 注册/登录 | ✅ | — |
| 首页(今日概览/近期日记) | ✅ | — |
| 写日记(手写+贴纸+照片+文字) | ✅ | — |
| **再次打开编辑已有日记** | ❌ | **P0: editor 打开已有日记=空白** |
| 保存(本地+云端) | ✅ | SyncEngine 已接入 |
| 心情记录/查看 | ✅ | — |
| 日历回看 | ✅ | — |
| 搜索历史日记 | ❌ | **P2: Isar FTS 未实现(当前用客户端过滤)** |
| [可选] 加入班级 | ✅ | — |
| [可选] 分享到班级 | ✅ | — |
| [可选] 收到老师点评 | ⚠️ | 需验证通知→查看闭环 |
**关键缺口:再次编辑 + 搜索**
### 2.2 家长旅程(守门人)
```
注册 → 关联孩子账号 → 查看孩子日记 → 查看心情趋势
→ 感到安心 → [可选] 管理数据权限/导出
```
| 步骤 | 当前状态 | 缺口 |
|------|----------|------|
| 注册+关联孩子 | ✅ | 多孩子选择器已修 |
| 查看孩子日记 | ⚠️ | 数据流未端到端验证 |
| 查看心情趋势 | ⚠️ | MoodBloc 未验证 |
| 查看老师评语 | ❌ | 未验证是否展示给家长 |
| **数据管理权**(查阅/更正/删除/导出) | ⚠️ | **UI 已存在parent_page.dart需端到端验证+补全文件下载** |
**关键缺口PIPL 数据权利端到端验证 + 导出文件下载补全**
### 2.3 老师旅程(可选增强)
```
注册(普通用户) → 创建班级 → 获得班级码 → 布置主题
→ 查看学生日记墙 → 写评语/鼓励
```
| 步骤 | 当前状态 | 缺口 |
|------|----------|------|
| 注册 | ✅ | — |
| 创建班级 | ✅ | — |
| 布置主题 | ✅ | P2 已修 classId |
| 查看班级日记墙 | ✅ | 服务端过滤已修 |
| **写评语/鼓励** | ⚠️ | API 完整UI 需端到端验证 |
| 管理班级(编辑/停用/重置码) | ❌ | H6 缺失C端可延后 |
**关键缺口:点评闭环需端到端验证**
### 2.4 管理员旅程(平台运营)
| 步骤 | 当前状态 | 缺口 |
|------|----------|------|
| 登录管理端 | ✅ | — |
| 用户/角色管理 | ✅ | 基座继承 |
| **班级管理** | ⚠️ | C1: API 不匹配 |
| 日记审阅 | ✅ | 95% 完成 |
| 贴纸包管理 | ❌ | H7: 只有只读 |
| 主题管理 | ⚠️ | H8: 缺编辑/停用 |
| **菜单完整性** | ❌ | 多个功能无入口 |
---
## 3. 跨角色链路
### 3.1 必须打通的三条链路
```
链路 1合规刚需: 孩子 → 家长
孩子≠写日记 → 家长≠能查看 + 管理数据权利
链路 2动力增强: 孩子 → 老师 → 孩子
老师≠布置主题 → 孩子≠看到主题→写日记→分享 → 老师≠写评语 → 孩子≠下次看到鼓励
链路 3平台运营: 老师管理端
管理员≠审核内容 + 管理班级/主题 + 管理贴纸资源
```
---
## 4. 架构决策
### 4.1 决策 1编辑器加载已有数据 → 方案 A: LoadJournal Event
**问题:** EditorPage 的 `_EditorStack` 从不加载 `journalId` 对应的数据,编辑已有日记时页面空白。
**方案:** 在 EditorBloc 添加 `LoadJournal` event从 JournalRepository 读取日记数据,还原 strokes + elements + mood + tags 到 EditorState。
**实现要点:**
```dart
// editor_event.dart
class LoadJournal extends EditorEvent {
final String journalId;
const LoadJournal(this.journalId);
}
// editor_bloc.dart — 新增 event handler
Future<void> _onLoadJournal(LoadJournal event, Emitter<EditorState> emit) async {
final journal = await repo.getJournal(event.journalId);
if (journal == null) return;
// 加载元素
final elements = await repo.getElements(event.journalId);
// 查找 handwriting_ref 元素,反序列化为 Stroke 列表
final strokes = await _loadStrokes(repo, event.journalId);
emit(state.copyWith(
title: journal.title,
selectedMood: journal.mood,
tags: journal.tags,
strokes: strokes,
elements: elements,
lastSavedAt: journal.updatedAt,
));
}
```
**触发时机:** `_EditorStack.initState` 中,当 `widget.journalId != null` 时 dispatch `LoadJournal`
**笔画反序列化:** `Stroke.fromJson()` 工厂方法**已实现**stroke_model.dart:97-110无需额外开发。只需验证序列化/反序列化的一致性。
**约束:**
- EditorBloc 当前不接受 JournalRepository 作为依赖。需要修改构造函数注入 `JournalRepository`,或在 `LoadJournal` event 中传入所需数据。
- 推荐:在 event 中传入 `JournalEntry` + `List<JournalElement>`,由 EditorPage 的外层 widget 负责加载后传入。
### 4.2 决策 2PIPL 数据管理权 → 方案 B: 验证+补全现有实现
**问题:** PIPL 要求家长有查阅、更正、删除、导出孩子数据的权利。
**现状(审查修正):** `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` 页面上**验证和补全缺口**。
**需要补全的具体缺口:**
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: 轮询拉取
**问题:** 老师写评语后学生看不到。SSE 有端口配置问题,且 Flutter Web 对 SSE 支持有限。
**方案:** 学生打开日记时从后端拉取该日记的评论列表并展示。不做实时推送Phase 2 再完善 SSE。
**实现要点:**
```dart
// 在日记详情/查看页面加载时
final comments = await apiClient.get('/diary/journals/$journalId/comments');
// 展示评论列表(在日记内容下方)
// - 老师评语特殊样式(头像+标签)
// - 未读评论标记(对比本地最后查看时间)
```
**展示位置:** EditorPage 顶栏添加评语图标,点击弹出评论列表。仅在 journalId != null查看已有日记时显示。
**BLoC 架构决策:** 评论列表使用**独立 FutureBuilder widget**,不纳入 EditorBloc state。
- 理由:评论是只读展示,不需要复杂的 state 管理(加载/错误/空三态 FutureBuilder 足够)
- 评论数据通过 `context.read<ApiClient>().get(...)` 直接获取
- 避免给已复杂的 EditorBloc 增加更多职责
**老师写评语位置:** 班级日记墙页面class diary wall在学生日记卡片上添加评语按钮。
- 文件:`app/lib/features/class_/views/` 相关页面
- 通过 ClassBloc 已有的 `_onCommentCreate` 事件处理
- 需验证:评语提交后日记墙上的评语计数是否更新
**约束:**
- 后端 API 已有:`GET /diary/journals/:id/comments``POST /diary/journals/:id/comments`
- 评论需要区分"老师评语"和"同学评论"(通过角色判断)
- 内容安全过滤:审查确认 `comment_service.rs` 已集成 ContentSafetyServiceH4 实际已完成)
---
## 5. 实施路线图
### Phase 1孩子核心闭环~2-3 天)
> **验证标准:** 孩子能完成"写日记→保存→再打开→继续编辑→搜索找到→日历回看"
> **测试要求:** `flutter test` 通过 + EditorBloc LoadJournal 单元测试
| # | 任务 | 文件 | 优先级 | 依赖 |
|---|------|------|--------|------|
| 1 | EditorBloc 添加 `LoadJournal` event + state 还原逻辑(注入 JournalEntry + List\<JournalElement\> | 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家长合规闭环~1-2 天)
> **验证标准:** 家长能"关联孩子→查看日记→导出数据文件→删除数据+确认"
> **测试要求:** ParentBloc 端到端数据流验证通过
| # | 任务 | 文件 | 优先级 | 依赖 |
|---|------|------|--------|------|
| 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 顶栏添加评语图标 + 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 场景走通
| # | 任务 | 文件 | 优先级 | 依赖 |
|---|------|------|--------|------|
| 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 |
### 总时间线
```
Week 1: Phase 1 (孩子闭环) + Phase 2 开始
Week 2: Phase 2 (家长闭环) + Phase 3 (师生闭环)
Week 3: Phase 4 (管理端) + 全系统端到端验证
```
---
## 6. 风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|----------|
| 笔画序列化/反序列化格式不匹配 | 低 | Phase 1 阻塞 | Stroke.fromJson 已实现Day 1 做一致性验证 |
| 管理端 React 改动涉及基座代码 | 低 | Phase 4 延期 | 只改 pages/diary/ 和路由配置,不动基座 |
| 家长端数据流实际未连通API 调用失败) | 中 | Phase 2 阻塞 | Phase 2 启动先用 curl 验证 API再验证 ParentBloc |
---
## 7. 审计报告状态追踪
### 已完成(审查确认,无需开发)
| 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, #3, #4 |
| PIPL | 家长数据权利端到端验证+补全 | 任务 #7, #8, #9, #10 |
| 点评闭环 | 师生评论展示+写入闭环 | 任务 #11, #12, #14 |
| H5 | catch_ 异常处理 | 任务 #6 |
| H7 | 贴纸 CRUD | 任务 #17 |
| H8 | 主题编辑/停用 | 任务 #18 |
| 管理端 | 菜单补全 | 任务 #16 |
### 明确延后到 Phase 2 的功能
| 功能 | 理由 |
|------|------|
| SSE 实时通知 | 轮询已满足试点需求 |
| 班级管理(编辑/停用/重置码) | C 端产品,班级是可选增强 |
| BLoC 统一迁移 | 不影响功能,代码风格问题 |
| 成就系统管理端页面 | 试点阶段不紧急 |
| 心情统计管理端页面 | 试点阶段不紧急 |
| Discover 页面数据接入 | 全 mock需单独设计 |
---
## 8. 验证方案
### 8.1 编译验证(每个 Phase 提交前必须通过)
```bash
# 后端
cd g:/nj && cargo check && cargo test
# Flutter
cd g:/nj/app && flutter analyze && flutter test
# 管理端
cd g:/nj/apps/web && pnpm build
```
### 8.2 Phase 1 验证:孩子核心闭环
**自动化测试:**
```bash
cd g:/nj/app
flutter test test/features/editor/bloc/editor_bloc_test.dart # LoadJournal event 测试
```
**手动验证:**
1. 创建日记(手写+贴纸+心情)→ 保存 → 返回首页
2. 点击刚创建的日记 → 编辑器正确加载:标题、笔画、贴纸元素、心情
3. 继续编辑 → 添加新内容 → 再次保存 → 验证更新成功
4. 搜索关键词 → 找到对应日记
5. 日历点击某天 → 显示该天的日记
### 8.3 Phase 2 验证:家长合规闭环
**手动验证:**
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.6 全系统端到端验证Phase 4 任务 #19
完整走通以下场景(四角色 × 跨角色):
1. **学生闭环**:新用户注册 → 写第一篇日记 → 分享到班级 → 收到老师评语 → 再次编辑
2. **老师闭环**:创建班级 → 布置主题 → 查看学生作品 → 写评语
3. **家长闭环**:关联孩子 → 查看日记+评语 → 导出数据
4. **管理员闭环**:审核内容 → 管理班级 → 管理贴纸/主题 → 查看统计
---
*设计规格版本 1.0 | 2026-06-02 | 基于 main (7e928ae)*