新增未读消息计数和桌面图标徽章更新功能

This commit is contained in:
jxxghp
2025-06-12 22:58:16 +08:00
parent b69a338e13
commit 750b91db66
5 changed files with 178 additions and 10 deletions

View File

@@ -1008,6 +1008,8 @@ export interface SystemNotification {
text: string
// 通知时间
date: string
// 是否已读
read?: boolean
}
// 下载器配置

View File

@@ -2,6 +2,7 @@
import { formatDateDifference } from '@core/utils/formatters'
import { SystemNotification } from '@/api/types'
import { useI18n } from 'vue-i18n'
import { updateAppBadge, clearAppBadge } from '@/utils/badge'
const { t } = useI18n()
@@ -17,6 +18,34 @@ let eventSource: EventSource | null = null
// 弹窗
const appsMenu = ref(false)
// 未读消息数量
const unreadCount = computed(() => {
return notificationList.value.filter(item => !item.read).length
})
// 更新桌面图标徽章
async function updateBadge() {
const count = unreadCount.value
await updateAppBadge(count)
}
// 清除桌面图标徽章
async function clearBadge() {
await clearAppBadge()
}
// 标记所有消息为已读
function markAllAsRead() {
hasNewMessage.value = false
// 标记所有消息为已读
notificationList.value.forEach(item => {
item.read = true
})
// 清除桌面图标徽章
clearBadge()
appsMenu.value = false
}
// SSE持续接收消息
function startSSEMessager() {
// 延迟 3 秒启动 SSE避免相关认证信息尚未写入 Cookie 导致 403
@@ -27,6 +56,8 @@ function startSSEMessager() {
const noti: SystemNotification = JSON.parse(event.data)
notificationList.value.unshift(noti)
hasNewMessage.value = true
// 更新桌面图标徽章
updateBadge()
}
})
}, 3000)
@@ -70,15 +101,7 @@ onBeforeUnmount(() => {
<template #append>
<VTooltip :text="t('notification.markRead')">
<template #activator="{ props }">
<IconBtn
v-bind="props"
@click="
() => {
hasNewMessage = false
appsMenu = false
}
"
>
<IconBtn v-bind="props" @click="markAllAsRead">
<VIcon icon="mdi-email-check-outline" size="20" />
</IconBtn>
</template>

View File

@@ -11,6 +11,9 @@ precacheAndRoute(self.__WB_MANIFEST)
// to allow work offline
registerRoute(new NavigationRoute(createHandlerBoundToURL('index.html'), { denylist: [/^(\/[\w-]+)*\/api/] }))
// 消息计数器,用于存储未读消息数量
let unreadCount = 0
// 通知选项
const options = {
icon: '/logo.png',
@@ -18,6 +21,33 @@ const options = {
actions: [{ action: 'close', title: '关闭' }],
}
// 更新桌面图标徽章
async function updateBadge(count: number) {
if ('setAppBadge' in navigator) {
try {
if (count > 0) {
await navigator.setAppBadge(count)
} else {
await navigator.clearAppBadge()
}
} catch (error) {
console.error('Failed to update app badge:', error)
}
}
}
// 清除桌面图标徽章
async function clearBadge() {
if ('clearAppBadge' in navigator) {
try {
await navigator.clearAppBadge()
unreadCount = 0
} catch (error) {
console.error('Failed to clear app badge:', error)
}
}
}
// 监听 push 事件,显示通知
self.addEventListener('push', function (event) {
console.log('notification push')
@@ -43,7 +73,12 @@ self.addEventListener('push', function (event) {
data: { url: payload.url },
actions: options.actions,
}
event.waitUntil(self.registration.showNotification(payload.title, content))
// 增加未读消息计数
unreadCount++
// 更新桌面图标徽章
event.waitUntil(Promise.all([self.registration.showNotification(payload.title, content), updateBadge(unreadCount)]))
} catch (e) {
console.error(e)
}
@@ -71,3 +106,26 @@ self.addEventListener('notificationclick', function (event) {
event.waitUntil(self.clients.openWindow(info.data?.url))
}
})
// 监听来自主应用的消息,用于清除徽章或更新徽章数量
self.addEventListener('message', function (event) {
console.log('service worker received message:', event.data)
if (event.data && event.data.type === 'CLEAR_BADGE') {
// 清除徽章
clearBadge()
event.ports[0]?.postMessage({ success: true })
} else if (event.data && event.data.type === 'UPDATE_BADGE') {
// 更新徽章数量
const count = event.data.count || 0
unreadCount = count
updateBadge(count)
.then(() => {
event.ports[0]?.postMessage({ success: true })
})
.catch(error => {
console.error('Failed to update badge:', error)
event.ports[0]?.postMessage({ success: false, error: error.message })
})
}
})

17
src/types/global.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
// PWA Badge API 类型定义
declare global {
interface Navigator {
/**
* 设置应用徽章数量
* @param contents 要显示的数量,可选
*/
setAppBadge(contents?: number): Promise<void>
/**
* 清除应用徽章
*/
clearAppBadge(): Promise<void>
}
}
export {}

68
src/utils/badge.ts Normal file
View File

@@ -0,0 +1,68 @@
/**
* PWA 徽章管理工具
*/
// 清除桌面图标徽章
export async function clearAppBadge(): Promise<boolean> {
try {
// 如果浏览器支持原生Badge API直接调用
if ('clearAppBadge' in navigator) {
await navigator.clearAppBadge()
}
// 向service worker发送清除徽章消息
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
const messageChannel = new MessageChannel()
return new Promise(resolve => {
messageChannel.port1.onmessage = event => {
resolve(event.data.success)
}
navigator.serviceWorker.controller?.postMessage({ type: 'CLEAR_BADGE' }, [messageChannel.port2])
})
}
return true
} catch (error) {
console.error('Failed to clear app badge:', error)
return false
}
}
// 更新桌面图标徽章数量
export async function updateAppBadge(count: number): Promise<boolean> {
try {
// 如果浏览器支持原生Badge API直接调用
if ('setAppBadge' in navigator) {
if (count > 0) {
await navigator.setAppBadge(count)
} else {
await navigator.clearAppBadge()
}
}
// 向service worker发送更新徽章消息
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
const messageChannel = new MessageChannel()
return new Promise(resolve => {
messageChannel.port1.onmessage = event => {
resolve(event.data.success)
}
navigator.serviceWorker.controller?.postMessage({ type: 'UPDATE_BADGE', count }, [messageChannel.port2])
})
}
return true
} catch (error) {
console.error('Failed to update app badge:', error)
return false
}
}
// 检查浏览器是否支持Badge API
export function supportsBadgeAPI(): boolean {
return 'setAppBadge' in navigator && 'clearAppBadge' in navigator
}