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 分钟后触发的提醒,确认系统通知弹出
- 点击行为 - 点击通知后应用窗口获得焦点
- 取消功能 - 创建提醒后取消,确认不再弹出
- 持久化 - 创建提醒后重启应用,确认提醒仍生效
- 跨平台 - 在 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)
}