timeTable2 dev b352c4e116 refactor: high-cohesion low-coupling cleanup across store and components
ical-io.js:
- Import pad2/parseISODate from date.js (remove local duplicates)
- Rename ICAL.Time helpers to icalTimeTo* for clarity

useCalendar.js:
- Delegate addEvent to createEvent() factory
- Delegate splitEvent to splitRecurringEvent() pure function
- Use cloneEvent() instead of JSON.parse(JSON.stringify())
- Add selectEvent() action (components no longer mutate state.selectedUid directly)

Components:
- EventList: use selectEvent action + toDate() from date.js
- HeaderBar: destructure loadFile at top level (was re-calling useCalendar in handler)
- EventDetail: import DOW from weekday.js (was inlining the array)
- OccurrenceGrid: use toDate() from date.js
2026-07-13 20:06:00 +08:00
2026-07-13 17:31:43 +08:00
2026-07-13 18:53:39 +08:00

timeTable2 - 纯前端 ICS 日历整理器

浏览器内解析、去重、编辑 ICS 日历文件,全程无需 Python 或后端服务。拖入 .ics → 整理去重 → 编辑 → 下载。

功能

  • 📥 拖入即用:拖拽或选择 .ics 文件,浏览器内直接解析
  • 🔁 智能去重:自动检测重复模式,将 61 个扁平事件合并为 5 个 RRULE 重复事件 + 3 个独立事件
  • ✏️ 可视化编辑:编辑标题/地点/时间/重复规则,点击网格切换排除日期
  • ✂️ 拆分事件:把一个重复事件按日期拆成前后两段(如后半学期换教室)
  • 增删事件:新增单次/重复事件,删除任意事件
  • 撤销重做:快照式 undo/redo最多 50 步
  • 导出 ICS:编辑完成下载整理后的 .ics,可直接导入日历应用

快速开始

# 安装依赖(使用 npmmirror 镜像加速,已配在 .npmrc
npm install

# 启动开发服务器
npm run dev

# 构建生产版本
npm run build

# 运行单元测试
npm test

浏览器打开 http://localhost:5173,拖入 .ics 文件即可。

使用流程

  1. 导入:拖入 .ics 文件(或点击选择),左侧显示所有原始事件
  2. 去重:点击顶栏「🔁 整理去重」,自动合并重复事件为 RRULE + EXDATE
  3. 编辑:点击左侧事件,右侧展开编辑表单
    • 修改标题/地点/描述/时间
    • 调整重复规则(频率/间隔/星期/重复到)
    • 点击 occurrence 网格切换排除日期(绿色=包含,红色删除线=排除)
  4. 拆分(可选):点击「✂ 拆分此日程」,选日期,把重复事件拆成前后两段
  5. 新增/删除:点「 新增日程」或「删除此日程」
  6. 撤销:「↶ 撤销」/「↷ 重做」最多 50 步
  7. 下载:点「⬇ 下载 ICS」导出整理后的 .ics(有未保存更改时会提示确认)

项目结构

timeTable2/
├── index.html                      Vite 入口
├── package.json
├── .npmrc                          npmmirror 镜像加速
├── vite.config.js
├── vitest.config.js
├── src/
│   ├── main.js                     应用挂载
│   ├── App.vue                     布局装配
│   ├── composables/
│   │   └── useCalendar.js          中心 store + 快照 undo/redo
│   ├── lib/
│   │   ├── weekday.js              星期常量与工具(统一 getDay 索引)
│   │   ├── ical-io.js              ICS 文本 <-> plain modelical.js 封装)
│   │   ├── recur.js                RRULE 网格展开
│   │   └── organize.js             GCD 去重算法(移植自 Python 版)
│   └── components/
│       ├── HeaderBar.vue           顶栏:整理/撤销/重做/导入/下载
│       ├── DropZone.vue            拖拽/选择文件
│       ├── EventList.vue           侧栏事件列表
│       ├── EventDetail.vue         编辑表单 + 拆分
│       └── OccurrenceGrid.vue      排除日期网格
└── test/                           Vitest 单元测试34 个)
    ├── golden-org.ics              测试用例61 事件的真实课表)
    ├── weekday.test.js
    ├── ical-io.test.js
    ├── recur.test.js
    └── organize.test.js

三个核心 lib 模块

文件 作用
ical-io.js ICS 文本解析为普通对象、序列化回 ICS 文本ical.js 封装)
organize.js GCD 间隔检测 + 系列分组 + EXDATE 生成(移植自 ical_organizer.py
recur.js 从 RRULE + EXDATE 按需展开 occurrence 网格

算法

去重算法从 timeTable/ical_organizer.py 1:1 移植:

  1. 按 (标题 + 地点 + 开始时间 + 时长 + 星期) 将事件分组
  2. 计算组内相邻日期间隔的 GCD,识别:
    • GCD = 7 → FREQ=WEEKLY(每周,含 BYDAY
    • GCD = 1 → FREQ=DAILY(每天)
    • GCD = 14 → FREQ=WEEKLY;INTERVAL=2(每两周)
  3. 构建期望网格,网格上缺失的日期写入 EXDATE(如假期)
  4. 不符合规律的组(跳过次数 ≥ 事件数)保留为独立事件
  5. 合并后保留首个事件的原始 UID避免重新导入冲突

验证:对 org.ics61 事件)整理后输出 8 个事件5 重复 + 3 独立),与 Python 版 ical_organizer.py --verify 结果一致。下载的 ICS 用 Python recurring-ical-events 展开61 个 occurrence 与原始文件完全匹配。

技术栈

选择
框架 Vue 3.5SFC<script setup>
构建 Vite 6
ICS 库 ical.js 2.x仅用于文本↔对象转换
状态管理 composable 单例 store无 Pinia
测试 Vitest
包源 npmmirror.com中国镜像加速

与 timeTable 的关系

timeTablePython 版)使用 Flask + icalendar 库,需创建 venv 并启动本地服务器。 timeTable2 是纯前端等价实现:零安装、双击即用、算法行为一致。

对比 timeTable (Python) timeTable2 (前端)
运行方式 venv + Flask 服务器 浏览器直接打开
依赖 icalendar, flask, recurring-ical-events ical.js
整理器 ical_organizer.py 命令行 网页内「整理去重」按钮
编辑器 ical_editor.py + editor.html Vue3 组件
验证 recurring_ical_events 展开 网页内 occurrence 网格
导出 保存到磁盘 / 下载 下载 .ics
Description
No description provided
Readme 377 KiB
Languages
JavaScript 59.7%
Vue 38.7%
CSS 1.3%
HTML 0.3%