From f4c11bdc3eecdc351c27e65ec89017ac021d0772 Mon Sep 17 00:00:00 2001 From: zikai Date: Wed, 22 Jul 2026 04:04:29 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E5=8F=8A?= =?UTF-8?q?=E9=A2=9C=E8=89=B2/=E6=97=A5=E6=9C=9F=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - date.js: 新增 startOfWeek, startOfMonth, hhmmToMinutes 辅助函数 - colors.js (新增): 基于 uid 哈希的事件颜色分配, 12 色调色板 - useCalendar.js: 新增 viewMode (edit/month/week) 和 previewDate 状态, 以及 setViewMode/prevMonth/nextMonth/prevWeek/nextWeek 操作 --- src/composables/useCalendar.js | 35 +++++++++++++++++++++++++++ src/lib/colors.js | 44 ++++++++++++++++++++++++++++++++++ src/lib/date.js | 33 +++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/lib/colors.js diff --git a/src/composables/useCalendar.js b/src/composables/useCalendar.js index ee29575..c7acc9e 100644 --- a/src/composables/useCalendar.js +++ b/src/composables/useCalendar.js @@ -3,6 +3,7 @@ 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, startOfWeek, parseISODate, pad2 } from '../lib/date.js' const MAX_HISTORY = 50 @@ -15,6 +16,8 @@ const state = reactive({ redoStack: [], fileName: null, dirty: false, + viewMode: 'edit', // 'edit' | 'month' | 'week' + previewDate: todayISO(), // 预览锚定日期 (月预览取当月, 周预览取当周) }) // ------------------------------------------------------------------ // @@ -142,6 +145,33 @@ function markSaved() { state.dirty = false } +// ------------------------------------------------------------------ // +// 视图切换 / 预览导航 +// ------------------------------------------------------------------ // +function setViewMode(mode) { + state.viewMode = mode +} + +function prevMonth() { + const { year, month } = parseISODate(startOfMonth(state.previewDate)) + const d = new Date(year, month - 2, 1) // month-2 = 上个月 (0 基) + state.previewDate = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-01` +} + +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` +} + +function prevWeek() { + state.previewDate = addDays(state.previewDate, -7) +} + +function nextWeek() { + state.previewDate = addDays(state.previewDate, 7) +} + // ------------------------------------------------------------------ // // 计算属性 // ------------------------------------------------------------------ // @@ -177,5 +207,10 @@ export function useCalendar() { redo, serialize, markSaved, + setViewMode, + prevMonth, + nextMonth, + prevWeek, + nextWeek, } } diff --git a/src/lib/colors.js b/src/lib/colors.js new file mode 100644 index 0000000..3e25624 --- /dev/null +++ b/src/lib/colors.js @@ -0,0 +1,44 @@ +/** + * 事件颜色分配工具。 + * + * 基于事件 uid 的哈希值从预定义调色板中选取颜色, 确保相同事件始终 + * 同色, 不同事件大概率不同色。预定义调色板保证颜色区分度高且可读。 + */ + +// 12 种区分度高的颜色, 每种包含背景色、边框色、文字色 +const PALETTE = [ + { bg: '#e3f2fd', border: '#1976d2', text: '#0d47a1' }, // 蓝 + { bg: '#f3e5f5', border: '#7b1fa2', text: '#4a148c' }, // 紫 + { bg: '#e8f5e9', border: '#388e3c', text: '#1b5e20' }, // 绿 + { bg: '#fff3e0', border: '#e65100', text: '#bf360c' }, // 橙 + { bg: '#fce4ec', border: '#c2185b', text: '#880e4f' }, // 粉 + { bg: '#e0f7fa', border: '#00838f', text: '#006064' }, // 青 + { bg: '#f1f8e9', border: '#827717', text: '#33691e' }, // 黄绿 + { bg: '#ede7f6', border: '#512da8', text: '#311b92' }, // 靛 + { bg: '#fbe9e7', border: '#d84315', text: '#bf360c' }, // 红橙 + { bg: '#e0f2f1', border: '#00695c', text: '#004d40' }, // 蓝绿 + { bg: '#fff8e1', border: '#f9a825', text: '#f57f17' }, // 金 + { bg: '#f9fbe7', border: '#afb42b', text: '#827717' }, // 橄榄 +] + +/** + * 对字符串进行简单哈希, 返回非负整数。 + * @param {string} str + * @returns {number} + */ +function hashStr(str) { + let h = 0 + for (let i = 0; i < str.length; i++) { + h = (h * 31 + str.charCodeAt(i)) | 0 + } + return Math.abs(h) +} + +/** + * 根据 uid 返回调色板中的颜色对象。 + * @param {string} uid + * @returns {{ bg: string, border: string, text: string }} + */ +export function eventColor(uid) { + return PALETTE[hashStr(uid) % PALETTE.length] +} diff --git a/src/lib/date.js b/src/lib/date.js index 47c4057..4f55601 100644 --- a/src/lib/date.js +++ b/src/lib/date.js @@ -56,3 +56,36 @@ export function nowCompactTimestamp() { export function toHHMM(date) { return `${pad2(date.getHours())}:${pad2(date.getMinutes())}` } + +/** + * 返回 isoDate 所在周的周一 (ISO 周, 周一为首日)。 + * @param {string} isoDate - 'YYYY-MM-DD' + * @returns {string} 周一的 'YYYY-MM-DD' + */ +export function startOfWeek(isoDate) { + const d = toDate(isoDate) + const dow = d.getDay() // 0=周日 .. 6=周六 + const diff = dow === 0 ? -6 : 1 - dow // 回退到周一 + d.setDate(d.getDate() + diff) + return toISODate(d) +} + +/** + * 返回 isoDate 所在月的第一天。 + * @param {string} isoDate - 'YYYY-MM-DD' + * @returns {string} 当月 1 号的 'YYYY-MM-DD' + */ +export function startOfMonth(isoDate) { + const { year, month } = parseISODate(isoDate) + return `${year}-${pad2(month)}-01` +} + +/** + * 将 'HH:MM' 转换为当天分钟数 (0~1439)。 + * @param {string} hhmm - 'HH:MM' + * @returns {number} + */ +export function hhmmToMinutes(hhmm) { + const [h, m] = hhmm.split(':').map(Number) + return h * 60 + m +}