最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3利用Notification API實(shí)現(xiàn)瀏覽器通知功能

 更新時(shí)間:2026年04月28日 09:17:25   作者:拾壹此間  
文章介紹了如何在關(guān)閉瀏覽器后點(diǎn)擊歷史通知仍能打開站點(diǎn)并跳轉(zhuǎn)目標(biāo)頁的方法,主要使用Notification API和Service Worker實(shí)現(xiàn),需要覆蓋舊通知點(diǎn)擊跳轉(zhuǎn)行為,實(shí)現(xiàn)通知發(fā)送、Service Worker處理點(diǎn)擊事件等邏輯,并處理權(quán)限等問題,最后強(qiáng)調(diào)了實(shí)現(xiàn)細(xì)節(jié)和注意事項(xiàng)
  1. Notification API 介紹。
  2. 關(guān)閉瀏覽器后,點(diǎn)擊歷史通知仍能打開站點(diǎn)并跳轉(zhuǎn)目標(biāo)頁,如何實(shí)現(xiàn)。

1. 先說結(jié)論

只用 new Notification() 不夠。要覆蓋“舊通知點(diǎn)擊跳轉(zhuǎn)”,必須:

  • 發(fā)送階段:優(yōu)先 ServiceWorkerRegistration.showNotification()
  • 點(diǎn)擊階段:在 public/notification-sw.js 監(jiān)聽 notificationclick
  • 跳轉(zhuǎn)策略:先找已有窗口并 focus(),沒有再 openWindow()

一句話:把點(diǎn)擊處理從頁面 JS 移到 Service Worker

2. Notification API 參數(shù)

2.1new Notification(title, options)的核心參數(shù)

  • title:通知標(biāo)題(必填)
  • body:正文內(nèi)容
  • icon:大圖標(biāo)(建議 192x192 或 256x256)
  • badge:小徽標(biāo)(Android 常見,建議單色清晰圖)
  • tag:通知分組標(biāo)識(shí);相同 tag 會(huì)覆蓋舊通知
  • data:自定義數(shù)據(jù)載荷(本方案用來傳 url
  • requireInteractiontrue 表示通知不自動(dòng)關(guān)閉(瀏覽器行為可能有差異)
  • silent:是否靜音(不同瀏覽器支持度不同)

示例(占位鏈接):

new Notification('系統(tǒng)提醒', {
  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'
  }
})

2.2 常用事件

  • notification.onclick:頁面存活時(shí)可用
  • notification.onclose:通知關(guān)閉回調(diào)
  • notification.onerror:創(chuàng)建或展示失敗回調(diào)

頁面被關(guān)閉后,onclick 不可靠,所以才需要 SW 的 notificationclick。

2.3 權(quán)限相關(guān) API

  • Notification.permissiondefault / granted / denied
  • Notification.requestPermission():請(qǐng)求授權(quán)(需要用戶手勢(shì)觸發(fā)更穩(wěn))

3. 項(xiàng)目落地實(shí)現(xiàn)(3 步)

3.1 注冊(cè)通知 Service Worker

文件: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)
    })
  })
}

作用:讓瀏覽器知道通知點(diǎn)擊事件由 public/notification-sw.js 接管。

3.2 統(tǒng)一封裝通知發(fā)送(優(yōu)先 SW,失敗降級(jí))

文件:src/composables/useBrowserNotification.ts

項(xiàng)目實(shí)現(xiàn)的關(guān)鍵點(diǎn):

  • 權(quán)限不是 granted 直接攔截
  • 先拿 SW registration,再 showNotification
  • 通過 data.url 傳跳轉(zhuǎn)目標(biāo)
  • SW 發(fā)送失敗再降級(jí)到 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)

3.3 在 SW 中處理點(diǎn)擊(關(guān)鍵中的關(guān)鍵)

文件: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)
    })
  )
})

這段邏輯保證:

  • 有現(xiàn)成頁面:聚焦現(xiàn)有頁
  • 沒有頁面:新開頁并跳轉(zhuǎn)
  • 瀏覽器關(guān)閉后點(diǎn)擊歷史通知:依然可回站

4. 為什么“舊通知點(diǎn)擊可跳轉(zhuǎn)”

點(diǎn)擊系統(tǒng)通知時(shí),事件發(fā)給的是 Service Worker,不依賴頁面是否還活著。
因此即使用戶關(guān)了頁面,只要 SW 生效,仍可完成 focus/openWindow。

5. 注意

  • 使用 HTTPS 或 localhost,否則 Notification/SW 都可能不可用
  • clickUrl 建議絕對(duì)地址,避免路由 base 造成解析偏差
  • tag 要按業(yè)務(wù)維度設(shè)計(jì)(例如 module-item-123),防止通知刷屏
  • 對(duì) denied 狀態(tài)給出 UI 引導(dǎo),提示去瀏覽器設(shè)置中手動(dòng)開啟
  • requireInteraction 行為在不同瀏覽器有差異,需實(shí)機(jī)驗(yàn)證

