import { BellOutlined, CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, DislikeOutlined, ExclamationCircleOutlined, LikeOutlined, LockOutlined, } from "@ant-design/icons"; import { useEffect, useRef, useState } from "react"; import type { WebNotification, WebNotificationType, WebViewKey } from "../types"; import { AnimatedPanel } from "./AnimatedPanel"; const NOTIFICATION_ICONS: Record = { task_completed: , task_failed: , review_pending: , review_passed: , review_rejected: , credits_low: , session_expired: , info: , }; function parseTimestamp(dateStr: string): number { if (!dateStr) return Date.now(); let ts = new Date(dateStr).getTime(); if (Number.isNaN(ts)) { ts = new Date(dateStr.replace(" ", "T") + "Z").getTime(); } if (Number.isNaN(ts)) return Date.now(); return ts; } function timeAgo(dateStr: string, now: number): string { const ts = parseTimestamp(dateStr); const diff = now - ts; if (diff < 0) return "刚刚"; const seconds = Math.floor(diff / 1000); if (seconds < 60) return "刚刚"; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}分钟前`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}小时前`; const days = Math.floor(hours / 24); if (days < 30) return `${days}天前`; const date = new Date(ts); return `${date.getMonth() + 1}月${date.getDate()}日`; } interface NotificationCenterProps { items?: WebNotification[]; onNavigate: (view: WebViewKey, targetId?: string) => void; onMarkRead?: (id: string, isRead?: boolean) => void; onMarkAllRead?: () => void; onClear?: () => void; } function NotificationCenter({ items, onNavigate, onMarkRead, onMarkAllRead, onClear }: NotificationCenterProps) { const [readIds, setReadIds] = useState([]); const [open, setOpen] = useState(false); const [now, setNow] = useState(Date.now); const containerRef = useRef(null); const notifications = items ?? []; const unreadCount = notifications.filter((n) => !readIds.includes(n.id) && !n.isRead).length; useEffect(() => { if (!open) return; const timer = setInterval(() => setNow(Date.now()), 30_000); return () => clearInterval(timer); }, [open]); useEffect(() => { if (items && items.length === 0) { setReadIds([]); } }, [items]); useEffect(() => { if (!open) return; const handlePointerDown = (e: PointerEvent) => { if (!containerRef.current?.contains(e.target as Node)) { setOpen(false); } }; document.addEventListener("pointerdown", handlePointerDown); return () => document.removeEventListener("pointerdown", handlePointerDown); }, [open]); const markAllRead = () => { setReadIds((prev) => Array.from(new Set([...prev, ...notifications.map((n) => n.id)]))); onMarkAllRead?.(); }; const handleClickNotification = (n: WebNotification) => { setReadIds((prev) => (prev.includes(n.id) ? prev : [...prev, n.id])); onMarkRead?.(n.id, true); setOpen(false); if (n.targetView) { onNavigate(n.targetView, n.targetId); } }; return (
通知中心
{unreadCount > 0 && ( )} {notifications.length > 0 && onClear && ( )}
{notifications.length === 0 ? (
暂无通知
) : ( notifications.map((n) => ( )) )}
); } export default NotificationCenter;