定时提醒服务的前端数据收集、处理
定时提醒服务前端时间数据结构设计
#tech / dev / frame
#type / howto
#status / seed
#tech / dev / frontend
定时提醒服务的前端数据收集、处理
目标
设计定时提醒服务的前端数据收集与处理方案,支持:
- 单次提醒和重复提醒(每天 / 每周 / 自定义 cron)
- 时间段提醒(如工作日 9:00-18:00 每小时提醒)
- 提醒数据的本地存储与服务端同步
- 跨时区兼容
前置条件
- 了解 JavaScript Date 对象和时区处理
- 了解前端状态管理(Pinia / Redux / Zustand)
- 了解 localStorage / IndexedDB 基本用法
- 相关:Electron 弹窗提醒实现
数据结构设计
基础提醒结构
interface Reminder {
id: string // UUID
title: string // 提醒标题
body: string // 提醒内容
type: 'once' | 'recurring' // 单次/重复
status: 'active' | 'paused' | 'completed'
// 时间相关
triggerAt: number // 单次提醒:触发时间戳 (ms)
cron?: string // 重复提醒:cron 表达式
timezone: string // 用户时区,如 'Asia/Shanghai'
// 元数据
createdAt: number
updatedAt: number
tags?: string[]
}
时间选择器数据格式
// 时间选择器输出格式
interface TimeSelection {
date: string // '2026-05-27' (ISO date)
time: string // '09:30' (HH:mm)
repeat: RepeatRule
}
interface RepeatRule {
type: 'none' | 'daily' | 'weekly' | 'monthly' | 'custom'
daysOfWeek?: number[] // [1,3,5] = 周一三五
daysOfMonth?: number[] // [1,15] = 每月1号和15号
interval?: number // 自定义间隔(天)
endDate?: string // 重复结束日期
}
步骤
1. 时间选择器组件
使用 Element Plus / Ant Design 的 DateTimePicker,封装为统一接口:
<script setup lang="ts">
import { ref, computed } from 'vue'
const emit = defineEmits<{
change: [selection: TimeSelection]
}>()
const selectedDate = ref('')
const selectedTime = ref('')
const repeatType = ref<RepeatRule['type']>('none')
const repeatDays = ref<number[]>([])
const triggerTimestamp = computed(() => {
if (!selectedDate.value || !selectedTime.value) return 0
const datetime = `${selectedDate.value}T${selectedTime.value}:00`
return new Date(datetime).getTime()
})
function handleChange() {
emit('change', {
date: selectedDate.value,
time: selectedTime.value,
repeat: {
type: repeatType.value,
daysOfWeek: repeatDays.value,
},
})
}
</script>
2. 本地存储方案
使用 IndexedDB(通过 Dexie.js)存储提醒数据:
import Dexie from 'dexie'
class ReminderDatabase extends Dexie {
reminders!: Table<Reminder>
constructor() {
super('ReminderDB')
this.version(1).stores({
reminders: 'id, status, triggerAt, type',
})
}
}
const db = new ReminderDatabase()
// 保存提醒
async function saveReminder(reminder: Reminder) {
await db.reminders.put(reminder)
}
// 获取所有活跃提醒
async function getActiveReminders() {
return db.reminders
.where('status')
.equals('active')
.sortBy('triggerAt')
}
3. 服务端同步策略
采用 本地优先 + 后台同步 的模式:
// 同步管理器
class ReminderSyncManager {
private syncQueue: Reminder[] = []
// 本地保存后加入同步队列
async save(reminder: Reminder) {
await saveToLocal(reminder)
this.syncQueue.push(reminder)
this.debouncedSync()
}
// 防抖同步,避免频繁请求
private debouncedSync = useDebounceFn(async () => {
if (this.syncQueue.length === 0) return
try {
await api.syncReminders(this.syncQueue)
this.syncQueue = []
} catch (error) {
console.warn('Sync failed, will retry:', error)
}
}, 3000)
}
4. 通知触发机制
在渲染进程中轮询检查即将触发的提醒:
// 提醒调度器
class ReminderScheduler {
private checkInterval = 30_000 // 每30秒检查一次
start() {
setInterval(() => this.checkDueReminders(), this.checkInterval)
}
private async checkDueReminders() {
const now = Date.now()
const reminders = await getActiveReminders()
for (const reminder of reminders) {
if (reminder.triggerAt <= now) {
this.triggerNotification(reminder)
if (reminder.type === 'once') {
await markCompleted(reminder.id)
} else {
await updateNextTrigger(reminder)
}
}
}
}
private triggerNotification(reminder: Reminder) {
// 调用系统通知或自定义弹窗
new Notification(reminder.title, { body: reminder.body })
}
}
验证方式
- 创建单次提醒,确认到期后通知触发
- 创建重复提醒(如每天 9:00),修改系统时间验证触发
- 刷新页面后确认提醒数据持久化
- 在不同设备登录确认数据同步
- 测试跨时区场景(修改系统时区后提醒是否按预期触发)
常见问题
Q: 提醒时间不准确?
JavaScript 的 setInterval 不保证精确执行。对于精确提醒,使用 Web Worker 中的 Atomics.wait 或服务端推送(WebSocket / SSE)。
Q: 浏览器关闭后提醒失效?
浏览器关闭后 JavaScript 停止执行。解决方案:
- 服务端定时推送(最佳)
- 使用 PWA + Service Worker 的 Background Sync API
- Electron 应用中主进程常驻(见 electron-vue3-popup-reminder-service)
Q: 大量提醒导致性能问题?
对提醒按触发时间排序,使用时间轮(Timing Wheel)数据结构管理,只检查最近需要触发的提醒。