docs: 项目 Wiki 知识库 — 7 文件覆盖架构/手写/数据/前端/后端/技术债
新增 wiki/ 知识库 (遵循 HMS wiki-methodology.md 5 节结构): - index.md (84 行) — 症状导航 13 条 + 模块索引 + 系统数据流 - architecture.md (120 行) — 基座剥离 7 耦合点 + Feature Flag + PIPL 合规 - handwriting-engine.md (124 行) — 双层 Canvas + O(1) 点缓冲 + 光栅化缓存 - data-layer.md (127 行) — Isar + SyncEngine 离线同步 + 踩坑记录 - frontend.md (118 行) — 16 模块地图 + BLoC 注入链 + 设计系统 - erp-diary.md (101 行) — 15 Entity / 10 Service / 8 Handler + API 端点 新增 docs/: - tech-debt-board.md (110 行) — 10 条技术债 + 偿还优先级排名 其他更新: - .gitignore: 添加 .understand-anything/ (待初始化) - CLAUDE.md §9: 添加 wiki 参考文档链接
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -98,6 +98,9 @@ trace-*.json
|
|||||||
# Graphify knowledge graph (regenerated locally)
|
# Graphify knowledge graph (regenerated locally)
|
||||||
graphify-out/
|
graphify-out/
|
||||||
|
|
||||||
|
# Understand-Anything knowledge graph (local dev tool)
|
||||||
|
.understand-anything/
|
||||||
|
|
||||||
# Native miniprogram (separate project)
|
# Native miniprogram (separate project)
|
||||||
apps/mp-native/
|
apps/mp-native/
|
||||||
|
|
||||||
|
|||||||
@@ -414,6 +414,13 @@ chore(docker): 添加 PostgreSQL 16 + Redis 7 开发环境
|
|||||||
|
|
||||||
| 文档 | 位置 |
|
| 文档 | 位置 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| **知识库首页** | `wiki/index.md` — 症状导航 + 模块索引 |
|
||||||
|
| 架构决策 | `wiki/architecture.md` — 基座剥离 + Feature Flag + 多租户 |
|
||||||
|
| 手写引擎 | `wiki/handwriting-engine.md` — 双层 Canvas + 光栅化缓存 |
|
||||||
|
| 数据层 | `wiki/data-layer.md` — Isar + SyncEngine 离线同步 |
|
||||||
|
| Flutter 前端 | `wiki/frontend.md` — 16 模块 + BLoC + 设计系统 |
|
||||||
|
| 后端模块 | `wiki/erp-diary.md` — Entity/Service/Handler 清单 |
|
||||||
|
| 技术债看板 | `docs/tech-debt-board.md` — 10 条待偿还债务 |
|
||||||
| 产品设计规格 v1.2 | `docs/superpowers/specs/2026-05-31-nuanji-warm-notes-design.md` |
|
| 产品设计规格 v1.2 | `docs/superpowers/specs/2026-05-31-nuanji-warm-notes-design.md` |
|
||||||
| 实施规划 v2.1 | `plans/hazy-petting-lampson.md` |
|
| 实施规划 v2.1 | `plans/hazy-petting-lampson.md` |
|
||||||
| 头脑风暴文档 (8 份) | `.superpowers/brainstorm/734-1780218658/` |
|
| 头脑风暴文档 (8 份) | `.superpowers/brainstorm/734-1780218658/` |
|
||||||
|
|||||||
110
docs/tech-debt-board.md
Normal file
110
docs/tech-debt-board.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 暖记技术债看板
|
||||||
|
|
||||||
|
> 最后更新: 2026-06-01
|
||||||
|
|
||||||
|
## 指标概览
|
||||||
|
|
||||||
|
| 指标 | 当前值 | 目标 | 状态 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| flutter analyze 错误 | 0 | 0 | 🟢 健康 |
|
||||||
|
| flutter analyze 警告 | 1 | 0 | 🟡 需改进 |
|
||||||
|
| flutter analyze info | 18 | ≤10 | 🟡 需改进 |
|
||||||
|
| 后端测试 | ~50 | 80%+ 覆盖 | 🟡 需改进 |
|
||||||
|
| 前端测试 | 0 | 80%+ 覆盖 | 🔴 缺失 |
|
||||||
|
| CI/CD | 无 | 全自动化 | 🔴 缺失 |
|
||||||
|
| Docker 部署 | 未验证 | 可运行 | 🔴 缺失 |
|
||||||
|
|
||||||
|
## 高息技术债(按优先级排序)
|
||||||
|
|
||||||
|
### TD-1: editor_page.dart authorId 硬编码 [利息: HIGH]
|
||||||
|
|
||||||
|
- **现状**: `JournalEntry.create(authorId: 'local')` 硬编码
|
||||||
|
- **影响**: 无法关联真实用户,同步后数据混乱
|
||||||
|
- **修复成本**: 0.5 天
|
||||||
|
- **计划**: 接入 AuthBloc 获取当前用户 ID
|
||||||
|
- **文件**: `app/lib/features/editor/views/editor_page.dart:96`
|
||||||
|
|
||||||
|
### TD-2: CI/CD 未建立 [利息: HIGH]
|
||||||
|
|
||||||
|
- **现状**: 手动 `cargo check` + `flutter analyze`
|
||||||
|
- **影响**: 无法防止回归,无自动部署
|
||||||
|
- **修复成本**: 1 天
|
||||||
|
- **计划**: GitHub Actions / Gitea CI + 基础流水线
|
||||||
|
|
||||||
|
### TD-3: 后端 Docker 部署未验证 [利息: HIGH]
|
||||||
|
|
||||||
|
- **现状**: `docker/` 目录有 compose 文件但从未实际运行
|
||||||
|
- **影响**: 无法确认后端可正常启动
|
||||||
|
- **修复成本**: 0.5 天
|
||||||
|
- **计划**: docker-compose up + 健康检查验证
|
||||||
|
|
||||||
|
### TD-4: 笔画 toImage() 同步阻塞 [利息: MEDIUM]
|
||||||
|
|
||||||
|
- **现状**: `stroke_cache.dart` 中 `toImage()` 在主线程执行
|
||||||
|
- **影响**: 大笔画可能导致 UI 卡顿
|
||||||
|
- **修复成本**: 1 天
|
||||||
|
- **计划**: compute() isolate 异步光栅化
|
||||||
|
|
||||||
|
### TD-5: 前端测试为零 [利息: MEDIUM]
|
||||||
|
|
||||||
|
- **现状**: `app/test/` 目录空
|
||||||
|
- **影响**: 无回归保护,重构风险高
|
||||||
|
- **修复成本**: 3 天
|
||||||
|
- **计划**: Repository 单元测试 → BLoC 测试 → Widget 测试
|
||||||
|
|
||||||
|
### TD-6: 画布尺寸变化缓存过渡 [利息: MEDIUM]
|
||||||
|
|
||||||
|
- **现状**: 屏幕旋转时 ui.Image 缓存失效,需要重新光栅化
|
||||||
|
- **影响**: 旋转瞬间可能卡顿或空白
|
||||||
|
- **修复成本**: 1 天
|
||||||
|
- **计划**: 监听尺寸变化,异步重建缓存
|
||||||
|
|
||||||
|
### TD-7: SecureStorage token 未持久化 [利息: MEDIUM]
|
||||||
|
|
||||||
|
- **现状**: app.dart 中 token 设置为 TODO 注释
|
||||||
|
- **影响**: 每次重启需要重新登录
|
||||||
|
- **修复成本**: 0.5 天
|
||||||
|
- **计划**: flutter_secure_storage 存取 JWT
|
||||||
|
|
||||||
|
### TD-8: 编辑器不加载已有日记 [利息: MEDIUM]
|
||||||
|
|
||||||
|
- **现状**: journalId 非空时未从 Isar 读取数据
|
||||||
|
- **影响**: 编辑已有日记时显示空白
|
||||||
|
- **修复成本**: 1 天
|
||||||
|
- **计划**: EditorBloc 添加 LoadJournal event + Isar 查询
|
||||||
|
|
||||||
|
### TD-9: Isar FTS 搜索未实现 [利息: LOW]
|
||||||
|
|
||||||
|
- **现状**: search 模块是空壳
|
||||||
|
- **影响**: 无法搜索日记内容
|
||||||
|
- **修复成本**: 1 天
|
||||||
|
- **计划**: Isar FTS 索引 + 搜索结果页
|
||||||
|
|
||||||
|
### TD-10: SyncEngine 蜂窝数据未支持 [利息: LOW]
|
||||||
|
|
||||||
|
- **现状**: 仅 WiFi 自动同步
|
||||||
|
- **影响**: 无 WiFi 时无法同步
|
||||||
|
- **修复成本**: 0.5 天
|
||||||
|
- **计划**: 添加用户设置允许蜂窝数据同步
|
||||||
|
|
||||||
|
## 已偿还债务
|
||||||
|
|
||||||
|
| 债务 | 还清日期 | 投入 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 手写引擎单层 Canvas 卡顿 | 2026-06-01 | 双层架构 + 光栅化缓存 |
|
||||||
|
| 编辑器 onSave 仅 debugPrint | 2026-06-01 | IsarJournalRepository 持久化 |
|
||||||
|
| SyncEngine 队列纯内存 | 2026-06-01 | Isar PendingOperation 持久化 |
|
||||||
|
| Isar 未初始化 | 2026-06-01 | main.dart init + Schema 注册 |
|
||||||
|
| 深色模式适配问题 | 2026-05-31 | F11 修复 + B7 测试套件 |
|
||||||
|
|
||||||
|
## 偿还优先级排名
|
||||||
|
|
||||||
|
| 优先级 | 债务 | 预估投入 | 预期效果 |
|
||||||
|
|--------|------|---------|---------|
|
||||||
|
| P0 | TD-1 authorId 硬编码 | 0.5 天 | 数据可关联真实用户 |
|
||||||
|
| P0 | TD-3 Docker 部署 | 0.5 天 | 后端可运行验证 |
|
||||||
|
| P1 | TD-7 token 持久化 | 0.5 天 | 免重复登录 |
|
||||||
|
| P1 | TD-8 加载已有日记 | 1 天 | 编辑器完整可用 |
|
||||||
|
| P1 | TD-4 toImage 异步 | 1 天 | 消除大笔画卡顿 |
|
||||||
|
| P2 | TD-2 CI/CD | 1 天 | 自动化质量守卫 |
|
||||||
|
| P2 | TD-5 前端测试 | 3 天 | 回归保护 |
|
||||||
120
wiki/architecture.md
Normal file
120
wiki/architecture.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
title: 架构决策
|
||||||
|
updated: 2026-06-01
|
||||||
|
status: active
|
||||||
|
tags: [architecture, base, multi-tenant, security]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 架构决策
|
||||||
|
|
||||||
|
> 从 [[index]] 导航。关联: [[data-layer]] [[erp-diary]] [[frontend]]
|
||||||
|
|
||||||
|
## 1. 设计决策
|
||||||
|
|
||||||
|
### Q: 为什么从 HMS 剥离而不是 fork?
|
||||||
|
|
||||||
|
独立 `base.git` 仓库(https://git.stableeasy.com/iven/base.git)作为通用 ERP 基座,多个项目可独立克隆后添加业务模块。fork 会将两个项目的 git 历史绑定,无法独立演进。
|
||||||
|
|
||||||
|
### Q: 为什么用 Feature Flag 组装模块?
|
||||||
|
|
||||||
|
`cargo build --features diary` 按需引入暖记模块。基座 crate(auth/config/message/workflow/plugin)不需要 diary 功能时零开销。
|
||||||
|
|
||||||
|
### Q: 为什么选 Flutter 而不是原生?
|
||||||
|
|
||||||
|
Android + iOS 跨平台首发。CustomPainter + Listener 手写性能满足 <16ms 目标,一套代码双端覆盖。
|
||||||
|
|
||||||
|
### Q: 为什么选 BLoC 不是 Provider/Riverpod?
|
||||||
|
|
||||||
|
编辑器、日历、同步引擎等复杂交互需要 Event/State 显式建模。BLoC 的单向数据流在复杂场景下比 Provider 更可控。
|
||||||
|
|
||||||
|
### 基座剥离 — 7 个耦合点
|
||||||
|
|
||||||
|
从 HMS 剥离到 base.git 时遇到的耦合及解决方案:
|
||||||
|
|
||||||
|
| 耦合点 | 问题 | 解决 |
|
||||||
|
|--------|------|------|
|
||||||
|
| main.rs 业务初始化 | 硬编码 health 模块注册 | 改为 Feature Flag 动态注册 |
|
||||||
|
| AppState 业务字段 | 嵌入 HealthState 等 | 改为 trait object / AnyMap |
|
||||||
|
| 迁移文件混合 | 基座 + 业务迁移在同一目录 | 按 prefix 分类,业务迁移独立 |
|
||||||
|
| 路由注册 | 业务路由写死在基座 | Module trait 的 routes() 方法 |
|
||||||
|
| 事件定义 | 业务事件在基座 crate | 各模块自定义 event.rs |
|
||||||
|
| 权限 seed | 业务权限码在基座迁移 | 各模块自带 seed 迁移 |
|
||||||
|
| Cargo.toml | 业务依赖在 workspace 根 | 各业务 crate 独立管理 |
|
||||||
|
|
||||||
|
## 2. 关键文件 + 数据流
|
||||||
|
|
||||||
|
### 仓库拓扑
|
||||||
|
|
||||||
|
```
|
||||||
|
HMS (G:\hms) [只读]
|
||||||
|
└─复制→ base.git (https://git.stableeasy.com/iven/base.git)
|
||||||
|
└─克隆→ nj.git (暖记 = 基座 + erp-diary + Flutter)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cargo Workspace
|
||||||
|
|
||||||
|
```
|
||||||
|
nj/crates/
|
||||||
|
├── erp-core/ # 基座 — 模块系统/事件/加密/错误
|
||||||
|
├── erp-auth/ # 基座 — JWT/RBAC/用户/角色
|
||||||
|
├── erp-config/ # 基座 — 字典/菜单/设置
|
||||||
|
├── erp-message/ # 基座 — 消息/通知/SSE
|
||||||
|
├── erp-workflow/ # 基座 — BPMN 工作流
|
||||||
|
├── erp-plugin/ # 基座 — WASM 插件运行时
|
||||||
|
├── erp-server/ # Axum 入口 + 迁移 + 路由组装
|
||||||
|
└── erp-diary/ # 暖记业务模块 (~5,500 行新增)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 集成契约
|
||||||
|
|
||||||
|
| 方向 | 模块 | 接口 | 触发时机 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 被调用 ← | erp-server | `DiaryModule::new()` | 启动时 feature=diary |
|
||||||
|
| 调用 → | erp-core | `EventBus` | diary.created 等事件 |
|
||||||
|
| 调用 → | erp-auth | `require_permission()` | handler 层权限守卫 |
|
||||||
|
| 提供 → | erp-server | `routes()` + `entities()` | Axum 路由注册 |
|
||||||
|
|
||||||
|
## 3. 代码逻辑
|
||||||
|
|
||||||
|
### 不变量
|
||||||
|
|
||||||
|
⚡ **G:\hms 只读** — 绝不修改 HMS 源码,所有操作在 nj 仓库进行
|
||||||
|
|
||||||
|
⚡ **业务 crate 间无直接依赖** — erp-diary 不 import erp-health,只通过 EventBus 通信
|
||||||
|
|
||||||
|
⚡ **所有查询带 tenant_id** — 多租户中间件自动注入,API 路径不含 tenant_id
|
||||||
|
|
||||||
|
⚡ **软删除不硬删** — 所有 delete 操作设置 deleted_at,不做 DELETE FROM
|
||||||
|
|
||||||
|
⚡ **version 乐观锁** — 所有 Entity 的 version 字段用于同步冲突检测
|
||||||
|
|
||||||
|
### 多租户隔离策略
|
||||||
|
|
||||||
|
中间件从 JWT 提取 tenant_id → 注入请求扩展 → handler/service 层自动过滤。API 路径不含 tenant_id(`/api/v1/diary/...` 而非 `/{tenant}/diary/...`)。
|
||||||
|
|
||||||
|
### PIPL 合规
|
||||||
|
|
||||||
|
- 未满 14 岁必须家长授权
|
||||||
|
- 最小必要数据(昵称 + 年级,无需真实姓名)
|
||||||
|
- AES-256-GCM 加密 + TLS 传输
|
||||||
|
- 30 天内注销删除所有关联数据
|
||||||
|
- 班级码:6 位混合码,5 次错误锁定 30 分钟
|
||||||
|
|
||||||
|
## 4. 活跃问题 + 陷阱
|
||||||
|
|
||||||
|
| 问题 | 级别 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 上下文窗口耗尽 | HIGH | 已缓解 | CLAUDE.md §8 会话交接机制 |
|
||||||
|
| Windows Defender 锁定 exe | MEDIUM | 需手动 | 排除 target/ 目录 |
|
||||||
|
| Docker 部署未验证 | MEDIUM | 待做 | docker/ 目录存在但未测试 |
|
||||||
|
|
||||||
|
### 历史教训
|
||||||
|
|
||||||
|
- 基座剥离耗时比预期长(7 个耦合点需逐一解耦)
|
||||||
|
- Isar 3.x 扩展方法不随传递 import 传播,必须显式 import
|
||||||
|
|
||||||
|
## 5. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-01 | 初始创建 — 架构决策、基座剥离记录、集成契约 |
|
||||||
127
wiki/data-layer.md
Normal file
127
wiki/data-layer.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
title: 数据层
|
||||||
|
updated: 2026-06-01
|
||||||
|
status: active
|
||||||
|
tags: [isar, offline-first, sync, repository-pattern]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 数据层 — Isar 本地存储 + SyncEngine 离线同步
|
||||||
|
|
||||||
|
> 从 [[index]] 导航。关联: [[handwriting-engine]] [[frontend]] [[erp-diary]]
|
||||||
|
|
||||||
|
## 1. 设计决策
|
||||||
|
|
||||||
|
### Q: 为什么离线优先?
|
||||||
|
|
||||||
|
小学生使用场景中网络不稳定(校园 WiFi 信号差、家庭网络波动)。所有功能必须离线可用,联网后自动同步。
|
||||||
|
|
||||||
|
### Q: 为什么选 Isar 不用 SQLite?
|
||||||
|
|
||||||
|
Isar 提供 FTS 全文搜索、内置加密、零配置 Flutter 原生支持、类型安全的查询 API。对 Flutter 项目比 SQLite+drift 更轻量。
|
||||||
|
|
||||||
|
### Q: 为什么用版本号冲突检测?
|
||||||
|
|
||||||
|
离线编辑 → 联网同步场景下,同一条数据可能在多端修改。version 字段做乐观锁,Phase 1 使用"本地优先"策略(本地版本覆盖远端),Phase 2 提供 UI 手动解决。
|
||||||
|
|
||||||
|
### Q: 为什么 Repository 抽象接口?
|
||||||
|
|
||||||
|
`JournalRepository` 抽象接口 → `IsarJournalRepository`(本地)+ `RemoteJournalRepository`(远程)。BLoC 只依赖抽象,切换实现不影响业务逻辑。
|
||||||
|
|
||||||
|
## 2. 关键文件 + 数据流
|
||||||
|
|
||||||
|
### 核心文件
|
||||||
|
|
||||||
|
| 文件 | 行数 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data/local/isar_database.dart` | 72 | 单例管理,3 Schema 注册 |
|
||||||
|
| `data/repositories/isar_journal_repository.dart` | 333 | CRUD + 乐观锁 + 软删除 |
|
||||||
|
| `data/repositories/remote_journal_repository.dart` | 114 | Dio HTTP API 代理 |
|
||||||
|
| `data/repositories/journal_repository.dart` | 184 | 抽象接口 + InMemory 测试实现 |
|
||||||
|
| `data/services/sync_engine.dart` | 338 | WiFi 增量同步 + Isar 队列持久化 |
|
||||||
|
| `data/local/collections/` | 3+3 文件 | Isar Collection 定义 + 生成 Schema |
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
UI 操作
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
EditorBloc.onSave (2s debounce)
|
||||||
|
│
|
||||||
|
├─→ IsarJournalRepository.createJournal() [首次]
|
||||||
|
├─→ IsarJournalRepository.updateJournal() [后续]
|
||||||
|
│ └─ 乐观锁: version 匹配 → +1 → 写入
|
||||||
|
│ └─ 不匹配 → throw StateError('版本冲突')
|
||||||
|
│
|
||||||
|
└─→ SyncEngine.enqueue() [待同步操作入队]
|
||||||
|
│ WiFi 可用时
|
||||||
|
▼
|
||||||
|
trySync() → RemoteJournalRepository → API → PostgreSQL
|
||||||
|
│ 成功
|
||||||
|
▼
|
||||||
|
persistPendingQueue() [更新 Isar 队列]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 集成契约
|
||||||
|
|
||||||
|
| 方向 | 组件 | 接口 | 触发时机 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 被调用 ← | EditorBloc | `onSave(EditorState)` | 笔画/元素变更 2s 后 |
|
||||||
|
| 调用 → | IsarJournalRepository | `createJournal/updateJournal` | 首次/后续保存 |
|
||||||
|
| 调用 → | SyncEngine | `enqueue/trySync` | 数据变更/网络恢复 |
|
||||||
|
| 启动时 → | SyncEngine | `restorePendingQueue()` | app.dart 创建时 |
|
||||||
|
|
||||||
|
## 3. 代码逻辑
|
||||||
|
|
||||||
|
### 不变量
|
||||||
|
|
||||||
|
⚡ **Isar 扩展方法需显式 import** — `import 'package:isar/isar.dart'` 不会随传递 import 传播,必须在使用 `findAll()`/`findFirst()` 的文件中显式导入
|
||||||
|
|
||||||
|
⚡ **笔画序列化固定 ID** — `${journalId}_strokes` 保证每次覆盖而非重复创建
|
||||||
|
|
||||||
|
⚡ **SyncEngine 双写** — 操作同时存在于内存 Queue 和 Isar PendingOperationCollection
|
||||||
|
|
||||||
|
⚡ **乐观锁** — updateJournal 比对 version,不匹配抛 StateError
|
||||||
|
|
||||||
|
⚡ **软删除级联** — 删除日记时关联元素的 isDeleted 也设为 true
|
||||||
|
|
||||||
|
### 3 个 Isar Collection
|
||||||
|
|
||||||
|
| Collection | Isar 主键 | 业务 ID | 用途 |
|
||||||
|
|------------|----------|---------|------|
|
||||||
|
| JournalEntryCollection | autoIncrement | id (indexed) | 日记条目 |
|
||||||
|
| JournalElementCollection | autoIncrement | id (indexed) | 日记元素 |
|
||||||
|
| PendingOperationCollection | autoIncrement | id (indexed) | 同步操作队列 |
|
||||||
|
|
||||||
|
### 初始化链
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// main.dart
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await IsarDatabase.init(); // 注册 3 个 Schema
|
||||||
|
|
||||||
|
// app.dart
|
||||||
|
final syncEngine = SyncEngine(apiClient: apiClient);
|
||||||
|
syncEngine.restorePendingQueue(); // fire-and-forget 恢复队列
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 活跃问题 + 陷阱
|
||||||
|
|
||||||
|
| 问题 | 级别 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| authorId 硬编码 'local' | HIGH | 待修 | EditorPage 未接入 AuthBloc 获取真实用户 |
|
||||||
|
| SyncEngine 仅 WiFi | MEDIUM | Phase 2 | 蜂窝数据同步未实现 |
|
||||||
|
| 版本冲突静默覆盖 | MEDIUM | Phase 2 | "本地优先"策略,需 UI 手动解决 |
|
||||||
|
| 编辑器未加载已有数据 | MEDIUM | 待做 | journalId 非空时未从 Isar 读取 |
|
||||||
|
|
||||||
|
### 历史教训
|
||||||
|
|
||||||
|
- Isar `findAll()` 编译报"未定义" — 原因是扩展方法不随 import 传播,需显式 `import 'package:isar/isar.dart'`
|
||||||
|
- Isar 3.x 查询链必须通过 `.filter()` 过渡才能调用 `.findAll()`
|
||||||
|
|
||||||
|
## 5. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-01 | Isar 集成完成:3 Collection + Repository + SyncEngine 持久化 (2481c8f) |
|
||||||
|
| 2026-06-01 | 初始创建 — 数据层架构、Isar 踩坑记录 |
|
||||||
101
wiki/erp-diary.md
Normal file
101
wiki/erp-diary.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
title: erp-diary 后端模块
|
||||||
|
updated: 2026-06-01
|
||||||
|
status: active
|
||||||
|
tags: [rust, axum, seaorm, diary, api]
|
||||||
|
---
|
||||||
|
|
||||||
|
# erp-diary — 暖记后端业务模块
|
||||||
|
|
||||||
|
> 从 [[index]] 导航。关联: [[architecture]] [[data-layer]]
|
||||||
|
|
||||||
|
## 1. 设计决策
|
||||||
|
|
||||||
|
### Q: 为什么独立 erp-diary crate?
|
||||||
|
|
||||||
|
基座解耦原则:erp-diary 通过 Feature Flag (`cargo build --features diary`) 按需引入。基座 crate 不依赖业务模块,其他项目可复用基座而不引入日记功能。
|
||||||
|
|
||||||
|
### Q: DiaryError 设计?
|
||||||
|
|
||||||
|
15 种变体枚举(NotFound / VersionConflict / Unauthorized / ContentUnsafe 等),实现 `Into<AppError>` 转换为 HTTP 状态码映射。集成测试验证每个错误码的正确 HTTP 响应。
|
||||||
|
|
||||||
|
### Q: 为什么内容安全过滤在 Service 层?
|
||||||
|
|
||||||
|
日记标题、文字元素的文本需要敏感词检查(含谐音/拼音变体)。放在 Service 层而非 Handler 层,确保即使有新的入口点(事件消费、管理 API)也不会绕过检查。
|
||||||
|
|
||||||
|
## 2. 关键文件 + 数据流
|
||||||
|
|
||||||
|
### 模块结构
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/erp-diary/src/
|
||||||
|
├── lib.rs (206 行) — DiaryModule 实现 + Feature Flag 注册
|
||||||
|
├── dto.rs (569 行) — 请求/响应 DTO + Validate 注解
|
||||||
|
├── error.rs (193 行) — DiaryError 15 种变体 → HTTP 状态码
|
||||||
|
├── event.rs (61 行) — 事件定义 (diary.created 等)
|
||||||
|
├── state.rs (13 行) — DiaryState (DiaryModule 专用状态)
|
||||||
|
├── entity/ (15 文件) — SeaORM Entity
|
||||||
|
├── service/ (10 文件) — 业务逻辑
|
||||||
|
└── handler/ (8 文件) — HTTP Handler + utoipa 注解
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entity 清单 (15 个)
|
||||||
|
|
||||||
|
achievement, class_member, comment, handwriting_stroke, journal_element, journal_entry, parent_child_binding, school_class, sticker, sticker_pack, teacher_profile, template, topic_assignment, user_achievement, user_settings
|
||||||
|
|
||||||
|
### Service 清单 (10 个)
|
||||||
|
|
||||||
|
journal, class, comment, content_safety, achievement, mood_stats, notification, sticker, sync, topic
|
||||||
|
|
||||||
|
### API 端点
|
||||||
|
|
||||||
|
| 端点前缀 | Handler | 主要操作 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `/api/v1/diary/journals` | journal_handler | CRUD + 列表过滤 |
|
||||||
|
| `/api/v1/diary/journals/:id/elements` | journal_handler (同) | 元素 CRUD |
|
||||||
|
| `/api/v1/diary/classes` | class_handler | 班级 CRUD + 班级码 |
|
||||||
|
| `/api/v1/diary/comments` | comment_handler | 评论 CRUD |
|
||||||
|
| `/api/v1/diary/topics` | topic_handler | 主题布置 |
|
||||||
|
| `/api/v1/diary/achievements` | achievement_handler | 成就系统 |
|
||||||
|
| `/api/v1/diary/stickers` | sticker_handler | 贴纸管理 |
|
||||||
|
| `/api/v1/diary/stats` | stats_handler | 心情/写作统计 |
|
||||||
|
| `/api/v1/diary/sync` | sync_handler | 增量同步 API |
|
||||||
|
|
||||||
|
### 集成契约
|
||||||
|
|
||||||
|
| 方向 | 模块 | 接口 | 触发时机 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 提供 → | erp-server | `DiaryModule::routes()` | 启动 feature=diary |
|
||||||
|
| 调用 → | erp-core | `EventBus::publish()` | 日记创建/更新/删除 |
|
||||||
|
| 调用 → | erp-auth | `require_permission()` | 每个 handler 入口 |
|
||||||
|
| 调用 → | erp-message | 通知服务 | 评论/班级事件 |
|
||||||
|
|
||||||
|
## 3. 代码逻辑
|
||||||
|
|
||||||
|
### 不变量
|
||||||
|
|
||||||
|
⚡ **所有 Entity 含标准字段** — id / tenant_id / created_at / updated_at / created_by / updated_by / deleted_at / version
|
||||||
|
|
||||||
|
⚡ **软删除** — 查询带 `deleted_at IS NULL` 过滤,不做硬删除
|
||||||
|
|
||||||
|
⚡ **多租户隔离** — 中间件注入 tenant_id,handler 不从 API 路径获取
|
||||||
|
|
||||||
|
⚡ **权限守卫** — 每个 handler 方法第一行 `require_permission("diary.xxx")`
|
||||||
|
|
||||||
|
⚡ **输入验证** — DTO 使用 `#[derive(Validate)]` + handler 层调 `.validate()`
|
||||||
|
|
||||||
|
⚡ **内容安全** — 日记标题/文字通过 ContentSafetyService 检查
|
||||||
|
|
||||||
|
## 4. 活跃问题 + 陷阱
|
||||||
|
|
||||||
|
| 问题 | 级别 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| Docker 部署未验证 | HIGH | 待做 | docker/ 目录存在但未实际运行 |
|
||||||
|
| CI/CD 未建立 | MEDIUM | 待做 | 无自动化构建/测试/部署 |
|
||||||
|
| 文件上传未实现 | MEDIUM | 待做 | 照片/贴纸文件上传参考健康模块 |
|
||||||
|
|
||||||
|
## 5. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-01 | 初始创建 — Entity/Service/Handler 清单、API 端点、集成契约 |
|
||||||
118
wiki/frontend.md
Normal file
118
wiki/frontend.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
title: Flutter 前端
|
||||||
|
updated: 2026-06-01
|
||||||
|
status: active
|
||||||
|
tags: [flutter, bloc, design-system, responsive]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Flutter 前端
|
||||||
|
|
||||||
|
> 从 [[index]] 导航。关联: [[handwriting-engine]] [[data-layer]]
|
||||||
|
|
||||||
|
## 1. 设计决策
|
||||||
|
|
||||||
|
### Q: 为什么 BLoC 不是 Provider/Riverpod?
|
||||||
|
|
||||||
|
编辑器(strokes + elements + undo/redo + autoSave)、同步引擎(pending queue + network status)、日历(date range + mood filter)等复杂交互需要 Event/State 显式建模。BLoC 的单向数据流比 Provider 的 notifyListeners() 更可控。
|
||||||
|
|
||||||
|
### Q: 为什么 go_router?
|
||||||
|
|
||||||
|
声明式路由 + 深链接支持 + 路由守卫(auth guard 重定向未登录用户)。比 Navigator 2.0 更简洁。
|
||||||
|
|
||||||
|
### Q: 设计系统 7 色双模主题?
|
||||||
|
|
||||||
|
暖记定位"温暖治愈",浅色模式奶油白(#FFF8F0) + 珊瑚色(#E07A5F) + 鼠尾草绿(#81B29A);深色模式自动映射为暖色调暗色。面向小学生,所有颜色避免冷硬感。
|
||||||
|
|
||||||
|
### Q: 为什么手写模型不用 freezed?
|
||||||
|
|
||||||
|
Stroke/StrokePoint 是热路径高频创建对象,freezed 生成的代码有额外开销。手写实现 copyWith + toJson/fromJson 更轻量。
|
||||||
|
|
||||||
|
## 2. 关键文件 + 数据流
|
||||||
|
|
||||||
|
### 16 个功能模块
|
||||||
|
|
||||||
|
| 模块 | BLoC | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| editor | EditorBloc | 手写 + 元素 + 撤销重做 + 自动保存 |
|
||||||
|
| auth | AuthBloc | 登录/注册/角色选择/班级码加入 |
|
||||||
|
| home | HomeBloc | 首页日记列表 + 搜索 |
|
||||||
|
| calendar | CalendarBloc | 日历视图 + 日期过滤 |
|
||||||
|
| mood | MoodBloc | 心情统计 + 趋势图 |
|
||||||
|
| class_ | ClassBloc | 班级管理 + 成员列表 |
|
||||||
|
| achievement | AchievementBloc | 成就徽章系统 |
|
||||||
|
| stickers | StickerBloc | 贴纸库浏览 + 选择 |
|
||||||
|
| templates | TemplateBloc | 模板画廊 |
|
||||||
|
| profile | SettingsBloc | 主题切换 + 个人设置 |
|
||||||
|
| search | — | 日记搜索 (Isar FTS 待实现) |
|
||||||
|
| teacher | — | 老师主题发布 + 批改 |
|
||||||
|
| parent | — | 家长监护 + 数据管理 |
|
||||||
|
| settings | — | 设置页面 UI |
|
||||||
|
|
||||||
|
### 注入链 (app.dart)
|
||||||
|
|
||||||
|
```
|
||||||
|
MultiRepositoryProvider
|
||||||
|
├─ ApiClient
|
||||||
|
├─ AuthRepository
|
||||||
|
├─ JournalRepository (= IsarJournalRepository, 离线优先)
|
||||||
|
├─ RemoteJournalRepository (供 SyncEngine)
|
||||||
|
├─ SyncEngine
|
||||||
|
├─ ClassRepository
|
||||||
|
└─ SettingsBloc (ChangeNotifier)
|
||||||
|
└─ BlocProvider<AuthBloc>
|
||||||
|
└─ MaterialApp.router (ListenableBuilder 监听主题)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路由表
|
||||||
|
|
||||||
|
`app_router.dart` (269 行) 定义完整路由:
|
||||||
|
- `/` → 首页(auth guard 重定向)
|
||||||
|
- `/login`, `/role-selection`, `/class-code-join`
|
||||||
|
- `/editor/:id?` → 编辑器
|
||||||
|
- `/calendar`, `/mood`, `/class`, `/achievements`
|
||||||
|
- `/stickers`, `/templates`, `/search`
|
||||||
|
- `/teacher/*`, `/parent/*`
|
||||||
|
- `/settings`, `/profile`
|
||||||
|
|
||||||
|
## 3. 代码逻辑
|
||||||
|
|
||||||
|
### 不变量
|
||||||
|
|
||||||
|
⚡ **响应式断点** — 手机 <600px 底部 TabBar 单列 / 平板 600-1024px 侧边双栏 / 桌面 >1024px 三栏
|
||||||
|
|
||||||
|
⚡ **触摸目标 ≥ 44px** — 面向小学生,所有可交互元素不小于 44px
|
||||||
|
|
||||||
|
⚡ **设计 Token 统一管理** — `core/constants/design_tokens.dart` 定义间距/圆角/阴影
|
||||||
|
|
||||||
|
⚡ **SettingsBloc 用 ChangeNotifier** — 不用 BLoC 因为设置是全局状态,ChangeNotifier + ListenableBuilder 更轻量
|
||||||
|
|
||||||
|
### 主题系统
|
||||||
|
|
||||||
|
```
|
||||||
|
AppTheme.light() / AppTheme.dark()
|
||||||
|
├─ 7 色 × 2 模式 (bg/accent/secondary/tertiary/fg/surface/rose)
|
||||||
|
├─ 3 套字体 (Noto Sans SC / Caveat / JetBrains Mono)
|
||||||
|
├─ 4 级圆角 (10/16/22/28/pill)
|
||||||
|
└─ 动画曲线 cubic-bezier(0.34, 1.56, 0.64, 1) — 弹性过冲
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 活跃问题 + 陷阱
|
||||||
|
|
||||||
|
| 问题 | 级别 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 编辑器不加载已有数据 | HIGH | 待做 | journalId 非空时需从 Isar 读取 |
|
||||||
|
| 搜索功能空壳 | MEDIUM | 待做 | Isar FTS 未实现 |
|
||||||
|
| 深色模式细节 | LOW | 持续 | 部分组件深色适配需检查 |
|
||||||
|
|
||||||
|
### 历史教训
|
||||||
|
|
||||||
|
- F11 深色模式修复需要 bloat bloc 测试套件同步更新 (05317d5)
|
||||||
|
- NuanjiApp 是 StatelessWidget,build() 可被调用多次 → 全局依赖应在 build() 中创建单例
|
||||||
|
|
||||||
|
## 5. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-01 | IsarJournalRepository 注入为主 JournalRepository (2481c8f) |
|
||||||
|
| 2026-06-01 | 设置页 UI + Mood/成就/贴纸 BLoC (8331db6) |
|
||||||
|
| 2026-06-01 | 初始创建 — 16 模块地图、注入链、设计系统 |
|
||||||
124
wiki/handwriting-engine.md
Normal file
124
wiki/handwriting-engine.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
title: 手写引擎
|
||||||
|
updated: 2026-06-01
|
||||||
|
status: active
|
||||||
|
tags: [handwriting, performance, canvas, perfect-freehand]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 手写引擎
|
||||||
|
|
||||||
|
> 从 [[index]] 导航。关联: [[data-layer]] [[frontend]]
|
||||||
|
|
||||||
|
暖记的**核心价值** — 保留用户真实笔迹。目标:<16ms 延迟(p99),在 Samsung Tab A9 / iPad 10th 上流畅运行。
|
||||||
|
|
||||||
|
## 1. 设计决策
|
||||||
|
|
||||||
|
### Q: 为什么用 Listener 不用 GestureDetector?
|
||||||
|
|
||||||
|
GestureDetector 内部有手势竞技场(gesture arena),额外延迟约 5-8ms。Listener 直接接收 PointerDownEvent/PointerMoveEvent,跳过竞技场,延迟最低。
|
||||||
|
|
||||||
|
### Q: 为什么双层 Canvas 架构?
|
||||||
|
|
||||||
|
单层 Canvas 每帧需要重绘全部 N 条笔画,复杂度 O(N×P)。双层架构将已完成笔画光栅化为 ui.Image 位图,每帧只绘制当前活跃笔画,复杂度降至 O(P_current)。
|
||||||
|
|
||||||
|
### Q: 为什么选 perfect_freehand?
|
||||||
|
|
||||||
|
支持压力感知、速度相关的变宽笔画,开箱即用的高质量笔迹渲染。4 种画笔(钢笔/铅笔/马克笔/橡皮擦)通过不同参数配置实现。
|
||||||
|
|
||||||
|
### Q: 笔画存储策略?
|
||||||
|
|
||||||
|
HandwritingStroke 作为独立 SeaORM Entity,大字段(points JSON)隔离,日记列表查询时延迟加载。前端 Isar 中笔画序列化为 handwriting_ref 元素,固定 ID `${journalId}_strokes`。
|
||||||
|
|
||||||
|
## 2. 关键文件 + 数据流
|
||||||
|
|
||||||
|
### 核心文件
|
||||||
|
|
||||||
|
| 文件 | 行数 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| `features/editor/widgets/handwriting_canvas.dart` | 307 | Listener + 双层 Stack + 掌心抑制 |
|
||||||
|
| `features/editor/widgets/stroke_cache.dart` | 303 | StrokeRasterCache 光栅化 + 合成 |
|
||||||
|
| `features/editor/widgets/active_stroke_painter.dart` | 35 | 实时绘制当前笔画 |
|
||||||
|
| `features/editor/widgets/cached_strokes_painter.dart` | 35 | drawImage 绘制缓存位图 |
|
||||||
|
| `features/editor/widgets/stroke_renderer.dart` | ~120 | 4 种画笔渲染(pen/pencil/marker/eraser) |
|
||||||
|
| `features/editor/widgets/stroke_model.dart` | 112 | Stroke/StrokePoint 数据模型 |
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
PointerDown/Move/Up Events
|
||||||
|
│ (Listener, not GestureDetector)
|
||||||
|
▼
|
||||||
|
StrokePoint (x, y, pressure, timestamp)
|
||||||
|
│ accumulate
|
||||||
|
▼
|
||||||
|
Stroke (id, points[], brushType, color, width)
|
||||||
|
│ ValueNotifier → 触发重绘
|
||||||
|
▼
|
||||||
|
ActiveStrokePainter ──── 实时绘制当前笔画
|
||||||
|
│ onStrokeCompleted
|
||||||
|
▼
|
||||||
|
StrokeRasterCache.addStroke()
|
||||||
|
│ toImage() 光栅化
|
||||||
|
▼
|
||||||
|
compositeImage (ui.Image) ──── 所有完成笔画合成位图
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CachedStrokesPainter ──── drawImage 绘制缓存
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 代码逻辑
|
||||||
|
|
||||||
|
### O(1) 点缓冲
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 旧方案:O(N²) — 每次 rebuild 都 spread 整个列表
|
||||||
|
final points = [..._allPoints, newPoint];
|
||||||
|
|
||||||
|
// 新方案:O(1) — 可变缓冲区 + ValueNotifier
|
||||||
|
_currentPoints.add(newPoint);
|
||||||
|
_pointsNotifier.value = _currentPoints;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 橡皮擦实现
|
||||||
|
|
||||||
|
```
|
||||||
|
saveLayer() // 保存当前画布状态
|
||||||
|
drawPath(eraserPath, BlendMode.dstOut) // 用 dstOut 混合模式"擦除"
|
||||||
|
restore() // 恢复,不穿透背景
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式切换
|
||||||
|
|
||||||
|
```
|
||||||
|
旧方案:if (isDrawingMode) Canvas() else ElementLayer() — 销毁重建
|
||||||
|
新方案:IgnorePointer(ignoring: !isDrawingMode) — 不销毁,只控制交互
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不变量
|
||||||
|
|
||||||
|
⚡ **shouldRepaint 守卫** — CachedStrokesPainter 仅在 compositeImage 引用变化时重绘
|
||||||
|
|
||||||
|
⚡ **_currentPoints 可变缓冲** — 不创建新 List,直接 add + ValueNotifier 通知
|
||||||
|
|
||||||
|
⚡ **掌心抑制** — 通过 PointerDeviceKind 过滤,仅处理 touch 事件
|
||||||
|
|
||||||
|
## 4. 活跃问题 + 陷阱
|
||||||
|
|
||||||
|
| 问题 | 级别 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| toImage() 同步阻塞 | HIGH | 待修 | 光栅化在主线程,大笔画可能卡 UI |
|
||||||
|
| 画布尺寸变化缓存失效 | MEDIUM | 待修 | 屏幕旋转时需平滑过渡 |
|
||||||
|
| 橡皮擦视觉反馈 | LOW | 待优化 | 擦除区域无实时预览 |
|
||||||
|
|
||||||
|
### 历史教训
|
||||||
|
|
||||||
|
- 初版用 GestureDetector,延迟约 20ms → 改用 Listener 降至 <16ms
|
||||||
|
- 初版 if/else 切换模式会销毁 Canvas → IgnorePointer 保持 Widget 树稳定
|
||||||
|
- 单层 Canvas 100+ 笔画时明显卡顿 → 双层 + 光栅化缓存解决
|
||||||
|
|
||||||
|
## 5. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-01 | 初始创建 — 双层架构、性能优化记录、活跃问题 |
|
||||||
|
| 2026-06-01 | 性能优化提交 (e07da7a):双层 Canvas + 光栅化缓存 + O(1) 点缓冲 |
|
||||||
84
wiki/index.md
Normal file
84
wiki/index.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 暖记 (Nuanji) — 知识库
|
||||||
|
|
||||||
|
> **温暖治愈风格的手写手账日记 App**,面向小学生首发,核心价值是保留真实笔迹。从 [[architecture]] 导航。
|
||||||
|
|
||||||
|
## 关键数字
|
||||||
|
|
||||||
|
> 最后更新: 2026-06-01 | 基线: main (2481c8f)
|
||||||
|
|
||||||
|
| 指标 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| Rust crate | 8 个(6 基座 + 1 入口 + erp-diary 新增) |
|
||||||
|
| Rust 新增代码 | ~5,500 行(erp-diary) |
|
||||||
|
| Dart 文件 | 70 个(~18,200 行,含生成代码) |
|
||||||
|
| SeaORM Entity | 15 个(erp-diary) |
|
||||||
|
| 数据库迁移 | 15 个(diary 相关) |
|
||||||
|
| BLoC 模块 | 12 个 |
|
||||||
|
| Flutter features | 16 个 |
|
||||||
|
| Isar Collection | 3 个(JournalEntry / JournalElement / PendingOperation) |
|
||||||
|
| 后端测试 | ~50 个通过 |
|
||||||
|
| flutter analyze | 0 error |
|
||||||
|
| Git 提交 | 17 次 |
|
||||||
|
|
||||||
|
## 系统数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
用户手写/涂鸦
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
HandwritingCanvas (Listener → StrokePoint)
|
||||||
|
│ Stroke
|
||||||
|
▼
|
||||||
|
EditorBloc (strokes + elements)
|
||||||
|
│ onSave (2s debounce)
|
||||||
|
▼
|
||||||
|
IsarJournalRepository ──→ Isar 本地数据库
|
||||||
|
│ │
|
||||||
|
│ ▼ (启动恢复)
|
||||||
|
│ SyncEngine (pending queue)
|
||||||
|
│ │ WiFi 可用
|
||||||
|
▼ ▼
|
||||||
|
JournalEntry RemoteJournalRepository
|
||||||
|
+ JournalElement │
|
||||||
|
▼
|
||||||
|
API Client (Dio)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Axum → erp-diary → PostgreSQL
|
||||||
|
```
|
||||||
|
|
||||||
|
## 模块导航
|
||||||
|
|
||||||
|
- [[architecture]] — 仓库拓扑、基座继承、Feature Flag、多租户、安全合规
|
||||||
|
- [[handwriting-engine]] — 双层 Canvas、光栅化缓存、perfect_freehand、4 种画笔
|
||||||
|
- [[data-layer]] — Isar 本地存储、Repository 模式、SyncEngine 离线同步
|
||||||
|
- [[frontend]] — Flutter BLoC、16 个功能模块、设计系统、响应式布局
|
||||||
|
- [[erp-diary]] — Rust 后端业务模块、Entity/Service/Handler、API 端点、权限码
|
||||||
|
|
||||||
|
## 症状导航
|
||||||
|
|
||||||
|
| 症状 | 先查 | 再查 | 常见根因 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| 笔画卡顿 >16ms | [[handwriting-engine]] | 光栅化缓存 | shouldRepaint 守卫失效 / 未用 Listener |
|
||||||
|
| 编辑器保存失败 | [[data-layer]] | IsarDatabase | Isar 未初始化 |
|
||||||
|
| Isar `findAll` 未定义 | [[data-layer]] | 扩展方法 | 缺少 `import 'package:isar/isar.dart'` |
|
||||||
|
| 同步版本冲突 | [[data-layer]] | 乐观锁 | version 不匹配 |
|
||||||
|
| 深色模式颜色异常 | [[frontend]] | AppTheme | token 未适配深色值 |
|
||||||
|
| API 返回 403 | [[erp-diary]] | 权限守卫 | 权限码不匹配 |
|
||||||
|
| 迁移执行失败 | [[architecture]] | 多租户 | 表冲突 / 缺失迁移 |
|
||||||
|
| 上下文窗口耗尽 | CLAUDE.md §8 | 会话交接 | 长会话未及时交接 |
|
||||||
|
| 手写穿透背景 | [[handwriting-engine]] | 橡皮擦 | 未用 saveLayer + dstOut |
|
||||||
|
| 模式切换卡顿 | [[handwriting-engine]] | IgnorePointer | if/else 销毁重建 Widget |
|
||||||
|
| 热重载后 Isar 崩溃 | [[data-layer]] | 初始化 | 未 close 就 re-open |
|
||||||
|
| 编辑器加载空白 | [[frontend]] | EditorBloc | journalId 有值但未加载 Isar 数据 |
|
||||||
|
| SyncEngine 队列丢失 | [[data-layer]] | 持久化 | 退出时未调 persistPendingQueue |
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
| 文档 | 位置 |
|
||||||
|
|------|------|
|
||||||
|
| 产品设计规格 v1.2 | `docs/superpowers/specs/2026-05-31-nuanji-warm-notes-design.md` |
|
||||||
|
| 实施规划 v2.1 | `plans/hazy-petting-lampson.md` |
|
||||||
|
| 项目协作规则 | `CLAUDE.md` |
|
||||||
|
| 技术债看板 | `docs/tech-debt-board.md` |
|
||||||
|
| 基座仓库 | https://git.stableeasy.com/iven/base.git |
|
||||||
Reference in New Issue
Block a user