feat(diary): B4+B5+B6 后端服务 + F5/F6/F7 前端模块

后端 (erp-diary):
- B4: CommentService 班级成员验证 + 删除评语 + SSE 通知推送
- B4: NotificationService 评语/主题/成就三类通知事件
- B5: StickerService 贴纸包列表 + 贴纸查询 + 模板管理
- B5: AchievementService 成就列表 + 解锁 + SSE 通知
- B6: MoodStatsService 心情统计 + 连续天数
- B6: ContentSafetyService 敏感词过滤框架
- SSE handler 增加 diary.notification.* 事件处理
- 新增 14 个 API 端点 + diary.comment.delete 权限

前端 (Flutter):
- F5: CalendarBloc + 月视图日历 + 日记列表
- F6: MoodBloc + fl_chart 心情饼图 + 统计卡片 + 连续天数
- F7: 贴纸库分类浏览 + 模板画廊
- 首页改为日记流 + 心情快速选择
- 成就页改为徽章收集展示

验证: cargo check ✓ cargo test 17/17 ✓ flutter analyze 0 error
This commit is contained in:
iven
2026-06-01 09:32:09 +08:00
parent 482eb244d5
commit 7e3597dc77
25 changed files with 3286 additions and 39 deletions

View File

@@ -112,6 +112,41 @@ pub async fn message_stream(
.id(event.id.to_string())
.data(data));
}
// 暖记通知事件 — 推送给目标用户
"diary.notification.comment"
| "diary.notification.achievement"
| "diary.notification.class_update" => {
let is_recipient = event.payload.get("recipient_id")
.and_then(|v| v.as_str())
.map(|s| s == user_id.to_string())
.unwrap_or(false);
if !is_recipient {
continue;
}
let sse_event_name = match event.event_type.as_str() {
"diary.notification.comment" => "comment",
"diary.notification.achievement" => "achievement",
"diary.notification.class_update" => "class_update",
_ => "diary",
};
let data = serde_json::to_string(&event.payload)
.unwrap_or_default();
yield Ok(Event::default()
.event(sse_event_name)
.id(event.id.to_string())
.data(data));
}
// 暖记主题布置 — 班级广播
"diary.notification.topic" => {
// 主题布置是班级广播,所有在线用户都会收到
// 前端根据 class_id 过滤
let data = serde_json::to_string(&event.payload)
.unwrap_or_default();
yield Ok(Event::default()
.event("topic")
.id(event.id.to_string())
.data(data));
}
"alert.triggered" => {
let patient_id = event.payload.get("patient_id")
.and_then(|v| v.as_str());