桌面通知(系统级消息提醒

This commit is contained in:
yml
2026-06-05 20:06:50 +08:00
parent e32362f425
commit bdd1ba6052
8 changed files with 383 additions and 17 deletions
@@ -0,0 +1,134 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import type { ChatEvent } from './useChatSSE'
export type NotificationPermission = 'default' | 'granted' | 'denied'
const permission = ref<NotificationPermission>('default')
const isSupported = 'Notification' in window
/**
* 桌面通知 composable
* 用于在收到新消息时发送系统级通知
*/
export function useDesktopNotification(scope: 'user' | 'admin') {
const router = useRouter()
// 初始化权限状态
if (isSupported) {
permission.value = Notification.permission as NotificationPermission
}
/**
* 请求通知权限
*/
async function requestPermission(): Promise<NotificationPermission> {
if (!isSupported) return 'denied'
try {
const result = await Notification.requestPermission()
permission.value = result as NotificationPermission
return permission.value
} catch {
permission.value = 'denied'
return 'denied'
}
}
/**
* 检查是否应该发送通知
*/
function shouldNotify(conversationId: number, currentConversationId: number | null): boolean {
// 不支持通知
if (!isSupported || permission.value !== 'granted') return false
// 页面在前台且正在查看该会话 - 不需要通知
if (!document.hidden && currentConversationId === conversationId) return false
return true
}
/**
* 发送桌面通知
*/
function notify(event: ChatEvent, currentConversationId: number | null) {
if (!event.message) return
if (!shouldNotify(event.conversation_id, currentConversationId)) return
const { sender_name, sender_role, content, attachment_urls } = event.message
// 构建通知内容
let body = content
if (!body && attachment_urls.length > 0) {
body = `[图片消息]`
}
if (!body) {
body = '收到新消息'
}
// 角色标签
const roleMap: Record<string, string> = {
renter: '租客',
owner: '号主',
support: '客服',
customer: '咨询',
admin: '管理员',
system: '系统',
}
const roleLabel = roleMap[sender_role] || '成员'
const title = `${roleLabel} · ${sender_name}`
try {
const notification = new Notification(title, {
body: body.length > 100 ? body.substring(0, 100) + '...' : body,
icon: '/favicon.ico',
badge: '/favicon.ico',
tag: `chat-${event.conversation_id}`, // 相同会话的通知会被替换
requireInteraction: false,
silent: false,
})
// 点击通知跳转到对应会话
notification.onclick = () => {
window.focus()
const path = scope === 'admin'
? `/admin/chats/${event.conversation_id}`
: `/messages/${event.conversation_id}`
router.push(path)
notification.close()
}
// 5秒后自动关闭
setTimeout(() => {
notification.close()
}, 5000)
} catch (error) {
console.error('Failed to show notification:', error)
}
}
/**
* 静默请求权限(不强制,用户可以忽略)
*/
function requestPermissionSilently() {
if (!isSupported || permission.value !== 'default') return
// 仅在用户交互后才请求(浏览器要求)
const handleInteraction = () => {
requestPermission()
document.removeEventListener('click', handleInteraction)
document.removeEventListener('keydown', handleInteraction)
}
document.addEventListener('click', handleInteraction, { once: true })
document.addEventListener('keydown', handleInteraction, { once: true })
}
return {
permission,
isSupported,
requestPermission,
notify,
requestPermissionSilently,
}
}