Notification API 介绍。只用 new Notification() 不够。要覆盖“旧通知点击跳转”,必须:

ServiceWorkerRegistration.showNotification()public/notification-sw.js 监听 notificationclickfocus(),没有再 openWindow()一句话:把点击处理从页面 JS 移到 Service Worker。
title:通知标题(必填)body:正文内容icon:大图标(建议 192x192 或 256x256)badge:小徽标(Android 常见,建议单色清晰图)tag:通知分组标识;相同 tag 会覆盖旧通知data:自定义数据载荷(本方案用来传 url)requireInteraction:true 表示通知不自动关闭(浏览器行为可能有差异)silent:是否静音(不同浏览器支持度不同)示例(占位链接):
new Notification('系统提醒', { body: '您有一条待处理消息', icon: 'https://example.com/assets/notify-icon.png', badge: 'https://example.com/assets/notify-badge.png', tag: 'todo-1001', requireInteraction: true, data: { url: 'https://example.com/app/todo?id=1001' }})notification.onclick:页面存活时可用notification.onclose:通知关闭回调notification.onerror:创建或展示失败回调页面被关闭后,onclick 不可靠,所以才需要 SW 的 notificationclick。
Notification.permission:default / granted / deniedNotification.requestPermission():请求授权(需要用户手势触发更稳)文件:src/main.ts
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/notification-sw.js').catch((error: unknown) => { console.warn('[NotificationSW] register failed:', error) }) })}作用:让浏览器知道通知点击事件由 public/notification-sw.js 接管。
文件:src/composables/useBrowserNotification.ts
项目实现的关键点:
granted 直接拦截showNotificationdata.url 传跳转目标new Notification核心片段:
const notificationOptions: NotificationOptions = { body: options.body, icon: notificationIcon, badge: notificationBadgeIcon, requireInteraction: options.requireInteraction ?? false, tag: options.tag, data: { url: options.clickUrl ?? '' }}await registration.showNotification(options.title, notificationOptions)文件:public/notification-sw.js
self.addEventListener('notificationclick', (event) => { event.notification.close() const targetUrl = String(event.notification?.data?.url || '').trim() if (!targetUrl) return event.waitUntil( self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => { for (const client of clients) { if (client.url === targetUrl && 'focus' in client) { return client.focus() } } return self.clients.openWindow(targetUrl) }) )})这段逻辑保证:
点击系统通知时,事件发给的是 Service Worker,不依赖页面是否还活着。
因此即使用户关了页面,只要 SW 生效,仍可完成 focus/openWindow。
localhost,否则 Notification/SW 都可能不可用clickUrl 建议绝对地址,避免路由 base 造成解析偏差tag 要按业务维度设计(例如 module-item-123),防止通知刷屏denied 状态给出 UI 引导,提示去浏览器设置中手动开启requireInteraction 行为在不同浏览器有差异,需实机验证import { computed, ref, type ComputedRef, type Ref } from 'vue'import notificationBadgeIcon from '@/assets/images/notify-badge-placeholder.png'import notificationIcon from '@/assets/images/notify-icon-placeholder.png'type NotifyPermission = NotificationPermission | 'unsupported'interface SendBrowserNotificationOptions { title: string body: string clickUrl?: string tag?: string requireInteraction?: boolean autoCloseMs?: number onClick?: () => void}interface UseBrowserNotification { message: Ref<string> isSupported: Ref<boolean> permissionState: Ref<NotifyPermission> supportText: ComputedRef<string> permissionLabel: ComputedRef<string> requestNotifyPermission: () => Promise<void> sendBrowserNotification: (options: SendBrowserNotificationOptions) => void}export const useBrowserNotification = (): UseBrowserNotification => { const message = ref('等待操作') const isSupported = ref<boolean>(typeof window !== 'undefined' && 'Notification' in window) const permissionState = ref<NotifyPermission>(isSupported.value ? Notification.permission : 'unsupported') const supportText = computed(() => (isSupported.value ? '是' : '否')) const permissionLabel = computed(() => { if (permissionState.value === 'unsupported') return '浏览器不支持' if (permissionState.value === 'granted') return '已授权' if (permissionState.value === 'denied') return '已拒绝' return '未授权(default)' }) const updatePermissionState = (): void => { permissionState.value = isSupported.value ? Notification.permission : 'unsupported' } const requestNotifyPermission = async (): Promise<void> => { if (!isSupported.value) { message.value = '当前浏览器不支持 Notification API' return } try { const result = await Notification.requestPermission() permissionState.value = result message.value = `权限申请结果:${result}` } catch (error) { message.value = '申请通知权限失败,请稍后重试' console.error('Notification.requestPermission failed:', error) } } const getServiceWorkerRegistration = async (): Promise<ServiceWorkerRegistration | null> => { if (typeof window === 'undefined' || !('serviceWorker' in navigator)) return null try { return await navigator.serviceWorker.getRegistration() } catch { return null } } const sendBrowserNotification = (options: SendBrowserNotificationOptions): void => { if (!isSupported.value) { message.value = '当前浏览器不支持 Notification API' return } updatePermissionState() if (permissionState.value !== 'granted') { message.value = '请先授权通知权限后再发送' return } const autoCloseMs = options.autoCloseMs ?? 4000 ;(async () => { const registration = await getServiceWorkerRegistration() if (registration) { try { const notificationOptions: NotificationOptions = { body: options.body, icon: notificationIcon, badge: notificationBadgeIcon, requireInteraction: options.requireInteraction ?? false, tag: options.tag, data: { url: options.clickUrl ?? '' } } await registration.showNotification(options.title, notificationOptions) message.value = `通知已发送:${options.title}` return } catch (error) { console.warn('ServiceWorker showNotification failed, fallback to page notification:', error) } } try { const notificationOptions: NotificationOptions = { body: options.body, icon: notificationIcon, badge: notificationBadgeIcon, requireInteraction: options.requireInteraction ?? false } // 不传 tag 时允许系统通知叠加显示;传 tag 时按 tag 覆盖同组通知 if (options.tag) { notificationOptions.tag = options.tag } const notice = new Notification(options.title, notificationOptions) const shouldAutoClose = !(options.requireInteraction ?? false) const autoCloseTimer = shouldAutoClose ? window.setTimeout(() => { notice.close() }, autoCloseMs) : null notice.onclick = () => { window.focus() notice.close() if (options.clickUrl) { window.open(options.clickUrl, '_blank', 'noopener,noreferrer') } options.onClick?.() message.value = '已点击通知,窗口已尝试聚焦' } notice.onclose = () => { if (autoCloseTimer !== null) { window.clearTimeout(autoCloseTimer) } } notice.onerror = () => { if (autoCloseTimer !== null) { window.clearTimeout(autoCloseTimer) } message.value = '通知发送失败,请检查浏览器通知设置' } message.value = `通知已发送:${options.title}` } catch (error) { message.value = '创建通知失败,请检查浏览器设置' console.error('Notification constructor failed:', error) } })().catch((error: unknown) => { message.value = '创建通知失败,请检查浏览器设置' console.error('sendBrowserNotification failed:', error) }) } return { message, isSupported, permissionState, supportText, permissionLabel, requestNotifyPermission, sendBrowserNotification }}self.addEventListener('notificationclick', (event) => { event.notification.close() const targetUrl = String(event.notification?.data?.url || '').trim() if (!targetUrl) return event.waitUntil( self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => { for (const client of clients) { if (client.url === targetUrl && 'focus' in client) { return client.focus() } } return self.clients.openWindow(targetUrl) }), )})以上就是Vue3利用Notification API实现浏览器通知功能的详细内容,更多关于Vue3 Notification浏览器通知的资料请关注本站其它相关文章!