"use client" import { useEffect, useState, useCallback } from 'react' import { Bell, X, CheckCircle, AlertCircle, Clock, Wallet, Gift, Info } from 'lucide-react' import { useStore } from '@/lib/store' // 消息类型 interface NotificationMessage { messageId: string type: string data: { message?: string title?: string content?: string amount?: number commission?: number daysRemaining?: number [key: string]: unknown } timestamp: string } interface RealtimeNotificationProps { onNewMessage?: (message: NotificationMessage) => void } export function RealtimeNotification({ onNewMessage }: RealtimeNotificationProps) { const { user, isLoggedIn } = useStore() const [notifications, setNotifications] = useState([]) const [unreadCount, setUnreadCount] = useState(0) const [showPanel, setShowPanel] = useState(false) const [lastTimestamp, setLastTimestamp] = useState(new Date().toISOString()) // 获取消息 const fetchMessages = useCallback(async () => { if (!isLoggedIn || !user?.id) return try { const response = await fetch( `/api/distribution/messages?userId=${user.id}&since=${encodeURIComponent(lastTimestamp)}` ) if (!response.ok) return const data = await response.json() if (data.success && data.messages?.length > 0) { setNotifications(prev => { const newMessages = data.messages.filter( (m: NotificationMessage) => !prev.some(p => p.messageId === m.messageId) ) if (newMessages.length > 0) { // 更新未读数 setUnreadCount(c => c + newMessages.length) // 显示Toast通知 newMessages.forEach((msg: NotificationMessage) => { showToast(msg) onNewMessage?.(msg) }) // 更新最后时间戳 const latestTime = newMessages.reduce( (max: string, m: NotificationMessage) => m.timestamp > max ? m.timestamp : max, lastTimestamp ) setLastTimestamp(latestTime) return [...newMessages, ...prev].slice(0, 50) // 保留最近50条 } return prev }) } } catch (error) { console.error('[RealtimeNotification] 获取消息失败:', error) } }, [isLoggedIn, user?.id, lastTimestamp, onNewMessage]) // 轮询获取消息 useEffect(() => { if (!isLoggedIn || !user?.id) return // 立即获取一次 fetchMessages() // 每5秒轮询一次 const intervalId = setInterval(fetchMessages, 5000) return () => clearInterval(intervalId) }, [isLoggedIn, user?.id, fetchMessages]) // 显示Toast通知 const showToast = (message: NotificationMessage) => { // 创建Toast元素 const toast = document.createElement('div') toast.className = 'fixed top-20 right-4 z-[100] animate-in slide-in-from-right duration-300' toast.innerHTML = `
${getIconSvg(message.type)}

${getTitle(message.type)}

${message.data.message || message.data.content || ''}

` document.body.appendChild(toast) // 3秒后移除 setTimeout(() => { toast.classList.add('animate-out', 'slide-out-to-right') setTimeout(() => toast.remove(), 300) }, 3000) } // 获取图标背景色 const getIconBgClass = (type: string): string => { switch (type) { case 'binding_expiring': return 'bg-orange-500/20' case 'binding_expired': return 'bg-red-500/20' case 'binding_converted': case 'earnings_added': return 'bg-green-500/20' case 'withdrawal_completed': return 'bg-[#38bdac]/20' case 'withdrawal_rejected': return 'bg-red-500/20' default: return 'bg-blue-500/20' } } // 获取图标SVG const getIconSvg = (type: string): string => { switch (type) { case 'binding_expiring': return '' case 'binding_expired': return '' case 'binding_converted': case 'earnings_added': return '' case 'withdrawal_completed': return '' case 'withdrawal_rejected': return '' default: return '' } } // 获取标题 const getTitle = (type: string): string => { switch (type) { case 'binding_expiring': return '绑定即将过期' case 'binding_expired': return '绑定已过期' case 'binding_converted': return '用户已付款' case 'earnings_added': return '收益增加' case 'withdrawal_approved': return '提现已通过' case 'withdrawal_completed': return '提现已到账' case 'withdrawal_rejected': return '提现被拒绝' case 'system_notice': return '系统通知' default: return '消息通知' } } // 获取图标组件 const getIcon = (type: string) => { switch (type) { case 'binding_expiring': return case 'binding_expired': return case 'binding_converted': case 'earnings_added': return case 'withdrawal_completed': return case 'withdrawal_rejected': return default: return } } // 标记消息已读 const markAsRead = async () => { if (!user?.id || notifications.length === 0) return const messageIds = notifications.slice(0, 10).map(n => n.messageId) try { await fetch('/api/distribution/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: user.id, messageIds }), }) setUnreadCount(0) } catch (error) { console.error('[RealtimeNotification] 标记已读失败:', error) } } // 打开面板时标记已读 const handleOpenPanel = () => { setShowPanel(true) if (unreadCount > 0) { markAsRead() } } if (!isLoggedIn || !user) return null return ( <> {/* 通知铃铛按钮 */} {/* 通知面板 */} {showPanel && (
setShowPanel(false)} />
{/* Header */}

消息通知

{/* 消息列表 */}
{notifications.length === 0 ? (

暂无消息

) : (
{notifications.map((notification) => (
{getIcon(notification.type)}

{getTitle(notification.type)}

{notification.data.message || notification.data.content || ''}

{new Date(notification.timestamp).toLocaleString('zh-CN')}

))}
)}
)} ) }