'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 && (
)}
);
}
// 全局错误管理器
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)}
/>
))}
>
);
}