import { reactive, computed } from 'vue' import { parseICS, serializeICS } from '../lib/ical-io.js' import { organize as organizeEvents } from '../lib/organize.js' import { createEvent, cloneEvent } from '../lib/event-factory.js' import { splitRecurringEvent } from '../lib/split.js' import { todayISO, addDays, startOfMonth, parseISODate, pad2 } from '../lib/date.js' const MAX_HISTORY = 50 // 单例响应式状态 const state = reactive({ meta: null, events: [], selectedUid: null, history: [], fileName: null, dirty: false, viewMode: 'edit', // 'edit' | 'month' | 'week' previewDate: todayISO(), // 预览锚定日期 (月预览取当月, 周预览取当周) }) // ------------------------------------------------------------------ // // 快照 / 撤销 // ------------------------------------------------------------------ // function snapshot() { return { meta: state.meta ? cloneEvent(state.meta) : null, events: state.events.map(cloneEvent), selectedUid: state.selectedUid, } } function pushHistory() { state.history.push(snapshot()) if (state.history.length > MAX_HISTORY) state.history.shift() } function restore(snap) { state.meta = snap.meta ? cloneEvent(snap.meta) : null state.events = snap.events.map(cloneEvent) state.selectedUid = snap.selectedUid } // ------------------------------------------------------------------ // // 文件加载 // ------------------------------------------------------------------ // function loadFile(file) { return file.text().then((text) => { const parsed = parseICS(text) state.meta = parsed.meta state.events = parsed.events state.selectedUid = null state.history = [] state.fileName = file.name state.dirty = false }) } // ------------------------------------------------------------------ // // 事件操作 // ------------------------------------------------------------------ // function selectEvent(uid) { state.selectedUid = uid } function organize() { if (state.events.length === 0) return pushHistory() const result = organizeEvents(state.events) state.events = result.events state.dirty = true return result.stats } /** * 更新指定事件的字段 (自动记录历史)。 * 供 EventDetail 的自动应用 (watch) 调用。 */ function updateEvent(uid, patch) { pushHistory() const ev = state.events.find((e) => e.uid === uid) if (ev) { Object.assign(ev, patch) state.dirty = true } } function deleteEvent(uid) { pushHistory() state.events = state.events.filter((e) => e.uid !== uid) if (state.selectedUid === uid) state.selectedUid = null state.dirty = true } function addEvent() { pushHistory() const ev = createEvent() state.events.push(ev) state.selectedUid = ev.uid state.dirty = true } function splitEvent(uid, splitDate) { const ev = state.events.find((e) => e.uid === uid) if (!ev || !ev.rrule) return const parts = splitRecurringEvent(ev, splitDate) if (!parts) return pushHistory() const [partA, partB] = parts const idx = state.events.findIndex((e) => e.uid === uid) state.events.splice(idx, 1, partA, partB) state.selectedUid = partB.uid state.dirty = true } function toggleOccurrence(uid, date) { pushHistory() const ev = state.events.find((e) => e.uid === uid) if (!ev) return const i = ev.exdates.indexOf(date) if (i >= 0) { ev.exdates.splice(i, 1) } else { ev.exdates.push(date) ev.exdates.sort() } state.dirty = true } function undo() { if (state.history.length === 0) return restore(state.history.pop()) } // ------------------------------------------------------------------ // // 序列化 // ------------------------------------------------------------------ // function serialize() { return serializeICS({ meta: state.meta, events: state.events }) } function markSaved() { state.dirty = false } // ------------------------------------------------------------------ // // 视图切换 / 预览导航 // ------------------------------------------------------------------ // function setViewMode(mode) { state.viewMode = mode } function shiftMonth(delta) { const { year, month } = parseISODate(startOfMonth(state.previewDate)) const d = new Date(year, month - 1 + delta, 1) state.previewDate = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-01` } function prevMonth() { shiftMonth(-1) } function nextMonth() { shiftMonth(1) } function prevWeek() { state.previewDate = addDays(state.previewDate, -7) } function nextWeek() { state.previewDate = addDays(state.previewDate, 7) } // ------------------------------------------------------------------ // // 计算属性 // ------------------------------------------------------------------ // const selectedEvent = computed(() => state.events.find((e) => e.uid === state.selectedUid) || null, ) const canUndo = computed(() => state.history.length > 0) const isLoaded = computed(() => state.events.length > 0) const stats = computed(() => { const nRecur = state.events.filter((e) => e.rrule !== null).length return { total: state.events.length, recurring: nRecur, single: state.events.length - nRecur } }) export function useCalendar() { return { state, selectedEvent, canUndo, isLoaded, stats, loadFile, selectEvent, organize, updateEvent, deleteEvent, addEvent, splitEvent, toggleOccurrence, undo, serialize, markSaved, setViewMode, prevMonth, nextMonth, prevWeek, nextWeek, } }