怎样实现一个优雅的通知中心?(Vue 3 + 消息队列实战)

[复制链接]
发表于 2026-8-14 16:53:25 | 显示全部楼层 |阅读模式
🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣
前言

一个完善的通知系统可以明显提拔用户体验,让用户及时了解:

  • 新批评回复
  • 文章被点赞
  • 系统公告
  • 签到嘉奖
本日分享怎样实现一个优雅的通知中心!
功能设计

通知类型
  1. // src/types/notification.ts
  2. export type NotificationType =
  3.   | 'comment'      // 评论通知
  4.   | 'reply'         // 回复通知
  5.   | 'like'          // 点赞通知
  6.   | 'follow'        // 关注通知
  7.   | 'system'        // 系统通知
  8.   | 'achievement'   // 成就通知
  9. export interface Notification {
  10.   id: string
  11.   type: NotificationType
  12.   title: string
  13.   content: string
  14.   avatar?: string
  15.   link?: string
  16.   read: boolean
  17.   createTime: number
  18. }
复制代码
核心实现

1. 通知服务
  1. // src/services/notification.ts
  2. import { defineStore } from 'pinia'
  3. import { ref, computed } from 'vue'
  4. import type { Notification, NotificationType } from '@/types/notification'
  5. export const useNotificationStore = defineStore('notification', () => {
  6.   const notifications = ref<Notification[]>([])
  7.   
  8.   // 加载通知
  9.   function loadNotifications() {
  10.     const data = localStorage.getItem('blog_notifications')
  11.     if (data) {
  12.       notifications.value = JSON.parse(data)
  13.     }
  14.   }
  15.   
  16.   // 保存通知
  17.   function saveNotifications() {
  18.     localStorage.setItem('blog_notifications', JSON.stringify(notifications.value))
  19.   }
  20.   
  21.   // 添加通知
  22.   function addNotification(notification: Omit<Notification, 'id' | 'read' | 'createTime'>) {
  23.     const newNotification: Notification = {
  24.       ...notification,
  25.       id: `notif_${Date.now()}_${Math.random().toString(36).slice(2)}`,
  26.       read: false,
  27.       createTime: Date.now()
  28.     }
  29.    
  30.     notifications.value.unshift(newNotification)
  31.     saveNotifications()
  32.    
  33.     // 触发浏览器通知
  34.     if (Notification.permission === 'granted') {
  35.       new Notification(newNotification.title, {
  36.         body: newNotification.content,
  37.         icon: newNotification.avatar
  38.       })
  39.     }
  40.    
  41.     return newNotification
  42.   }
  43.   
  44.   // 标记已读
  45.   function markAsRead(id: string) {
  46.     const notification = notifications.value.find(n => n.id === id)
  47.     if (notification) {
  48.       notification.read = true
  49.       saveNotifications()
  50.     }
  51.   }
  52.   
  53.   // 全部已读
  54.   function markAllAsRead() {
  55.     notifications.value.forEach(n => {
  56.       n.read = true
  57.     })
  58.     saveNotifications()
  59.   }
  60.   
  61.   // 删除通知
  62.   function deleteNotification(id: string) {
  63.     const index = notifications.value.findIndex(n => n.id === id)
  64.     if (index > -1) {
  65.       notifications.value.splice(index, 1)
  66.       saveNotifications()
  67.     }
  68.   }
  69.   
  70.   // 未读数量
  71.   const unreadCount = computed(() => {
  72.     return notifications.value.filter(n => !n.read).length
  73.   })
  74.   
  75.   // 按类型分组
  76.   const groupedNotifications = computed(() => {
  77.     const groups: Record<NotificationType, Notification[]> = {
  78.       comment: [],
  79.       reply: [],
  80.       like: [],
  81.       follow: [],
  82.       system: [],
  83.       achievement: []
  84.     }
  85.    
  86.     notifications.value.forEach(n => {
  87.       groups[n.type].push(n)
  88.     })
  89.    
  90.     return groups
  91.   })
  92.   
  93.   // 请求通知权限
  94.   async function requestPermission() {
  95.     if ('Notification' in window) {
  96.       const permission = await Notification.requestPermission()
  97.       return permission === 'granted'
  98.     }
  99.     return false
  100.   }
  101.   
  102.   loadNotifications()
  103.   
  104.   return {
  105.     notifications,
  106.     unreadCount,
  107.     groupedNotifications,
  108.     addNotification,
  109.     markAsRead,
  110.     markAllAsRead,
  111.     deleteNotification,
  112.     requestPermission
  113.   }
  114. })
复制代码
2. 通知中心组件
  1. <template>
  2.   <el-popover
  3.     v-model:visible="visible"
  4.     placement="bottom-end"
  5.     :width="360"
  6.     trigger="click"
  7.   >
  8.     <template #reference>
  9.       
  10.         <el-badge :value="unreadCount" :hidden="unreadCount === 0" :max="99">
  11.           <el-button :icon="Bell" circle />
  12.         </el-badge>
  13.         
  14.         <span v-if="hasNewNotification"  />
  15.       
  16.     </template>
  17.    
  18.     <template #default>
  19.       
  20.         
  21.         
  22.           <h3>通知中心</h3>
  23.           <el-button
  24.             v-if="unreadCount > 0"
  25.             text
  26.             size="small"
  27.             @click="handleMarkAllRead"
  28.           >
  29.             全部已读
  30.           </el-button>
  31.         
  32.         
  33.         
  34.         <el-tabs v-model="activeTab" >
  35.           <el-tab-pane label="全部" name="all" />
  36.           <el-tab-pane label="评论" name="comment" />
  37.           <el-tab-pane label="点赞" name="like" />
  38.           <el-tab-pane label="系统" name="system" />
  39.         </el-tabs>
  40.         
  41.         
  42.         
  43.          
  44.             <el-avatar
  45.               :src="notification.avatar || defaultAvatar"
  46.               :size="40"
  47.             />
  48.             
  49.             
  50.               {{ notification.title }}
  51.               {{ notification.content }}
  52.               {{ formatTime(notification.createTime) }}
  53.             
  54.             
  55.             
  56.               <el-button
  57.                 v-if="!notification.read"
  58.                 text
  59.                 size="small"
  60.                 @click.stop="handleMarkRead(notification.id)"
  61.               >
  62.                 标记已读
  63.               </el-button>
  64.               <el-button
  65.                 text
  66.                 size="small"
  67.                 @click.stop="handleDelete(notification.id)"
  68.               >
  69.                 删除
  70.               </el-button>
  71.             
  72.          
  73.          
  74.           <el-empty
  75.             v-if="filteredNotifications.length === 0"
  76.             description="暂无通知"
  77.           />
  78.         
  79.       
  80.     </template>
  81.   </el-popover>
  82. </template>
复制代码
使用示例
  1. <template>
  2.   <header>
  3.    
  4.       
  5.       <NotificationCenter />
  6.    
  7.   </header>
  8. </template>
复制代码
浏览器通知
  1. // 在需要时请求权限并发送通知
  2. async function sendBrowserNotification(title: string, options?: NotificationOptions) {
  3.   if ('Notification' in window && Notification.permission === 'granted') {
  4.     new Notification(title, {
  5.       icon: '/logo.png',
  6.       badge: '/badge.png',
  7.       ...options
  8.     })
  9.   }
  10. }
复制代码
💡 进阶功能

  • 接入 WebSocket 实现及时推送
  • 添加通知免打扰模式
  • 支持通知折叠和展开
如果对您有所资助,欢迎您点个关注,我会定时更新技能文档,大家一起讨论学习,一起进步。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表