notify.ts 3.12 KB
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { 
  getUnreadNotifications, 
  acknowledgeNotification, 
  acknowledgeAllNotifications, 
  streamNotifications, 
  getNotificationsPage,
  type NotificationDTO 
} from '@/api/notify'

export const useNotifyStore = defineStore('notify', () => {
  const unreadNotifications = ref<NotificationDTO[]>([])
  const unreadCount = ref(0)
  const isConnected = ref(false)
  let closeStream: (() => void) | null = null
  let reconnectTimer: any = null
  let reconnectCount = 0

  const fetchUnread = async () => {
    try {
      // 优化方案:调用分页接口获取真实的未读总数 (total)
      const { data: pageData } = await getNotificationsPage({ 
        read: false, 
        page: 0, 
        size: 1 
      })
      unreadCount.value = pageData.total || 0

      // 获取前 50 条消息用于界面列表展示
      const { data: listData } = await getUnreadNotifications(50)
      unreadNotifications.value = listData || []
    } catch (error) {
      console.error('Failed to fetch unread notifications:', error)
    }
  }

  const startListening = () => {
    if (closeStream) return

    const connect = () => {
      const { close } = streamNotifications({
        onOpen: () => {
          isConnected.value = true
          reconnectCount = 0
          fetchUnread()
          console.log('Notification stream connected')
        },
        onMessage: () => {
          fetchUnread()
        },
        onError: (err) => {
          console.error('Notification stream error:', err)
          isConnected.value = false
          
          // 指数退避重连逻辑
          if (reconnectTimer) clearTimeout(reconnectTimer)
          const delay = Math.min(30000, Math.pow(2, reconnectCount) * 1000)
          console.log(`Attempting to reconnect in ${delay}ms...`)
          
          reconnectTimer = setTimeout(() => {
            reconnectCount++
            startListening()
          }, delay)
        },
        onEnd: () => {
          isConnected.value = false
          closeStream = null
          console.log('Notification stream ended')
        }
      })
      closeStream = close
    }
    
    connect()
  }

  const stopListening = () => {
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
      reconnectTimer = null
    }
    if (closeStream) {
      closeStream()
      closeStream = null
    }
  }

  const markAsRead = async (id: number) => {
    try {
      await acknowledgeNotification(id)
      unreadNotifications.value = unreadNotifications.value.filter(n => n.id !== id)
      unreadCount.value = Math.max(0, unreadCount.value - 1)
    } catch (error) {
      console.error('Failed to mark notification as read:', error)
    }
  }

  const markAllAsRead = async () => {
    try {
      await acknowledgeAllNotifications()
      unreadNotifications.value = []
      unreadCount.value = 0
    } catch (error) {
      console.error('Failed to mark all notifications as read:', error)
    }
  }

  return {
    unreadNotifications,
    unreadCount,
    isConnected,
    fetchUnread,
    startListening,
    stopListening,
    markAsRead,
    markAllAsRead
  }
})