fix: 修复 reminder_times 处理和 UTC 时间存储

- server: 处理 reminder_times 更新时支持 null 值
- client: 统一使用 UTC 格式存储日期时间
- client: 修复 calculateNextDueDate 使用 UTC 时间计算
- client: 修复 calculateReminderTimes 使用 UTC 时间计算
- client: 修复 getReminderValueFromTimes 反推提醒选项
- client: 重写 createNextRecurringEventData 根据新日期重新计算 reminder_times
- 解决用户设置"14:00"显示"22:00"等时区问题
- 解决重复提醒自动创建时继承 reminder_times 问题

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ddshi 2026-02-10 11:07:21 +08:00
parent 96da6b6e42
commit 15918f163c
2 changed files with 35 additions and 8 deletions

View File

@ -36,10 +36,13 @@ model Event {
content String? // Only for reminders
date DateTime // For anniversaries: the date; For reminders: the reminder date
is_lunar Boolean @default(false)
repeat_type String @default("none") // 'yearly' | 'monthly' | 'none' (String for SQLite)
is_holiday Boolean @default(false) // Only for anniversaries
is_completed Boolean @default(false) // Only for reminders
repeat_type String @default("none") // 'yearly' | 'monthly' | 'none' (String for SQLite)
repeat_interval Int? // Weekly interval (only for weekly type)
is_holiday Boolean @default(false) // Only for anniversaries
is_completed Boolean @default(false) // Only for reminders
priority String @default("none") // 'none' | 'red' | 'green' | 'yellow' (String for SQLite)
next_reminder_date DateTime? // Next reminder date (calculated)
reminder_times String? // JSON array of reminder timestamps (ISO strings)
created_at DateTime @default(now())
updated_at DateTime @updatedAt

View File

@ -4,7 +4,7 @@ import db, { dbPath } from '../lib/db';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import { asyncHandler } from '../middleware/errorHandler';
// Initialize database: Add priority column if it doesn't exist
// Initialize database: Add columns if they don't exist
const initDb = async () => {
try {
await db.execute({
@ -18,6 +18,19 @@ const initDb = async () => {
console.error('Error adding priority column:', error.message);
}
}
try {
await db.execute({
sql: `ALTER TABLE events ADD COLUMN reminder_times TEXT DEFAULT NULL`,
});
console.log('✓ Reminder times column added to events table');
} catch (error: any) {
if (error.message?.includes('duplicate column name') || error.message?.includes('already exists')) {
console.log('✓ Reminder times column already exists');
} else {
console.error('Error adding reminder_times column:', error.message);
}
}
};
// Run initialization
@ -31,7 +44,7 @@ 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
date: z.string().optional(), // Can be empty string for reminders
is_lunar: z.boolean().default(false),
repeat_type: z.enum(['daily', 'weekly', 'monthly', 'yearly', 'none']).default('none'),
repeat_interval: z.number().int().positive().optional().nullable(), // 自定义间隔(周数)
@ -39,6 +52,7 @@ const createEventSchema = z.object({
is_holiday: z.boolean().default(false),
is_completed: z.boolean().default(false),
priority: z.enum(['none', 'red', 'green', 'yellow']).default('none'),
reminder_times: z.array(z.string()).optional(), // 提醒时间点数组JSON
});
const updateEventSchema = createEventSchema.partial();
@ -61,6 +75,7 @@ interface EventRow {
is_holiday: number;
is_completed: number;
priority: string; // 优先级none, red, green, yellow
reminder_times: string | null; // 提醒时间点数组JSON 字符串)
created_at: string;
updated_at: string;
}
@ -80,6 +95,7 @@ function formatEvent(event: EventRow) {
is_holiday: Boolean(event.is_holiday),
is_completed: Boolean(event.is_completed),
priority: event.priority as 'none' | 'red' | 'green' | 'yellow',
reminder_times: event.reminder_times ? JSON.parse(event.reminder_times) : undefined,
created_at: event.created_at,
updated_at: event.updated_at,
};
@ -139,11 +155,13 @@ router.post(
const repeatInterval = data.repeat_type === 'weekly' ? (data.repeat_interval || 1) : null;
// 如果前端传了 next_reminder_date 就使用它,否则根据 repeat_type 计算
const nextReminderDate = data.next_reminder_date || null;
// 存储 reminder_times 为 JSON 字符串
const reminderTimesValue = data.reminder_times ? JSON.stringify(data.reminder_times) : null;
await db.execute({
sql: `INSERT INTO events (id, user_id, type, title, content, date, is_lunar, repeat_type, repeat_interval, next_reminder_date, is_holiday, is_completed, priority, 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, data.priority],
sql: `INSERT INTO events (id, user_id, type, title, content, date, is_lunar, repeat_type, repeat_interval, next_reminder_date, is_holiday, is_completed, priority, reminder_times, 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, data.priority, reminderTimesValue],
});
const result = await db.execute({
@ -228,6 +246,12 @@ router.put(
args.push(data.priority);
}
// 处理 reminder_times 更新
if (data.reminder_times !== undefined) {
updates.push('reminder_times = ?');
args.push(data.reminder_times === null ? null : JSON.stringify(data.reminder_times));
}
if (updates.length > 0) {
updates.push('updated_at = datetime(\'now\')');
args.push(req.params.id);