'use client'; import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { XMarkIcon, ExclamationTriangleIcon, CheckCircleIcon, InformationCircleIcon } from '@heroicons/react/24/outline'; export type ErrorType = 'error' | 'warning' | 'success' | 'info'; interface ErrorNotificationProps { message: string; type?: ErrorType; duration?: number; onClose?: () => void; } export function ErrorNotification({ message, type = 'error', duration = 5000, onClose }: ErrorNotificationProps) { const [isVisible, setIsVisible] = useState(true); useEffect(() => { if (duration > 0) { const timer = setTimeout(() => { setIsVisible(false); onClose?.(); }, duration); return () => clearTimeout(timer); } }, [duration, onClose]); const handleClose = () => { setIsVisible(false); onClose?.(); }; const getIcon = () => { switch (type) { case 'error': return ; case 'warning': return ; case 'success': return ; case 'info': return ; } }; const getStyles = () => { switch (type) { case 'error': return { bg: 'bg-red-50 dark:bg-red-900/20', border: 'border-red-200 dark:border-red-800', text: 'text-red-800 dark:text-red-200', icon: 'text-red-500', close: 'text-red-400 hover:text-red-600 dark:text-red-300 dark:hover:text-red-100' }; case 'warning': return { bg: 'bg-yellow-50 dark:bg-yellow-900/20', border: 'border-yellow-200 dark:border-yellow-800', text: 'text-yellow-800 dark:text-yellow-200', icon: 'text-yellow-500', close: 'text-yellow-400 hover:text-yellow-600 dark:text-yellow-300 dark:hover:text-yellow-100' }; case 'success': return { bg: 'bg-green-50 dark:bg-green-900/20', border: 'border-green-200 dark:border-green-800', text: 'text-green-800 dark:text-green-200', icon: 'text-green-500', close: 'text-green-400 hover:text-green-600 dark:text-green-300 dark:hover:text-green-100' }; case 'info': return { bg: 'bg-blue-50 dark:bg-blue-900/20', border: 'border-blue-200 dark:border-blue-800', text: 'text-blue-800 dark:text-blue-200', icon: 'text-blue-500', close: 'text-blue-400 hover:text-blue-600 dark:text-blue-300 dark:hover:text-blue-100' }; } }; const styles = getStyles(); return ( {isVisible && (
{getIcon()}

{message}

)}
); } // 全局错误管理器 class ErrorManager { private static instance: ErrorManager; private listeners: Array<(notification: ErrorNotificationProps & { id: string }) => void> = []; static getInstance(): ErrorManager { if (!ErrorManager.instance) { ErrorManager.instance = new ErrorManager(); } return ErrorManager.instance; } showError(message: string, duration?: number) { this.showNotification(message, 'error', duration); } showWarning(message: string, duration?: number) { this.showNotification(message, 'warning', duration); } showSuccess(message: string, duration?: number) { this.showNotification(message, 'success', duration); } showInfo(message: string, duration?: number) { this.showNotification(message, 'info', duration); } private showNotification(message: string, type: ErrorType, duration?: number) { const notification = { id: Math.random().toString(36).substr(2, 9), message, type, duration: duration ?? (type === 'error' ? 5000 : 3000) }; this.listeners.forEach(listener => listener(notification)); } subscribe(listener: (notification: ErrorNotificationProps & { id: string }) => void) { this.listeners.push(listener); return () => { this.listeners = this.listeners.filter(l => l !== listener); }; } } export const errorManager = ErrorManager.getInstance(); // 错误提示容器组件 export function ErrorNotificationContainer() { const [notifications, setNotifications] = useState>([]); useEffect(() => { const unsubscribe = errorManager.subscribe((notification) => { setNotifications(prev => [...prev, notification]); }); return unsubscribe; }, []); const removeNotification = (id: string) => { setNotifications(prev => prev.filter(n => n.id !== id)); }; return ( <> {notifications.map((notification) => ( removeNotification(notification.id)} /> ))} ); }