feat(stage4): 自动保存/崩溃恢复/外部修改检测/工作区管理(含合并验收清单)

This commit is contained in:
Muse
2026-11-13 22:43:29 +08:00
parent 0774b70d29
commit e5ad58eb93
12 changed files with 1013 additions and 21 deletions

37
docs/08-stage4-report.md Normal file
View File

@@ -0,0 +1,37 @@
# 阶段 4 · 文件管理与工作区报告(实测记录)
> 日期:2026-11-13 · 范围(需求 §23 阶段 4):Rust/文件系统、最近文件(SQLite 迁移)、自动保存、文件恢复记录、外部修改检测、工作区文件管理、未保存提醒。
## 1. 本阶段实现内容
- **SQLite 存储层**(Rust `src-tauri/src/storage.rs`,rusqlite bundled):四张表 `recent_files / workspaces / prefs / recovery`,数据库位于应用数据目录 `new-markdown.db`,启动时建表并由 Tauri State 托管连接。红线不变:正文只在 .md 文件里,recovery 只是未保存草稿的副本。前端经 `services/store.ts` 访问:桌面走 SQLite、Web 降级走 localStorage(同构接口)。
- **数据迁移**:阶段 1 的 localStorage 数据(最近文件是路径字符串数组、UI 偏好是 JSON)在桌面模式首次启动时一次性迁入 SQLite 并清掉本地副本;兼容两种历史格式。
- **自动保存**:视图菜单可开关(默认开,30 秒);只自动保存「已落盘的脏文档」,保存后同步刷新外部检测基线并清掉对应恢复记录。
- **崩溃恢复**:脏标签每 6 秒把最新草稿写入 recovery(文件按路径键、未命名按标签键);启动时先读磁盘现状做划分——与磁盘一致的记录静默清理,不一致/文件已删/未命名的逐项弹窗(恢复/丢弃/稍后再说);恢复文件文档时以磁盘内容为已保存基线、草稿为当前内容,dirty 状态准确。
- **外部修改检测**:打开/保存时记录基线(mtime+大小),每 4 秒轮询 `stat_file`:
- 干净文档被外部改动 → 自动重新加载并右下角通知;
- 脏文档被外部改动 → 冲突弹窗三选:重新加载磁盘版 / 保留我的内容(更新基线,下次保存覆盖)/ 另存为;
- 文件被外部删除 → 通知,脏文档保留编辑内容、保存时重建。
- **工作区文件管理**:文件树右键菜单(打开/重命名/删除到回收站,Rust `trash` 实现,失败对文件退回永久删除);树头「+」在工作区新建文档(自动补 .md、重名报错不覆盖);重命名已打开文档时标签路径、最近记录、恢复键同步迁移;删除已打开文档会连带关标签。另有 `stat_file / create_text_file / create_dir / rename_path` 命令(重名保护)。
- **关窗拦截**:关闭窗口时先把全部草稿刷入恢复记录;若有已落盘文档未保存,弹「保存并退出 / 不保存退出 / 取消」;只有未命名草稿时直接放行(下次启动靠恢复弹窗找回);在退出弹窗里选「不保存」会清掉对应恢复记录,不让已明确放弃的内容下次再冒出来。
## 2. 构建与测试结果(实跑)
| 项 | 结果 |
|---|---|
| 类型检查 `tsc -b` | ✅ 0 错误 |
| 自动化测试 | ✅ **48/48**(10 文件;本阶段新增 3:恢复键/划分计划、reducer 重载与改路径) |
| Rust 存储层 | ✅ 独立 crate 用真 rusqlite(bundled SQLite 编译运行)断言全过:最近文件上限 30 与置顶去重、偏好 upsert、工作区去重、恢复记录更新/排序/删除 |
| Rust 语法 | ✅ 三个源文件 rustfmt 解析通过(命令包装层的类型检查仍随 Windows 完整 Tauri 构建验,同前几阶段口径) |
| 生产构建 / dev 冒烟 | ✅ 主包 943KB / gzip 292KB;HTTP 200 |
## 3. 已知问题与边界
1. **外部检测是轮询不是文件系统监听**:4 秒间隔、靠 mtime+大小判变;同一秒内同大小改写理论上可漏检。换 notify 监听是阶段 6 性能/体验项(需处理防抖与自身保存回声,轮询版已用基线机制规避了自保存误报)。
2. 自动保存间隔当前固定 30 秒(偏好已落库,设置界面留到阶段 5 主题/设置页统一做);自动保存不处理未命名文档(靠恢复记录兜底,不悄悄替用户决定文件名)。
3. 恢复弹窗「稍后再说」后,本次会话内不再重复弹(记录仍在,下次启动再问);同一文件在两台机器/被外部改名后恢复键失效属预期(按路径键)。
4. 删除文件夹到回收站依赖 `trash` 在 Windows 的实现,真机验;删除后若文件树未即时刷新可用树头 ⟳ 手动刷新(已做 refreshSignal 自动刷新,真机复核)。
## 4. 下一阶段(阶段 5)预告
主题与输出:内置主题(默认/GitHub/Notion/简约蓝/深色护眼/专注写作/经典纸张)+ 自定义 CSS 作用域注入、文档级临时样式、导出 HTML/PDF、另存为 HTML、复制为 HTML、打印与打印预览、导出时资源处理(图片转相对路径/内嵌选项)。