Electron-Vue3下的弹窗提醒服务实现

Electron-Vue3 下的弹窗提醒服务实现

#tech / dev / desktop #resource / electron #type / howto #status / growing

[!info] related notes

Electron-Vue3下的弹窗提醒服务实现


目标

在 Electron + Vue3 桌面应用中实现定时弹窗提醒功能,支持:

  • 指定时间触发系统级通知
  • 自定义弹窗样式和交互
  • 多条提醒的并发管理
  • 跨平台兼容(Windows / macOS / Linux)

前置条件

  • 已搭建 Electron + Vue3 项目(推荐 electron-vite 或 Vite + electron-builder)
  • 了解 Electron 主进程与渲染进程的区别
  • 了解 IPC(Inter-Process Communication)通信机制
  • Node.js >= 18

技术方案

采用 主进程 Notification API + IPC 通信 + 渲染进程 Vue 组件 的三层架构:

层级职责技术
主进程定时器管理、系统通知触发、权限检查setTimeout / setInterval + Electron Notification
IPC 通道双向消息传递ipcMain.handle / ipcRenderer.invoke
渲染进程用户界面、提醒配置、自定义弹窗Vue3 组件 + Pinia 状态管理

步骤

1. 注册 IPC 通道(主进程)

在主进程入口文件(如 main/index.ts)中注册 IPC 处理器:

// main/index.ts
import { ipcMain, Notification } from 'electron'

// 创建定时提醒
ipcMain.handle('reminder:create', async (_event, payload: {
  title: string
  body: string
  triggerAt: number  // 时间戳 ms
}) => {
  const delay = payload.triggerAt - Date.now()
  if (delay <= 0) {
    showNotification(payload.title, payload.body)
    return { status: 'immediate' }
  }

  const timerId = setTimeout(() => {
    showNotification(payload.title, payload.body)
  }, delay)

  // 存储 timerId 以便取消
  return { status: 'scheduled', timerId }
})

// 取消提醒
ipcMain.handle('reminder:cancel', async (_event, timerId: number) => {
  clearTimeout(timerId)
  return { status: 'cancelled' }
})

function showNotification(title: string, body: string) {
  if (!Notification.isSupported()) return

  const notification = new Notification({ title, body })
  notification.on('click', () => {
    // 聚焦到主窗口
    BrowserWindow.getAllWindows()[0]?.focus()
  })
  notification.show()
}

2. 渲染进程预加载脚本(preload)

通过 contextBridge 安全暴露 API:

// preload/index.ts
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('reminderAPI', {
  create: (payload: { title: string; body: string; triggerAt: number }) =>
    ipcRenderer.invoke('reminder:create', payload),
  cancel: (timerId: number) =>
    ipcRenderer.invoke('reminder:cancel', timerId),
  list: () =>
    ipcRenderer.invoke('reminder:list'),
})

3. Vue3 组件层

<!-- ReminderPanel.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const title = ref('')
const body = ref('')
const triggerTime = ref('')

async function createReminder() {
  const triggerAt = new Date(triggerTime.value).getTime()
  const result = await window.reminderAPI.create({
    title: title.value,
    body: body.value,
    triggerAt,
  })
  console.log('Reminder scheduled:', result)
}
</script>

<template>
  <div class="reminder-panel">
    <input v-model="title" placeholder="提醒标题" />
    <textarea v-model="body" placeholder="提醒内容" />
    <input v-model="triggerTime" type="datetime-local" />
    <button @click="createReminder">创建提醒</button>
  </div>
</template>

4. 通知权限处理

macOS 需要在 Info.plist 中配置通知权限,Windows/Linux 通常无需额外配置:

// main/index.ts - 应用启动时检查
if (process.platform === 'darwin') {
  // macOS 通知权限由系统自动管理
  // 确保 Info.plist 中包含 NSUserNotificationsUsageDescription
}

5. 持久化提醒数据(可选)

将提醒存储到本地 JSON 或 SQLite,应用重启后恢复:

import Store from 'electron-store'

const store = new Store({ name: 'reminders' })

// 保存提醒
ipcMain.handle('reminder:save', async (_event, reminder) => {
  const reminders = store.get('reminders', []) as any[]
  reminders.push(reminder)
  store.set('reminders', reminders)
})

// 应用启动时恢复提醒
function restoreReminders() {
  const reminders = store.get('reminders', []) as any[]
  reminders.forEach(r => {
    const delay = r.triggerAt - Date.now()
    if (delay > 0) setTimeout(() => showNotification(r.title, r.body), delay)
  })
}

验证

  1. 基本通知 - 创建一个 1 分钟后触发的提醒,确认系统通知弹出
  2. 点击行为 - 点击通知后应用窗口获得焦点
  3. 取消功能 - 创建提醒后取消,确认不再弹出
  4. 持久化 - 创建提醒后重启应用,确认提醒仍生效
  5. 跨平台 - 在 Windows 和 macOS 上分别测试通知样式

常见问题

Q: macOS 上通知不显示?

macOS 的通知中心可能已将应用通知设为静默。检查 系统设置 → 通知 中应用的通知权限。

Q: Windows 上通知被系统勿扰模式拦截?

Windows 11 的专注助手(Focus Assist)会拦截通知。检查 系统 → 专注助手 设置。

Q: 应用最小化后通知不触发?

Electron 的 Notification API 不依赖窗口是否可见,只要主进程在运行即可。如果不触发,检查 setTimeout 是否被正确设置。

Q: 长时间定时器(超过 24.8 天)失效?

JavaScript 的 setTimeout 最大延迟约为 24.8 天(2^31 - 1 毫秒)。对于超长定时,使用轮询方式:

function longTimeout(callback: () => void, delay: number) {
  const maxInt = 2147483647
  if (delay > maxInt) {
    return setTimeout(() => longTimeout(callback, delay - maxInt), maxInt)
  }
  return setTimeout(callback, delay)
}

Q: 如何实现重复提醒(如每天 9:00)?

使用 node-cron 或手动计算下次触发时间:

function scheduleDaily(hour: number, minute: number, callback: () => void) {
  const now = new Date()
  const target = new Date()
  target.setHours(hour, minute, 0, 0)
  if (target <= now) target.setDate(target.getDate() + 1)

  const delay = target.getTime() - now.getTime()
  setTimeout(() => {
    callback()
    scheduleDaily(hour, minute, callback) // 递归调度下一次
  }, delay)
}
创建于 2025/1/1 更新于 2026/5/27