feat: 自动保存/移除撤销与红点, 间隔合并改进(DAILY/稀疏/交错拆分), 删死代码, 更新测试与文档

- organize: seriesKey 移除 weekday, 解锁每3天等 DAILY 序列合并
- organize: fitSeries 放宽阈值(稀疏序列如 BUS LAW Workshop 合并)
- organize: 新增 splitIntoChains, GCD=1 的交错多班次拆为多个系列
- useCalendar: 移除 dirty/history/undo/snapshot/markSaved 死代码
- HeaderBar: 移除撤销按钮与红点●, 下载兜底序列化当前状态
- 测试: 42 项(原32), 覆盖 DAILY/稀疏/交错拆分/两两和候选等场景
- 文档: README 精简, 新增 docs/organize-algorithm.md 与 auto-save-design.md
- 保留移动端 CSS 适配
This commit is contained in:
zikai
2026-07-22 06:32:47 +00:00
parent b49dbd46eb
commit 037b70deec
13 changed files with 617 additions and 137 deletions

73
docs/auto-save-design.md Normal file
View File

@@ -0,0 +1,73 @@
# 自动保存设计
本文档记录 `src/composables/useCalendar.js`
`src/components/HeaderBar.vue` 的保存模型重构。
## 背景
原设计有「dirty 标记 + 下载前确认弹窗 + 快照式 undo/redo最多 50 步)」。
EventDetail 的表单已改为自动应用(`watch(form)` 即时写回 store
commit `1c6ecf9`dirty 仅用于「下载前提示未保存」与红点显示。撤销是
独立的历史栈。
## 重构:移除 dirty / undo
纯前端工具,无持久化需求,所有编辑本就即时生效(自动应用 watch
dirty 标记与确认弹窗是冗余的仪式感undo 栈在自动应用场景下也意义
有限(每次 keystroke 都会 push 历史,栈迅速填满噪声)。移除以降低
复杂度、减少状态字段。
### 删除的死代码(`useCalendar.js`
- `state.history`(快照栈)、`state.dirty`
- `MAX_HISTORY` 常量
- `snapshot()``pushHistory()``restore()`
- `undo()` action、`canUndo` computed
- `markSaved()` action
- 各 action`organize`/`updateEvent`/`deleteEvent`/`addEvent`/
`splitEvent`/`toggleOccurrence`)内的 `pushHistory()`
`state.dirty = true` 调用
- `loadFile` 内的 `state.history = []``state.dirty = false`
- 不再使用的 `cloneEvent` 导入snapshot 曾用)
### `HeaderBar.vue` 改动
- 移除「撤销」按钮
- 移除下载按钮后的红点 `●``<span class="dirty-dot">`)与
`.dirty-dot` 样式
- `onDownload()`:移除 `if (state.dirty && !confirm(...))` 确认弹窗与
`markSaved()` 调用;直接 `serialize()` 下载
## 自动保存语义
- **所有编辑即时生效**`updateEvent`/`deleteEvent`/`addEvent`/
`splitEvent`/`toggleOccurrence` 直接 mutate 响应式 `state.events`
无中间缓冲。EventDetail 的 `watch(form, { deep: true })` 把表单变化
立即 patch 回 store。
- **下载兜底**`onDownload``serialize()` 序列化**当前最新状态**。
即便有任何未触发渲染的边界改动,下载时也会被序列化包含。文件名取
原文件名去 `.ics``.edited.ics`
- **occurrences 不持久化**EventModel 不存 occurrences`OccurrenceGrid`
渲染时实时调 `expandOccurrences(ev)` 计算,`toggleOccurrence` 直接改
`exdates`。无缓存,无不同步风险。
## 状态字段(重构后)
```js
const state = reactive({
meta: null,
events: [],
selectedUid: null,
fileName: null,
viewMode: 'edit', // 'edit' | 'month' | 'week'
previewDate: todayISO(),
})
```
`history`、无 `dirty`、无 `redoStack`。store 仅承载当前状态与视图锚点。
## 取消确认弹窗的理由
`onDownload``dirty` 时弹「有未保存的更改,仍要下载?」。但自动
应用下,所有更改已在 store 中,下载即序列化 store不存在「未保存」。
弹窗只会误导用户以为有数据丢失风险。移除后下载是一次性操作。