mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
Fix notification read and clear state
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SystemNotification } from '@/api/types'
|
import type { SystemNotification } from '@/api/types'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { clearUnreadMessages } from '@/utils/badge'
|
import { appUnreadMessageCount, clearUnreadMessages } from '@/utils/badge'
|
||||||
import { emitAgentAssistantNotificationBubble } from '@/utils/agentAssistantBubble'
|
import { emitAgentAssistantNotificationBubble } from '@/utils/agentAssistantBubble'
|
||||||
import { formatDateDifference } from '@core/utils/formatters'
|
import { formatDateDifference } from '@core/utils/formatters'
|
||||||
import { useBackground } from '@/composables/useBackground'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
@@ -13,7 +13,6 @@ type NotificationDisplayItem =
|
|||||||
| { kind: 'section'; key: string; title: string; count: number }
|
| { kind: 'section'; key: string; title: string; count: number }
|
||||||
| { kind: 'notification'; key: string; notification: SystemNotification }
|
| { kind: 'notification'; key: string; notification: SystemNotification }
|
||||||
type NotificationClearScope = 'all' | 'system' | 'media'
|
type NotificationClearScope = 'all' | 'system' | 'media'
|
||||||
type NotificationClearBefore = Record<NotificationClearScope, number>
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDelayedSSE } = useBackground()
|
const { useDelayedSSE } = useBackground()
|
||||||
@@ -23,9 +22,6 @@ const createConfirm = useConfirm()
|
|||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
// 虚拟滚动的默认通知项高度,展开后的实际高度由 VVirtualScroll 的 itemRef 动态测量。
|
// 虚拟滚动的默认通知项高度,展开后的实际高度由 VVirtualScroll 的 itemRef 动态测量。
|
||||||
const NOTIFICATION_ITEM_HEIGHT = 136
|
const NOTIFICATION_ITEM_HEIGHT = 136
|
||||||
const MAX_FILTERED_PAGES_PER_LOAD = 5
|
|
||||||
const CLEAR_NOTIFICATION_ENDPOINTS = ['message/notification', 'message/notification/clear']
|
|
||||||
const NOTIFICATION_CLEAR_BEFORE_STORAGE_KEY = 'moviepilot-notification-clear-before'
|
|
||||||
|
|
||||||
const appsMenu = ref(false)
|
const appsMenu = ref(false)
|
||||||
const hasNewMessage = ref(false)
|
const hasNewMessage = ref(false)
|
||||||
@@ -35,10 +31,12 @@ const loading = ref(false)
|
|||||||
const clearing = ref(false)
|
const clearing = ref(false)
|
||||||
const hasMore = ref(true)
|
const hasMore = ref(true)
|
||||||
const notificationKeys = new Set<string>()
|
const notificationKeys = new Set<string>()
|
||||||
const notificationClearBefore = ref(readNotificationClearBefore())
|
|
||||||
const expandedNotificationKeys = ref(new Set<string>())
|
const expandedNotificationKeys = ref(new Set<string>())
|
||||||
|
|
||||||
const hasUnreadNotifications = computed(() => notificationList.value.some(item => item.read === false))
|
const hasUnreadNotifications = computed(() => notificationList.value.some(item => item.read === false))
|
||||||
|
const hasBadgeUnreadMessages = computed(() => appUnreadMessageCount.value > 0)
|
||||||
|
const canMarkAllAsRead = computed(() => hasUnreadNotifications.value || hasBadgeUnreadMessages.value)
|
||||||
|
const hasUnreadMessageIndicator = computed(() => hasNewMessage.value || canMarkAllAsRead.value)
|
||||||
const notificationDisplayList = computed(() => buildNotificationDisplayList(notificationList.value))
|
const notificationDisplayList = computed(() => buildNotificationDisplayList(notificationList.value))
|
||||||
const notificationClearCounts = computed(() => getNotificationClearCounts())
|
const notificationClearCounts = computed(() => getNotificationClearCounts())
|
||||||
const notificationClearOptions = computed(() => [
|
const notificationClearOptions = computed(() => [
|
||||||
@@ -65,60 +63,6 @@ const notificationClearOptions = computed(() => [
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
/** 生成默认清理时间,分别记录全部、系统消息和媒体消息的清理范围。 */
|
|
||||||
function createDefaultNotificationClearBefore(): NotificationClearBefore {
|
|
||||||
return {
|
|
||||||
all: 0,
|
|
||||||
system: 0,
|
|
||||||
media: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 规范化清理时间,兼容旧版本只存一个数字的本地存储格式。 */
|
|
||||||
function normalizeNotificationClearBefore(value: unknown): NotificationClearBefore {
|
|
||||||
const clearBefore = createDefaultNotificationClearBefore()
|
|
||||||
|
|
||||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
||||||
clearBefore.all = value
|
|
||||||
return clearBefore
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value || typeof value !== 'object') return clearBefore
|
|
||||||
|
|
||||||
const scopes: NotificationClearScope[] = ['all', 'system', 'media']
|
|
||||||
scopes.forEach(scope => {
|
|
||||||
const scopeValue = Number((value as Partial<NotificationClearBefore>)[scope] || 0)
|
|
||||||
clearBefore[scope] = Number.isFinite(scopeValue) ? scopeValue : 0
|
|
||||||
})
|
|
||||||
|
|
||||||
return clearBefore
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从本地存储读取通知清理时间戳,用于过滤已清理的历史通知。 */
|
|
||||||
function readNotificationClearBefore() {
|
|
||||||
if (typeof localStorage === 'undefined') return createDefaultNotificationClearBefore()
|
|
||||||
|
|
||||||
const storedValue = localStorage.getItem(NOTIFICATION_CLEAR_BEFORE_STORAGE_KEY)
|
|
||||||
if (!storedValue) return createDefaultNotificationClearBefore()
|
|
||||||
|
|
||||||
try {
|
|
||||||
return normalizeNotificationClearBefore(JSON.parse(storedValue))
|
|
||||||
} catch {
|
|
||||||
return normalizeNotificationClearBefore(Number(storedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 写入指定范围的通知清理时间戳,使清理结果在刷新后仍然生效。 */
|
|
||||||
function writeNotificationClearBefore(scope: NotificationClearScope, value: number) {
|
|
||||||
notificationClearBefore.value = {
|
|
||||||
...notificationClearBefore.value,
|
|
||||||
[scope]: value,
|
|
||||||
}
|
|
||||||
if (typeof localStorage === 'undefined') return
|
|
||||||
|
|
||||||
localStorage.setItem(NOTIFICATION_CLEAR_BEFORE_STORAGE_KEY, JSON.stringify(notificationClearBefore.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将通知备注统一转换成稳定字符串,用于生成去重 key。 */
|
/** 将通知备注统一转换成稳定字符串,用于生成去重 key。 */
|
||||||
function normalizeNote(note: SystemNotification['note']) {
|
function normalizeNote(note: SystemNotification['note']) {
|
||||||
if (note == null) return ''
|
if (note == null) return ''
|
||||||
@@ -185,16 +129,6 @@ function parseNotificationTime(value: string) {
|
|||||||
return new Date(value.includes('T') ? value : value.replaceAll(/-/g, '/')).getTime() || 0
|
return new Date(value.includes('T') ? value : value.replaceAll(/-/g, '/')).getTime() || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断历史通知是否早于本地清理时间,需要从列表中过滤。 */
|
|
||||||
function isClearedHistoryNotification(item: SystemNotification) {
|
|
||||||
const scope = getNotificationClearScope(item)
|
|
||||||
const clearBefore = Math.max(notificationClearBefore.value.all, notificationClearBefore.value[scope])
|
|
||||||
if (!clearBefore) return false
|
|
||||||
|
|
||||||
const notificationTime = parseNotificationTime(getNotificationTime(item))
|
|
||||||
return notificationTime > 0 && notificationTime <= clearBefore
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 按通知时间倒序重排当前列表。 */
|
/** 按通知时间倒序重排当前列表。 */
|
||||||
function sortNotifications() {
|
function sortNotifications() {
|
||||||
notificationList.value = [...notificationList.value].sort(
|
notificationList.value = [...notificationList.value].sort(
|
||||||
@@ -284,8 +218,8 @@ function rebuildExpandedNotificationKeys() {
|
|||||||
|
|
||||||
/** 列表内容变化后同步未读红点和应用角标状态。 */
|
/** 列表内容变化后同步未读红点和应用角标状态。 */
|
||||||
function syncUnreadStateAfterListChange() {
|
function syncUnreadStateAfterListChange() {
|
||||||
hasNewMessage.value = hasUnreadNotifications.value
|
// 只用当前列表更新通知中心红点,应用 badge 数量由 badge 工具维护。
|
||||||
if (!hasUnreadNotifications.value) void clearUnreadMessages()
|
hasNewMessage.value = canMarkAllAsRead.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 统计当前已加载通知中各清理范围的数量,用于菜单展示和禁用空操作。 */
|
/** 统计当前已加载通知中各清理范围的数量,用于菜单展示和禁用空操作。 */
|
||||||
@@ -333,33 +267,12 @@ function getClearSuccessText(scope: NotificationClearScope) {
|
|||||||
return t('notification.clearAllSuccess')
|
return t('notification.clearAllSuccess')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 通过后端接口清理全部通知历史,兼容新旧后端可能暴露的清理路径。 */
|
/** 调用后端记录清理范围,后续分页查询会直接返回过滤后的通知。 */
|
||||||
async function deleteNotificationHistory() {
|
|
||||||
let lastError: unknown = null
|
|
||||||
|
|
||||||
for (const endpoint of CLEAR_NOTIFICATION_ENDPOINTS) {
|
|
||||||
try {
|
|
||||||
return await api.delete(endpoint)
|
|
||||||
} catch (error: any) {
|
|
||||||
lastError = error
|
|
||||||
if (error?.response?.status !== 404 && error?.response?.status !== 405) break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 尝试调用后端清理接口;分类清理使用本地时间戳过滤以兼容当前全量接口语义。 */
|
|
||||||
async function tryDeleteNotificationHistory(scope: NotificationClearScope) {
|
async function tryDeleteNotificationHistory(scope: NotificationClearScope) {
|
||||||
if (scope !== 'all') return true
|
const result: { [key: string]: any } = await api.delete('message/notification', {
|
||||||
|
params: { scope },
|
||||||
try {
|
})
|
||||||
const result: { [key: string]: any } = await deleteNotificationHistory()
|
return result?.success !== false
|
||||||
return result?.success !== false
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error?.response?.status === 404 || error?.response?.status === 405) return true
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 确认并清空通知中心历史,同时同步清理未读角标。 */
|
/** 确认并清空通知中心历史,同时同步清理未读角标。 */
|
||||||
@@ -382,7 +295,6 @@ async function clearNotifications(scope: NotificationClearScope) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeNotificationClearBefore(scope, Date.now())
|
|
||||||
removeNotificationsByScope(scope)
|
removeNotificationsByScope(scope)
|
||||||
if (scope === 'all') {
|
if (scope === 'all') {
|
||||||
await clearUnreadMessages()
|
await clearUnreadMessages()
|
||||||
@@ -410,30 +322,24 @@ async function loadNotifications({ done }: { done: (status: 'ok' | 'empty' | 'er
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
let accepted = false
|
|
||||||
let loadedPages = 0
|
|
||||||
|
|
||||||
// 清理后的分页里可能连续出现已被本地过滤的历史消息,循环跳过这些空页。
|
const items = (await api.get('message/notification', {
|
||||||
while (hasMore.value && !accepted && loadedPages < MAX_FILTERED_PAGES_PER_LOAD) {
|
params: {
|
||||||
const items = (await api.get('message/notification', {
|
page: page.value,
|
||||||
params: {
|
count: PAGE_SIZE,
|
||||||
page: page.value,
|
},
|
||||||
count: PAGE_SIZE,
|
})) as SystemNotification[]
|
||||||
},
|
|
||||||
})) as SystemNotification[]
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
break
|
done('empty')
|
||||||
}
|
return
|
||||||
|
|
||||||
const visibleItems = items.filter(item => !isClearedHistoryNotification(item))
|
|
||||||
accepted = mergeNotifications(visibleItems, { read: true })
|
|
||||||
page.value += 1
|
|
||||||
loadedPages += 1
|
|
||||||
hasMore.value = items.length >= PAGE_SIZE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergeNotifications(items, { read: true })
|
||||||
|
page.value += 1
|
||||||
|
hasMore.value = items.length >= PAGE_SIZE
|
||||||
|
|
||||||
done(hasMore.value ? 'ok' : 'empty')
|
done(hasMore.value ? 'ok' : 'empty')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载通知失败:', error)
|
console.error('加载通知失败:', error)
|
||||||
@@ -449,7 +355,6 @@ function handleMessage(event: MessageEvent) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const notification = JSON.parse(event.data) as SystemNotification
|
const notification = JSON.parse(event.data) as SystemNotification
|
||||||
if (isClearedHistoryNotification(notification)) return
|
|
||||||
|
|
||||||
if (mergeNotifications([notification], { prepend: true, read: false })) {
|
if (mergeNotifications([notification], { prepend: true, read: false })) {
|
||||||
hasNewMessage.value = true
|
hasNewMessage.value = true
|
||||||
@@ -462,6 +367,8 @@ function handleMessage(event: MessageEvent) {
|
|||||||
|
|
||||||
/** 将通知列表标记为已读,并同步清理应用角标、未读红点和通知弹窗。 */
|
/** 将通知列表标记为已读,并同步清理应用角标、未读红点和通知弹窗。 */
|
||||||
function markAllAsRead() {
|
function markAllAsRead() {
|
||||||
|
if (!canMarkAllAsRead.value) return
|
||||||
|
|
||||||
hasNewMessage.value = false
|
hasNewMessage.value = false
|
||||||
notificationList.value.forEach(item => {
|
notificationList.value.forEach(item => {
|
||||||
item.read = true
|
item.read = true
|
||||||
@@ -536,11 +443,10 @@ function isNotificationExpanded(item: SystemNotification) {
|
|||||||
return expandedNotificationKeys.value.has(getNotificationExpansionKey(item))
|
return expandedNotificationKeys.value.has(getNotificationExpansionKey(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记单条通知为已读,并在全部已读时同步清理未读角标。 */
|
/** 标记单条通知为已读,仅同步当前通知中心列表的未读状态。 */
|
||||||
function markNotificationAsRead(item: SystemNotification) {
|
function markNotificationAsRead(item: SystemNotification) {
|
||||||
item.read = true
|
item.read = true
|
||||||
hasNewMessage.value = hasUnreadNotifications.value
|
hasNewMessage.value = hasUnreadNotifications.value
|
||||||
if (!hasUnreadNotifications.value) void clearUnreadMessages()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换通知正文展开状态。 */
|
/** 切换通知正文展开状态。 */
|
||||||
@@ -579,7 +485,7 @@ useDelayedSSE(
|
|||||||
scrim
|
scrim
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<VBadge v-if="hasNewMessage" dot color="error" :offset-x="5" :offset-y="5" v-bind="props">
|
<VBadge v-if="hasUnreadMessageIndicator" dot color="error" :offset-x="5" :offset-y="5" v-bind="props">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
<VIcon icon="mdi-bell-outline" size="22" />
|
<VIcon icon="mdi-bell-outline" size="22" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
@@ -628,7 +534,7 @@ useDelayedSSE(
|
|||||||
</VTooltip>
|
</VTooltip>
|
||||||
<VTooltip :text="t('notification.markRead')">
|
<VTooltip :text="t('notification.markRead')">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" :disabled="!hasUnreadNotifications" @click.stop="markAllAsRead">
|
<IconBtn v-bind="props" :disabled="!canMarkAllAsRead" @click.stop="markAllAsRead">
|
||||||
<VIcon icon="mdi-email-check-outline" size="20" />
|
<VIcon icon="mdi-email-check-outline" size="20" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+18
-1
@@ -283,6 +283,17 @@ async function setStoredUnreadCount(count: number): Promise<void> {
|
|||||||
await set(UNREAD_COUNT_KEY, count)
|
await set(UNREAD_COUNT_KEY, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通知已打开的页面同步未读计数,保证前台通知中心能感知 PWA badge 的变化。
|
||||||
|
async function broadcastUnreadCount(count: number) {
|
||||||
|
const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' })
|
||||||
|
clients.forEach(client => {
|
||||||
|
client.postMessage({
|
||||||
|
type: 'UNREAD_COUNT_UPDATE',
|
||||||
|
count,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function updateBadge(count: number) {
|
async function updateBadge(count: number) {
|
||||||
if ('setAppBadge' in self.navigator) {
|
if ('setAppBadge' in self.navigator) {
|
||||||
try {
|
try {
|
||||||
@@ -309,6 +320,7 @@ async function clearBadge() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await setStoredUnreadCount(0)
|
await setStoredUnreadCount(0)
|
||||||
|
await broadcastUnreadCount(0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to clear unread count:', error)
|
console.error('Failed to clear unread count:', error)
|
||||||
}
|
}
|
||||||
@@ -422,7 +434,11 @@ self.addEventListener('push', function (event) {
|
|||||||
const currentCount = await getStoredUnreadCount()
|
const currentCount = await getStoredUnreadCount()
|
||||||
const newCount = currentCount + 1
|
const newCount = currentCount + 1
|
||||||
await setStoredUnreadCount(newCount)
|
await setStoredUnreadCount(newCount)
|
||||||
await Promise.all([self.registration.showNotification(payload.title, content), updateBadge(newCount)])
|
await Promise.all([
|
||||||
|
self.registration.showNotification(payload.title, content),
|
||||||
|
updateBadge(newCount),
|
||||||
|
broadcastUnreadCount(newCount),
|
||||||
|
])
|
||||||
})(),
|
})(),
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -454,6 +470,7 @@ self.addEventListener('message', function (event) {
|
|||||||
const count = event.data.count || 0
|
const count = event.data.count || 0
|
||||||
setStoredUnreadCount(count)
|
setStoredUnreadCount(count)
|
||||||
.then(() => updateBadge(count))
|
.then(() => updateBadge(count))
|
||||||
|
.then(() => broadcastUnreadCount(count))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
event.ports[0]?.postMessage({ success: true })
|
event.ports[0]?.postMessage({ success: true })
|
||||||
})
|
})
|
||||||
|
|||||||
+42
-8
@@ -1,3 +1,5 @@
|
|||||||
|
import { readonly, ref } from 'vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PWA 徽章管理工具
|
* PWA 徽章管理工具
|
||||||
*/
|
*/
|
||||||
@@ -7,9 +9,27 @@ interface UnreadMessageEvent extends CustomEvent {
|
|||||||
detail: { count: number }
|
detail: { count: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unreadMessageCount = ref(0)
|
||||||
|
|
||||||
|
// 暴露只读未读计数,供通知中心等组件直接判断应用角标状态。
|
||||||
|
export const appUnreadMessageCount = readonly(unreadMessageCount)
|
||||||
|
|
||||||
|
function normalizeUnreadMessageCount(count: unknown) {
|
||||||
|
const normalizedCount = Number(count)
|
||||||
|
if (!Number.isFinite(normalizedCount) || normalizedCount <= 0) return 0
|
||||||
|
|
||||||
|
return Math.floor(normalizedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUnreadMessageCount(count: unknown) {
|
||||||
|
unreadMessageCount.value = normalizeUnreadMessageCount(count)
|
||||||
|
return unreadMessageCount.value
|
||||||
|
}
|
||||||
|
|
||||||
// 发送全局未读消息事件
|
// 发送全局未读消息事件
|
||||||
export function emitUnreadMessageEvent(count: number) {
|
export function emitUnreadMessageEvent(count: number) {
|
||||||
const event = new CustomEvent('unreadMessage', { detail: { count } }) as UnreadMessageEvent
|
const normalizedCount = setUnreadMessageCount(count)
|
||||||
|
const event = new CustomEvent('unreadMessage', { detail: { count: normalizedCount } }) as UnreadMessageEvent
|
||||||
window.dispatchEvent(event)
|
window.dispatchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +108,8 @@ export async function checkUnreadOnStartup(): Promise<number> {
|
|||||||
export async function checkAndEmitUnreadMessages() {
|
export async function checkAndEmitUnreadMessages() {
|
||||||
try {
|
try {
|
||||||
const count = await checkUnreadOnStartup()
|
const count = await checkUnreadOnStartup()
|
||||||
if (count > 0) {
|
// 启动时同步 0 值,避免组件复用上一轮角标状态。
|
||||||
emitUnreadMessageEvent(count)
|
emitUnreadMessageEvent(count)
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 静默处理错误
|
// 静默处理错误
|
||||||
}
|
}
|
||||||
@@ -139,11 +158,13 @@ export async function clearAppBadge(): Promise<boolean> {
|
|||||||
|
|
||||||
// 更新桌面图标徽章数量
|
// 更新桌面图标徽章数量
|
||||||
export async function updateAppBadge(count: number): Promise<boolean> {
|
export async function updateAppBadge(count: number): Promise<boolean> {
|
||||||
|
const normalizedCount = normalizeUnreadMessageCount(count)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 如果浏览器支持原生Badge API,直接调用
|
// 如果浏览器支持原生Badge API,直接调用
|
||||||
if ('setAppBadge' in navigator) {
|
if ('setAppBadge' in navigator) {
|
||||||
if (count > 0) {
|
if (normalizedCount > 0) {
|
||||||
await navigator.setAppBadge(count)
|
await navigator.setAppBadge(normalizedCount)
|
||||||
} else {
|
} else {
|
||||||
await navigator.clearAppBadge()
|
await navigator.clearAppBadge()
|
||||||
}
|
}
|
||||||
@@ -155,13 +176,18 @@ export async function updateAppBadge(count: number): Promise<boolean> {
|
|||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
messageChannel.port1.onmessage = event => {
|
messageChannel.port1.onmessage = event => {
|
||||||
resolve(event.data.success)
|
const success = Boolean(event.data.success)
|
||||||
|
if (success) emitUnreadMessageEvent(normalizedCount)
|
||||||
|
resolve(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.serviceWorker.controller?.postMessage({ type: 'UPDATE_BADGE', count }, [messageChannel.port2])
|
navigator.serviceWorker.controller?.postMessage({ type: 'UPDATE_BADGE', count: normalizedCount }, [
|
||||||
|
messageChannel.port2,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitUnreadMessageEvent(normalizedCount)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update app badge:', error)
|
console.error('Failed to update app badge:', error)
|
||||||
@@ -195,3 +221,11 @@ export async function getUnreadCount(): Promise<number> {
|
|||||||
export function supportsBadgeAPI(): boolean {
|
export function supportsBadgeAPI(): boolean {
|
||||||
return 'setAppBadge' in navigator && 'clearAppBadge' in navigator
|
return 'setAppBadge' in navigator && 'clearAppBadge' in navigator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.addEventListener('message', event => {
|
||||||
|
if (event.data?.type === 'UNREAD_COUNT_UPDATE') {
|
||||||
|
emitUnreadMessageEvent(event.data.count || 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user