useBrowserNotification全量源碼

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 '已授權(quán)'
    if (permissionState.value === 'denied') return '已拒絕'
    return '未授權(quán)(default)'
  })

  const updatePermissionState = (): void => {
    permissionState.value = isSupported.value ? Notification.permission : 'unsupported'
  }

  const requestNotifyPermission = async (): Promise<void> => {
    if (!isSupported.value) {
      message.value = '當(dāng)前瀏覽器不支持 Notification API'
      return
    }

    try {
      const result = await Notification.requestPermission()
      permissionState.value = result
      message.value = `權(quán)限申請(qǐng)結(jié)果:${result}`
    } catch (error) {
      message.value = '申請(qǐng)通知權(quán)限失敗,請(qǐng)稍后重試'
      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 = '當(dāng)前瀏覽器不支持 Notification API'
      return
    }

    updatePermissionState()
    if (permissionState.value !== 'granted') {
      message.value = '請(qǐng)先授權(quán)通知權(quán)限后再發(fā)送'
      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 = `通知已發(fā)送:${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 時(shí)允許系統(tǒng)通知疊加顯示;傳 tag 時(shí)按 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 = '已點(diǎn)擊通知,窗口已嘗試聚焦'
        }
        notice.onclose = () => {
          if (autoCloseTimer !== null) {
            window.clearTimeout(autoCloseTimer)
          }
        }
        notice.onerror = () => {
          if (autoCloseTimer !== null) {
            window.clearTimeout(autoCloseTimer)
          }
          message.value = '通知發(fā)送失敗,請(qǐng)檢查瀏覽器通知設(shè)置'
        }

        message.value = `通知已發(fā)送:${options.title}`
      } catch (error) {
        message.value = '創(chuàng)建通知失敗,請(qǐng)檢查瀏覽器設(shè)置'
        console.error('Notification constructor failed:', error)
      }
    })().catch((error: unknown) => {
      message.value = '創(chuàng)建通知失敗,請(qǐng)檢查瀏覽器設(shè)置'
      console.error('sendBrowserNotification failed:', error)
    })
  }

  return {
    message,
    isSupported,
    permissionState,
    supportText,
    permissionLabel,
    requestNotifyPermission,
    sendBrowserNotification
  }
}

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

以上就是Vue3利用Notification API實(shí)現(xiàn)瀏覽器通知功能的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Notification瀏覽器通知的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue元素實(shí)現(xiàn)動(dòng)畫過渡效果

    vue元素實(shí)現(xiàn)動(dòng)畫過渡效果

    這篇文章主要介紹了vue元素實(shí)現(xiàn)動(dòng)畫過渡效果,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • vue基于element的區(qū)間選擇組件

    vue基于element的區(qū)間選擇組件

    這篇文章主要介紹了vue基于element的區(qū)間選擇組件,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • vue輪播圖插件vue-awesome-swiper的使用代碼實(shí)例

    vue輪播圖插件vue-awesome-swiper的使用代碼實(shí)例

    本篇文章主要介紹了vue輪播圖插件vue-awesome-swiper的使用代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Vue監(jiān)聽頁面刷新和關(guān)閉功能

    Vue監(jiān)聽頁面刷新和關(guān)閉功能

    我在做項(xiàng)目的時(shí)候,有一個(gè)需求,在離開(跳轉(zhuǎn)或者關(guān)閉)購(gòu)物車頁面或者刷新購(gòu)物車頁面的時(shí)候向服務(wù)器提交一次購(gòu)物車商品數(shù)量的變化。這篇文章主要介紹了vue監(jiān)聽頁面刷新和關(guān)閉功能,需要的朋友可以參考下
    2019-06-06
  • weex里Vuex state使用storage持久化詳解

    weex里Vuex state使用storage持久化詳解

    本篇文章主要介紹了weex里Vuex state使用storage持久化詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-09-09
  • vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼

    vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼

    這篇文章主要介紹了vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • vue里如何主動(dòng)銷毀keep-alive緩存的組件

    vue里如何主動(dòng)銷毀keep-alive緩存的組件

    這篇文章主要介紹了vue里如何主動(dòng)銷毀keep-alive緩存的組件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • vue3+axios封裝攔截器方式

    vue3+axios封裝攔截器方式

    介紹了如何在Vue項(xiàng)目中使用Axios封裝請(qǐng)求、配置攔截器,并在api.js中統(tǒng)一管理API接口,同時(shí),也講解了如何在vite.config.js中配置解決跨域問題,這些操作可以優(yōu)化前端代碼結(jié)構(gòu),提高開發(fā)效率
    2024-09-09
  • vue.js響應(yīng)式原理解析與實(shí)現(xiàn)

    vue.js響應(yīng)式原理解析與實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了vue.js響應(yīng)式原理解析與實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • Vue中使用Canvas實(shí)現(xiàn)繪制二維碼

    Vue中使用Canvas實(shí)現(xiàn)繪制二維碼

    這篇文章主要為大家詳細(xì)介紹了如何在Vue中使用Canvas實(shí)現(xiàn)繪制二維碼,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2007-02-02

最新評(píng)論

岫岩| 黄陵县| 孟村| 敖汉旗| 饶阳县| 淳化县| 万荣县| 松原市| 泰顺县| 徐闻县| 攀枝花市| 乌什县| 阿瓦提县| 高阳县| 江油市| 古田县| 宝山区| 凤冈县| 东丽区| 云和县| 双桥区| 平谷区| 扬州市| 安岳县| 洮南市| 安陆市| 宁波市| 永济市| 荔浦县| 常宁市| 高平市| 泽库县| 郓城县| 盈江县| 巴林右旗| 浙江省| 图木舒克市| 高台县| 理塘县| 溆浦县| 英山县|