diff --git a/src/migrations/001_add_repeat_interval.sql b/src/migrations/001_add_repeat_interval.sql new file mode 100644 index 0000000..aecc7ad --- /dev/null +++ b/src/migrations/001_add_repeat_interval.sql @@ -0,0 +1,15 @@ +-- 迁移脚本:添加重复提醒相关字段 +-- 执行时间:2026-02-03 +-- 说明:为支持每天/每周重复提醒,添加 repeat_interval 字段 + +-- 添加 repeat_interval 字段(如果不存在) +-- SQLite 不支持 IF NOT EXISTS 语法,需要检查列是否存在 +-- 这里使用 ALTER TABLE 添加列 + +ALTER TABLE events ADD COLUMN repeat_interval INTEGER DEFAULT NULL; + +-- 更新现有数据:将 'none' 以外的旧 repeat_type 保持不变 +-- 注意:此迁移假设现有数据只有 'yearly', 'monthly', 'none' 三种值 + +-- 验证迁移 +-- SELECT id, repeat_type, repeat_interval FROM events WHERE type = 'reminder'; diff --git a/src/migrations/002_add_next_reminder_date.sql b/src/migrations/002_add_next_reminder_date.sql new file mode 100644 index 0000000..f9a6ab7 --- /dev/null +++ b/src/migrations/002_add_next_reminder_date.sql @@ -0,0 +1,9 @@ +-- 迁移脚本:添加下一次提醒日期字段 +-- 执行时间:2026-02-03 +-- 说明:支持重复提醒的双时间机制(当前提醒时间 + 下一次提醒时间) + +-- 添加 next_reminder_date 字段 +ALTER TABLE events ADD COLUMN next_reminder_date TEXT DEFAULT NULL; + +-- 验证迁移 +-- SELECT id, title, date, repeat_type, next_reminder_date FROM events WHERE type = 'reminder'; diff --git a/src/routes/events.ts b/src/routes/events.ts index f72c05b..f9119c4 100644 --- a/src/routes/events.ts +++ b/src/routes/events.ts @@ -7,13 +7,16 @@ import { asyncHandler } from '../middleware/errorHandler'; const router = Router(); // Validation schemas - date can be empty string for reminders +// repeat_type: 'daily'每天, 'weekly'每周, 'monthly'每月, 'yearly'每年, 'none'不重复 const createEventSchema = z.object({ type: z.enum(['anniversary', 'reminder']), title: z.string().min(1, 'Title is required').max(200), content: z.string().optional(), date: z.string().optional(), // Can be empty for reminders is_lunar: z.boolean().default(false), - repeat_type: z.enum(['yearly', 'monthly', 'none']).default('none'), + repeat_type: z.enum(['daily', 'weekly', 'monthly', 'yearly', 'none']).default('none'), + repeat_interval: z.number().int().positive().optional().nullable(), // 自定义间隔(周数) + next_reminder_date: z.string().optional().nullable(), // 下一次提醒日期 is_holiday: z.boolean().default(false), is_completed: z.boolean().default(false), }); @@ -33,6 +36,8 @@ interface EventRow { date: string; is_lunar: number; repeat_type: string; + repeat_interval: number | null; // 周数间隔(仅 weekly 类型使用) + next_reminder_date: string | null; // 下一次提醒日期 is_holiday: number; is_completed: number; created_at: string; @@ -49,6 +54,8 @@ function formatEvent(event: EventRow) { date: event.date, is_lunar: Boolean(event.is_lunar), repeat_type: event.repeat_type, + repeat_interval: event.repeat_interval, + next_reminder_date: event.next_reminder_date, is_holiday: Boolean(event.is_holiday), is_completed: Boolean(event.is_completed), created_at: event.created_at, @@ -107,11 +114,14 @@ router.post( // Handle empty date - store as null for reminders without specific time // 直接存储前端发送的原始日期字符串,不做 toISOString 转换(避免时区问题) const dateValue = data.date || null; + const repeatInterval = data.repeat_type === 'weekly' ? (data.repeat_interval || 1) : null; + // 如果前端传了 next_reminder_date 就使用它,否则根据 repeat_type 计算 + const nextReminderDate = data.next_reminder_date || null; await db.execute({ - sql: `INSERT INTO events (id, user_id, type, title, content, date, is_lunar, repeat_type, is_holiday, is_completed, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, datetime('now'), datetime('now'))`, - args: [eventId, req.user!.userId, data.type, data.title, data.content || null, dateValue, data.is_lunar ? 1 : 0, data.repeat_type, data.is_holiday ? 1 : 0], + sql: `INSERT INTO events (id, user_id, type, title, content, date, is_lunar, repeat_type, repeat_interval, next_reminder_date, is_holiday, is_completed, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, datetime('now'), datetime('now'))`, + args: [eventId, req.user!.userId, data.type, data.title, data.content || null, dateValue, data.is_lunar ? 1 : 0, data.repeat_type, repeatInterval, nextReminderDate, data.is_holiday ? 1 : 0], }); const result = await db.execute({ @@ -168,6 +178,18 @@ router.put( if (data.repeat_type) { updates.push('repeat_type = ?'); args.push(data.repeat_type); + // 如果设置为 weekly 重复,设置默认间隔 + if (data.repeat_type === 'weekly') { + updates.push('repeat_interval = ?'); + args.push(data.repeat_interval || 1); + } else { + updates.push('repeat_interval = NULL'); + } + } + // 处理 next_reminder_date 更新 + if (data.next_reminder_date !== undefined) { + updates.push('next_reminder_date = ?'); + args.push(data.next_reminder_date); } if (data.is_holiday !== undefined) { updates.push('is_holiday = ?');