定时提醒服务的前端数据收集、处理

定时提醒服务前端时间数据结构设计

#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 })
  }
}

验证方式

  1. 创建单次提醒,确认到期后通知触发
  2. 创建重复提醒(如每天 9:00),修改系统时间验证触发
  3. 刷新页面后确认提醒数据持久化
  4. 在不同设备登录确认数据同步
  5. 测试跨时区场景(修改系统时区后提醒是否按预期触发)

常见问题

Q: 提醒时间不准确?

JavaScript 的 setInterval 不保证精确执行。对于精确提醒,使用 Web Worker 中的 Atomics.wait 或服务端推送(WebSocket / SSE)。

Q: 浏览器关闭后提醒失效?

浏览器关闭后 JavaScript 停止执行。解决方案:

Q: 大量提醒导致性能问题?

对提醒按触发时间排序,使用时间轮(Timing Wheel)数据结构管理,只检查最近需要触发的提醒。

创建于 2025/1/1 更新于 2026/5/27