5.5 KiB
5.5 KiB
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 文件即可。
使用流程
- 导入:拖入
.ics文件(或点击选择),左侧显示所有原始事件 - 去重:点击顶栏「🔁 整理去重」,自动合并重复事件为 RRULE + EXDATE
- 编辑:点击左侧事件,右侧展开编辑表单
- 修改标题/地点/描述/时间
- 调整重复规则(频率/间隔/星期/重复到)
- 点击 occurrence 网格切换排除日期(绿色=包含,红色删除线=排除)
- 拆分(可选):点击「✂ 拆分此日程」,选日期,把重复事件拆成前后两段
- 新增/删除:点「➕ 新增日程」或「删除此日程」
- 撤销:「↶ 撤销」/「↷ 重做」最多 50 步
- 下载:点「⬇ 下载 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 model(ical.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 移植:
- 按 (标题 + 地点 + 开始时间 + 时长 + 星期) 将事件分组
- 计算组内相邻日期间隔的 GCD,识别:
- GCD = 7 →
FREQ=WEEKLY(每周,含BYDAY) - GCD = 1 →
FREQ=DAILY(每天) - GCD = 14 →
FREQ=WEEKLY;INTERVAL=2(每两周)
- GCD = 7 →
- 构建期望网格,网格上缺失的日期写入
EXDATE(如假期) - 不符合规律的组(跳过次数 ≥ 事件数)保留为独立事件
- 合并后保留首个事件的原始 UID(避免重新导入冲突)
验证:对 org.ics(61 事件)整理后输出 8 个事件(5 重复 + 3 独立),与 Python 版 ical_organizer.py --verify 结果一致。下载的 ICS 用 Python recurring-ical-events 展开,61 个 occurrence 与原始文件完全匹配。
技术栈
| 项 | 选择 |
|---|---|
| 框架 | Vue 3.5(SFC,<script setup>) |
| 构建 | Vite 6 |
| ICS 库 | ical.js 2.x(仅用于文本↔对象转换) |
| 状态管理 | composable 单例 store(无 Pinia) |
| 测试 | Vitest |
| 包源 | npmmirror.com(中国镜像加速) |
与 timeTable 的关系
timeTable(Python 版)使用 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 |