From 1c6ecf9d8661c4d68c466dbb69fc8f02b1eb36a6 Mon Sep 17 00:00:00 2001 From: zikai Date: Wed, 22 Jul 2026 04:18:05 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E9=87=8D?= =?UTF-8?q?=E5=81=9A/=E5=BA=94=E7=94=A8=E6=8C=89=E9=92=AE,=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=BA=94=E7=94=A8=E6=9B=B4=E6=94=B9,=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=AD=BB=E4=BB=A3=E7=A0=81,=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI 变更: - 移除重做按钮及 redo/canRedo/redoStack 相关逻辑 (useCalendar, HeaderBar) - 移除应用更改按钮, EventDetail 改为 watch form 自动应用 (deep watch) - 用 syncing 标志防止 syncForm 回写时触发自动应用的循环 死代码清理: - weekday.js: 移除未使用的 BYDAY_TO_INDEX 和 dowLabel - date.js: 移除未使用的 toHHMM - App.vue: 移除未使用的 .empty CSS - DropZone.vue: 移除空的 .icon 容器及 CSS - test/weekday.test.js: 移除对应死代码的 2 个测试用例 性能修复: - WeekPreview: layoutEvents 从模板内联调用改为 computed (laidOutByDate), 避免每次渲染重新计算布局 - MonthPreview: occByDate 过滤为仅网格内 42 天, 避免展开全部 occurrence 代码质量: - useCalendar: prevMonth/nextMonth 合并为 shiftMonth(delta), 消除重复 - WeekPreview/MonthPreview: 移除未使用导入 (toISODate, toDate) --- src/App.vue | 5 -- src/components/DropZone.vue | 5 -- src/components/EventDetail.vue | 16 +++-- src/components/HeaderBar.vue | 3 +- src/components/MonthPreview.vue | 34 +++++----- src/components/WeekPreview.vue | 111 +++++++++++++++++--------------- src/composables/useCalendar.js | 39 ++++++----- src/lib/date.js | 5 -- src/lib/weekday.js | 7 -- test/weekday.test.js | 22 ++----- 10 files changed, 111 insertions(+), 136 deletions(-) diff --git a/src/App.vue b/src/App.vue index f9a9af6..5ba686a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -91,9 +91,4 @@ body { } .app-main { flex: 1; display: flex; overflow: hidden; } .detail-area { flex: 1; overflow-y: auto; } -.empty { - text-align: center; - padding: 60px 20px; - color: #95a5a6; -} diff --git a/src/components/DropZone.vue b/src/components/DropZone.vue index a124a1d..b3031d8 100644 --- a/src/components/DropZone.vue +++ b/src/components/DropZone.vue @@ -39,7 +39,6 @@ function onPick(e) { @drop.prevent="onDrop" >
-

拖入 .ics 文件,或

-
@@ -118,21 +125,19 @@ function tooltip(ev) {
-
+
{{ headers[idx] }} {{ parseISODate(dateStr).day }}
-
-
MAX_HISTORY) state.history.shift() - state.redoStack = [] } function restore(snap) { @@ -44,7 +42,7 @@ function restore(snap) { } // ------------------------------------------------------------------ // -// 操作 +// 文件加载 // ------------------------------------------------------------------ // function loadFile(file) { return file.text().then((text) => { @@ -53,12 +51,14 @@ function loadFile(file) { state.events = parsed.events state.selectedUid = null state.history = [] - state.redoStack = [] state.fileName = file.name state.dirty = false }) } +// ------------------------------------------------------------------ // +// 事件操作 +// ------------------------------------------------------------------ // function selectEvent(uid) { state.selectedUid = uid } @@ -72,6 +72,10 @@ function organize() { return result.stats } +/** + * 更新指定事件的字段 (自动记录历史)。 + * 供 EventDetail 的自动应用 (watch) 调用。 + */ function updateEvent(uid, patch) { pushHistory() const ev = state.events.find((e) => e.uid === uid) @@ -127,16 +131,12 @@ function toggleOccurrence(uid, date) { function undo() { if (state.history.length === 0) return - state.redoStack.push(snapshot()) restore(state.history.pop()) } -function redo() { - if (state.redoStack.length === 0) return - state.history.push(snapshot()) - restore(state.redoStack.pop()) -} - +// ------------------------------------------------------------------ // +// 序列化 +// ------------------------------------------------------------------ // function serialize() { return serializeICS({ meta: state.meta, events: state.events }) } @@ -152,16 +152,18 @@ function setViewMode(mode) { state.viewMode = mode } -function prevMonth() { +function shiftMonth(delta) { const { year, month } = parseISODate(startOfMonth(state.previewDate)) - const d = new Date(year, month - 2, 1) // month-2 = 上个月 (0 基) + const d = new Date(year, month - 1 + delta, 1) state.previewDate = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-01` } +function prevMonth() { + shiftMonth(-1) +} + function nextMonth() { - const { year, month } = parseISODate(startOfMonth(state.previewDate)) - const d = new Date(year, month, 1) // month = 下个月 (0 基) - state.previewDate = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-01` + shiftMonth(1) } function prevWeek() { @@ -180,7 +182,6 @@ const selectedEvent = computed(() => ) const canUndo = computed(() => state.history.length > 0) -const canRedo = computed(() => state.redoStack.length > 0) const isLoaded = computed(() => state.events.length > 0) const stats = computed(() => { const nRecur = state.events.filter((e) => e.rrule !== null).length @@ -192,7 +193,6 @@ export function useCalendar() { state, selectedEvent, canUndo, - canRedo, isLoaded, stats, loadFile, @@ -204,7 +204,6 @@ export function useCalendar() { splitEvent, toggleOccurrence, undo, - redo, serialize, markSaved, setViewMode, diff --git a/src/lib/date.js b/src/lib/date.js index 4f55601..830def4 100644 --- a/src/lib/date.js +++ b/src/lib/date.js @@ -52,11 +52,6 @@ export function nowCompactTimestamp() { return new Date().toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z' } -/** 将 JS Date 的时间格式化为 'HH:MM'。 */ -export function toHHMM(date) { - return `${pad2(date.getHours())}:${pad2(date.getMinutes())}` -} - /** * 返回 isoDate 所在周的周一 (ISO 周, 周一为首日)。 * @param {string} isoDate - 'YYYY-MM-DD' diff --git a/src/lib/weekday.js b/src/lib/weekday.js index 2a33d68..ed89986 100644 --- a/src/lib/weekday.js +++ b/src/lib/weekday.js @@ -4,14 +4,7 @@ export const DOW = ['周日', '周一', '周二', '周三', '周四', '周五', // BYDAY 编码, 按 JS getDay() 索引: DOW_EN[getDay()] => 'SU'..'SA' export const DOW_EN = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'] -export const BYDAY_TO_INDEX = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 } - /** 返回 JS Date 对应的 BYDAY 编码 ('SU'..'SA')。 */ export function bydayFromDate(date) { return DOW_EN[date.getDay()] } - -/** 返回 JS Date 对应的中文星期标签。 */ -export function dowLabel(date) { - return DOW[date.getDay()] -} diff --git a/test/weekday.test.js b/test/weekday.test.js index 62b3bca..02124fb 100644 --- a/test/weekday.test.js +++ b/test/weekday.test.js @@ -1,31 +1,21 @@ import { describe, it, expect } from 'vitest' -import { DOW, DOW_EN, BYDAY_TO_INDEX, bydayFromDate, dowLabel } from '../src/lib/weekday.js' +import { DOW, DOW_EN, bydayFromDate } from '../src/lib/weekday.js' describe('weekday', () => { - it('DOW has 7 entries starting Sunday', () => { + it('DOW 有 7 项, 首项为周日', () => { expect(DOW).toHaveLength(7) expect(DOW[0]).toBe('周日') expect(DOW[6]).toBe('周六') }) - it('DOW_EN indexed by JS getDay (0=Sunday)', () => { + it('DOW_EN 按 JS getDay 索引 (0=周日)', () => { expect(DOW_EN).toEqual(['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']) }) - it('BYDAY_TO_INDEX maps SU..SA to 0..6', () => { - expect(BYDAY_TO_INDEX.SU).toBe(0) - expect(BYDAY_TO_INDEX.SA).toBe(6) - }) - - it('bydayFromDate returns BYDAY code for a Date', () => { - // 2026-07-27 is a Monday -> getDay()=1 -> 'MO' + it('bydayFromDate 返回 Date 对应的 BYDAY 编码', () => { + // 2026-07-27 是周一 -> getDay()=1 -> 'MO' expect(bydayFromDate(new Date(2026, 6, 27))).toBe('MO') - // 2026-08-01 is a Saturday -> getDay()=6 -> 'SA' + // 2026-08-01 是周六 -> getDay()=6 -> 'SA' expect(bydayFromDate(new Date(2026, 7, 1))).toBe('SA') }) - - it('dowLabel returns Chinese label for a Date', () => { - expect(dowLabel(new Date(2026, 6, 27))).toBe('周一') - expect(dowLabel(new Date(2026, 6, 26))).toBe('周日') - }) })