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 +}