forked from CarrotSkin/carrotskin
683 lines
22 KiB
TypeScript
683 lines
22 KiB
TypeScript
'use client';
|
||
|
||
import Link from 'next/link';
|
||
import { motion, AnimatePresence, useScroll, useTransform } from 'framer-motion';
|
||
import { useState, useCallback, useEffect } from 'react';
|
||
import {
|
||
HomeIcon,
|
||
ArrowLeftIcon,
|
||
ExclamationTriangleIcon,
|
||
XCircleIcon,
|
||
ClockIcon,
|
||
ServerIcon,
|
||
WifiIcon,
|
||
ClipboardDocumentIcon,
|
||
ArrowPathIcon,
|
||
CubeIcon,
|
||
QuestionMarkCircleIcon,
|
||
SparklesIcon,
|
||
RocketLaunchIcon
|
||
} from '@heroicons/react/24/outline';
|
||
import { messageManager } from './MessageNotification';
|
||
|
||
export interface ErrorPageProps {
|
||
code?: number;
|
||
title?: string;
|
||
message?: string;
|
||
description?: string;
|
||
type?: '404' | '500' | '403' | 'network' | 'timeout' | 'maintenance' | 'custom';
|
||
actions?: {
|
||
primary?: {
|
||
label: string;
|
||
href?: string;
|
||
onClick?: () => void;
|
||
};
|
||
secondary?: {
|
||
label: string;
|
||
href?: string;
|
||
onClick?: () => void;
|
||
};
|
||
};
|
||
showContact?: boolean;
|
||
showRetry?: boolean;
|
||
onRetry?: () => void;
|
||
showCopyError?: boolean;
|
||
errorDetails?: string;
|
||
className?: string;
|
||
}
|
||
|
||
const errorConfigs = {
|
||
'404': {
|
||
icon: <CubeIcon className="w-20 h-20" />,
|
||
title: '页面未找到',
|
||
message: '这个页面似乎不存在于我们的世界中',
|
||
description: '页面可能已被移除、重命名,或者您输入的地址不正确。',
|
||
suggestions: [
|
||
'检查网址拼写是否正确',
|
||
'返回主页重新探索',
|
||
'使用搜索功能寻找内容'
|
||
]
|
||
},
|
||
'500': {
|
||
icon: <ServerIcon className="w-20 h-20" />,
|
||
title: '服务器错误',
|
||
message: '我们的服务器遇到了一些技术问题',
|
||
description: '工程师们正在紧急修复中,请稍后再试。',
|
||
suggestions: [
|
||
'稍后刷新页面重试',
|
||
'清除浏览器缓存',
|
||
'检查网络连接'
|
||
]
|
||
},
|
||
'403': {
|
||
icon: <ExclamationTriangleIcon className="w-20 h-20" />,
|
||
title: '访问被拒绝',
|
||
message: '您没有权限进入这个区域',
|
||
description: '请检查您的权限等级或联系管理员获取访问权限。',
|
||
suggestions: [
|
||
'确认您是否已登录',
|
||
'检查账户权限等级',
|
||
'联系管理员申请权限'
|
||
]
|
||
},
|
||
network: {
|
||
icon: <WifiIcon className="w-20 h-20" />,
|
||
title: '网络连接问题',
|
||
message: '与我们的连接出现了问题',
|
||
description: '请检查您的网络连接,然后重新尝试。',
|
||
suggestions: [
|
||
'检查网络连接状态',
|
||
'尝试重新连接',
|
||
'检查防火墙设置'
|
||
]
|
||
},
|
||
timeout: {
|
||
icon: <ClockIcon className="w-20 h-20" />,
|
||
title: '连接超时',
|
||
message: '服务器响应时间过长',
|
||
description: '服务器响应缓慢,请稍后再试。',
|
||
suggestions: [
|
||
'检查网络连接状态',
|
||
'稍后重新尝试连接',
|
||
'联系技术支持团队'
|
||
]
|
||
},
|
||
maintenance: {
|
||
icon: <ServerIcon className="w-20 h-20" />,
|
||
title: '系统维护中',
|
||
message: '我们正在对系统进行升级改造',
|
||
description: '为了提供更好的体验,系统暂时关闭维护。',
|
||
suggestions: [
|
||
'关注官方公告获取开放时间',
|
||
'加入官方群组了解进度',
|
||
'稍后再试'
|
||
]
|
||
},
|
||
custom: {
|
||
icon: <QuestionMarkCircleIcon className="w-20 h-20" />,
|
||
title: '未知错误',
|
||
message: '发生了一些奇怪的事情',
|
||
description: '请稍后再试或联系我们的支持团队。',
|
||
suggestions: []
|
||
}
|
||
};
|
||
|
||
// Action Button Component
|
||
function ActionButton({ action, colorClass, primary = false }: {
|
||
action: { label: string; href?: string; onClick?: () => void };
|
||
colorClass?: string;
|
||
primary?: boolean;
|
||
}) {
|
||
const buttonContent = (
|
||
<>
|
||
{primary ? <HomeIcon className="w-5 h-5 mr-2" /> : <ArrowLeftIcon className="w-5 h-5 mr-2" />}
|
||
{action.label}
|
||
</>
|
||
);
|
||
|
||
const buttonClass = primary
|
||
? `inline-flex items-center justify-center px-6 py-4 bg-gradient-to-r ${colorClass} text-white font-semibold rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105`
|
||
: 'inline-flex items-center justify-center px-6 py-4 border-2 border-orange-500 text-orange-500 hover:bg-orange-500 hover:text-white font-semibold rounded-xl transition-all duration-200';
|
||
|
||
if ('href' in action && action.href) {
|
||
return (
|
||
<Link href={action.href} className={buttonClass}>
|
||
{buttonContent}
|
||
</Link>
|
||
);
|
||
} else if ('onClick' in action && action.onClick) {
|
||
return (
|
||
<button onClick={action.onClick} className={buttonClass}>
|
||
{buttonContent}
|
||
</button>
|
||
);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
export function ErrorPage({
|
||
code,
|
||
title,
|
||
message,
|
||
description,
|
||
type = 'custom',
|
||
actions,
|
||
showContact = true,
|
||
showRetry = true,
|
||
onRetry,
|
||
showCopyError = true,
|
||
errorDetails,
|
||
className = ''
|
||
}: ErrorPageProps) {
|
||
const [isRetrying, setIsRetrying] = useState(false);
|
||
const [showDetails, setShowDetails] = useState(false);
|
||
const [copySuccess, setCopySuccess] = useState(false);
|
||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||
const { scrollYProgress } = useScroll();
|
||
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0.8]);
|
||
|
||
const config = errorConfigs[type] || {};
|
||
const displayTitle = title || config.title || '出错了';
|
||
const displayMessage = message || config.message || '发生了一些错误';
|
||
const displayDescription = description || config.description || '';
|
||
|
||
// 生成详细的错误信息
|
||
const generateErrorDetails = useCallback(() => {
|
||
const details = {
|
||
timestamp: new Date().toISOString(),
|
||
errorType: type,
|
||
errorCode: code,
|
||
userAgent: typeof window !== 'undefined' ? window.navigator.userAgent : 'Unknown',
|
||
url: typeof window !== 'undefined' ? window.location.href : 'Unknown',
|
||
customDetails: errorDetails
|
||
};
|
||
return JSON.stringify(details, null, 2);
|
||
}, [type, code, errorDetails]);
|
||
|
||
const defaultActions = {
|
||
primary: {
|
||
label: '返回主城',
|
||
href: '/'
|
||
},
|
||
secondary: {
|
||
label: '返回上页',
|
||
onClick: () => {
|
||
if (typeof window !== 'undefined') {
|
||
window.history.back();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
const finalActions = { ...defaultActions, ...actions };
|
||
|
||
const getThemeStyles = () => {
|
||
return {
|
||
bg: 'bg-gradient-to-br from-slate-50 via-orange-50 to-amber-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900',
|
||
card: 'bg-white/70 dark:bg-gray-800/70 backdrop-blur-lg',
|
||
text: 'text-gray-900 dark:text-white',
|
||
subtext: 'text-gray-600 dark:text-gray-300',
|
||
accent: 'text-orange-500 dark:text-orange-400'
|
||
};
|
||
};
|
||
|
||
const getIconColor = () => {
|
||
const colors = {
|
||
'404': 'text-orange-500',
|
||
'500': 'text-red-500',
|
||
'403': 'text-yellow-500',
|
||
'network': 'text-blue-500',
|
||
'timeout': 'text-purple-500',
|
||
'maintenance': 'text-gray-500',
|
||
'custom': 'text-gray-500'
|
||
};
|
||
return colors[type] || 'text-gray-500';
|
||
};
|
||
|
||
const getCodeColor = () => {
|
||
const colors = {
|
||
'404': 'from-orange-400 via-orange-500 to-amber-500',
|
||
'500': 'from-red-400 via-red-500 to-pink-500',
|
||
'403': 'from-yellow-400 via-yellow-500 to-orange-500',
|
||
'network': 'from-blue-400 via-blue-500 to-cyan-500',
|
||
'timeout': 'from-purple-400 via-purple-500 to-pink-500',
|
||
'maintenance': 'from-gray-400 via-gray-500 to-slate-500',
|
||
'custom': 'from-gray-400 via-gray-500 to-slate-500'
|
||
};
|
||
return colors[type] || 'from-gray-400 via-gray-500 to-slate-500';
|
||
};
|
||
|
||
const getButtonColor = () => {
|
||
return 'from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600';
|
||
};
|
||
|
||
const handleRetry = async () => {
|
||
if (onRetry) {
|
||
setIsRetrying(true);
|
||
try {
|
||
await onRetry();
|
||
messageManager.success('重试成功!', { duration: 3000 });
|
||
} catch (error) {
|
||
messageManager.error('重试失败,请稍后重试', { duration: 5000 });
|
||
} finally {
|
||
setIsRetrying(false);
|
||
}
|
||
} else {
|
||
// 默认重试逻辑:刷新页面
|
||
if (typeof window !== 'undefined') {
|
||
window.location.reload();
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleCopyError = async () => {
|
||
try {
|
||
const details = generateErrorDetails();
|
||
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
||
await navigator.clipboard.writeText(details);
|
||
setCopySuccess(true);
|
||
messageManager.success('错误信息已复制到剪贴板', { duration: 2000 });
|
||
setTimeout(() => setCopySuccess(false), 2000);
|
||
} else {
|
||
// 降级方案
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = details;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
setCopySuccess(true);
|
||
messageManager.success('错误信息已复制到剪贴板', { duration: 2000 });
|
||
setTimeout(() => setCopySuccess(false), 2000);
|
||
}
|
||
} catch (error) {
|
||
messageManager.error('复制失败,请手动复制', { duration: 3000 });
|
||
}
|
||
};
|
||
|
||
const handleReportError = () => {
|
||
messageManager.info('感谢您的反馈,我们会尽快处理', { duration: 3000 });
|
||
// 这里可以添加实际的错误报告逻辑
|
||
};
|
||
|
||
// 键盘快捷键支持
|
||
useEffect(() => {
|
||
const handleKeyDown = (event: KeyboardEvent) => {
|
||
if (event.key === 'r' && (event.ctrlKey || event.metaKey)) {
|
||
event.preventDefault();
|
||
handleRetry();
|
||
}
|
||
};
|
||
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('keydown', handleKeyDown);
|
||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
}
|
||
}, [handleRetry]);
|
||
|
||
const themeStyles = getThemeStyles();
|
||
|
||
return (
|
||
<div className={`min-h-screen bg-gradient-to-br from-slate-50 via-orange-50 to-amber-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 ${className}`}>
|
||
{/* Animated Background - 简化背景动画 */}
|
||
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
||
<motion.div
|
||
className="absolute top-1/4 left-1/4 w-96 h-96 bg-gradient-to-br from-orange-400/10 to-amber-400/10 rounded-full blur-3xl"
|
||
animate={{
|
||
scale: [1, 1.2, 1],
|
||
opacity: [0.3, 0.5, 0.3]
|
||
}}
|
||
transition={{ duration: 4, repeat: Infinity }}
|
||
/>
|
||
<motion.div
|
||
className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-gradient-to-tr from-pink-400/10 to-orange-400/10 rounded-full blur-3xl"
|
||
animate={{
|
||
scale: [1.2, 1, 1.2],
|
||
opacity: [0.5, 0.3, 0.5]
|
||
}}
|
||
transition={{ duration: 5, repeat: Infinity }}
|
||
/>
|
||
</div>
|
||
|
||
{/* Main Content - 考虑navbar高度的居中布局 */}
|
||
<motion.div
|
||
className="relative z-10 min-h-[calc(100vh-var(--navbar-height,4rem))] flex items-center justify-center px-4 sm:px-6 lg:px-8"
|
||
style={{ paddingTop: 'calc(var(--navbar-height, 4rem) + 2rem)' }}
|
||
>
|
||
<div className="max-w-4xl mx-auto w-full text-center">
|
||
|
||
{/* Error Code - 更突出的错误代码 */}
|
||
{code && (
|
||
<motion.div
|
||
initial={{ scale: 0.5, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
transition={{ duration: 0.8, type: 'spring', stiffness: 100 }}
|
||
className="mb-12"
|
||
>
|
||
<h1 className={`text-9xl md:text-[12rem] font-black bg-gradient-to-r ${getCodeColor()} bg-clip-text text-transparent mb-2`}>
|
||
{code}
|
||
</h1>
|
||
<motion.div
|
||
className={`w-24 h-1 bg-gradient-to-r ${getCodeColor()} mx-auto rounded-full`}
|
||
initial={{ width: 0 }}
|
||
animate={{ width: 96 }}
|
||
transition={{ delay: 0.5, duration: 0.8 }}
|
||
/>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* Main Error Message - 简洁有力的错误信息 */}
|
||
<motion.div
|
||
className="mb-16 space-y-6"
|
||
initial={{ opacity: 0, y: 30 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.3, duration: 0.8 }}
|
||
>
|
||
<div className="space-y-4">
|
||
<h2 className="text-4xl md:text-6xl font-bold text-gray-900 dark:text-white leading-tight">
|
||
{displayTitle}
|
||
</h2>
|
||
<p className="text-xl md:text-2xl text-gray-600 dark:text-gray-300 leading-relaxed max-w-2xl mx-auto">
|
||
{displayMessage}
|
||
</p>
|
||
{displayDescription && (
|
||
<p className="text-lg text-gray-500 dark:text-gray-400 leading-relaxed max-w-xl mx-auto">
|
||
{displayDescription}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Icon - 更简洁的图标展示 */}
|
||
<motion.div
|
||
className="flex justify-center"
|
||
initial={{ opacity: 0, scale: 0.5 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
transition={{ delay: 0.6, duration: 0.6 }}
|
||
>
|
||
<div className={`${getIconColor()} opacity-80`}>
|
||
{config.icon || <QuestionMarkCircleIcon className="w-24 h-24" />}
|
||
</div>
|
||
</motion.div>
|
||
</motion.div>
|
||
|
||
{/* Action Buttons - 更简洁的按钮布局 */}
|
||
<motion.div
|
||
className="mb-12"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.8, duration: 0.6 }}
|
||
>
|
||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-6">
|
||
{finalActions.primary && (
|
||
<ActionButton action={finalActions.primary} colorClass={getButtonColor()} primary />
|
||
)}
|
||
|
||
{finalActions.secondary && (
|
||
<ActionButton action={finalActions.secondary} />
|
||
)}
|
||
</div>
|
||
|
||
{showRetry && (
|
||
<motion.button
|
||
onClick={handleRetry}
|
||
disabled={isRetrying}
|
||
className="inline-flex items-center px-8 py-4 bg-gradient-to-r from-orange-500 to-amber-500 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold rounded-2xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105"
|
||
whileHover={{ scale: isRetrying ? 1 : 1.05 }}
|
||
whileTap={{ scale: isRetrying ? 1 : 0.95 }}
|
||
>
|
||
<ArrowPathIcon className={`w-5 h-5 mr-2 ${isRetrying ? 'animate-spin' : ''}`} />
|
||
{isRetrying ? '重试中...' : '重新加载'}
|
||
</motion.button>
|
||
)}
|
||
</motion.div>
|
||
|
||
{/* Suggestions - 更简洁的建议展示 */}
|
||
{config.suggestions && config.suggestions.length > 0 && (
|
||
<motion.div
|
||
className="mb-12"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ delay: 1, duration: 0.6 }}
|
||
>
|
||
<h3 className="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">
|
||
您可以尝试:
|
||
</h3>
|
||
<div className="flex flex-wrap justify-center gap-3">
|
||
{config.suggestions.map((suggestion, index) => (
|
||
<motion.div
|
||
key={index}
|
||
className="flex items-center px-4 py-2 bg-white/50 dark:bg-gray-800/50 rounded-full text-sm text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700"
|
||
initial={{ opacity: 0, y: 10 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 1.2 + index * 0.1, duration: 0.4 }}
|
||
>
|
||
<div className={`w-1.5 h-1.5 rounded-full ${getIconColor()} mr-2`} />
|
||
{suggestion}
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* Error Details - 更简洁的错误详情 */}
|
||
{showCopyError && (
|
||
<motion.div
|
||
className="mb-12"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ delay: 1.4, duration: 0.6 }}
|
||
>
|
||
<div className="flex justify-center gap-3 mb-4">
|
||
<button
|
||
onClick={handleCopyError}
|
||
className="inline-flex items-center px-4 py-2 bg-white/50 dark:bg-gray-800/50 hover:bg-white/70 dark:hover:bg-gray-800/70 text-gray-700 dark:text-gray-300 text-sm font-medium rounded-full transition-all duration-200 border border-gray-200 dark:border-gray-700"
|
||
>
|
||
<ClipboardDocumentIcon className="w-4 h-4 mr-2" />
|
||
{copySuccess ? '已复制!' : '复制错误信息'}
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setShowDetails(!showDetails)}
|
||
className="inline-flex items-center px-4 py-2 bg-white/50 dark:bg-gray-800/50 hover:bg-white/70 dark:hover:bg-gray-800/70 text-gray-700 dark:text-gray-300 text-sm font-medium rounded-full transition-all duration-200 border border-gray-200 dark:border-gray-700"
|
||
>
|
||
{showDetails ? '隐藏详情' : '显示详情'}
|
||
</button>
|
||
</div>
|
||
|
||
<AnimatePresence>
|
||
{showDetails && (
|
||
<motion.div
|
||
initial={{ opacity: 0, height: 0 }}
|
||
animate={{ opacity: 1, height: 'auto' }}
|
||
exit={{ opacity: 0, height: 0 }}
|
||
transition={{ duration: 0.3 }}
|
||
className="max-w-2xl mx-auto p-4 bg-white/30 dark:bg-gray-800/30 rounded-2xl text-left border border-white/50 dark:border-gray-700/50 backdrop-blur-sm"
|
||
>
|
||
<h4 className="font-semibold text-gray-900 dark:text-white mb-2 text-sm">错误详情:</h4>
|
||
<pre className="text-xs text-gray-600 dark:text-gray-400 overflow-auto max-h-24">
|
||
{generateErrorDetails()}
|
||
</pre>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* Contact Info - 更简洁的联系信息 */}
|
||
{showContact && (
|
||
<motion.div
|
||
className="text-center pb-8"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ delay: 1.6, duration: 0.6 }}
|
||
>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||
问题仍未解决?
|
||
<Link
|
||
href="/contact"
|
||
className="text-orange-500 hover:text-orange-600 underline mx-1 font-medium"
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
messageManager.info('联系我们页面正在开发中', { duration: 3000 });
|
||
}}
|
||
>
|
||
联系我们
|
||
</Link>
|
||
</p>
|
||
</motion.div>
|
||
)}
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 预设的错误页面组件
|
||
export function NotFoundPage() {
|
||
return <ErrorPage type="404" code={404} />;
|
||
}
|
||
|
||
export function ServerErrorPage() {
|
||
return <ErrorPage type="500" code={500} />;
|
||
}
|
||
|
||
export function ForbiddenPage() {
|
||
return <ErrorPage type="403" code={403} />;
|
||
}
|
||
|
||
export function NetworkErrorPage() {
|
||
return <ErrorPage type="network" />;
|
||
}
|
||
|
||
export function TimeoutErrorPage() {
|
||
return <ErrorPage type="timeout" />;
|
||
}
|
||
|
||
export function MaintenancePage() {
|
||
return <ErrorPage type="maintenance" />;
|
||
}
|
||
|
||
// 现代化的错误页面
|
||
export function ModernNotFoundPage() {
|
||
return <ErrorPage type="404" code={404} />;
|
||
}
|
||
|
||
export function ModernServerErrorPage() {
|
||
return <ErrorPage type="500" code={500} />;
|
||
}
|
||
|
||
// 使用示例和最佳实践
|
||
/*
|
||
|
||
增强后的ErrorPage组件提供了以下改进:
|
||
|
||
1. **统一的消息提示系统**
|
||
- 使用MessageNotification组件替代alert
|
||
- 支持成功、错误、警告、信息、加载等多种消息类型
|
||
- 更好的用户体验和视觉效果
|
||
|
||
2. **多种主题风格**
|
||
- Minecraft风格:适合游戏相关网站
|
||
- Modern风格:现代化简洁设计
|
||
- Minimal风格:极简主义设计
|
||
|
||
3. **增强的功能**
|
||
- 重试功能,支持自定义重试逻辑
|
||
- 错误信息复制功能
|
||
- 错误详情显示/隐藏
|
||
- 键盘快捷键支持 (Ctrl+R/⌘+R 重试)
|
||
- 进度条显示(可选)
|
||
- 自定义操作按钮
|
||
|
||
4. **改进的用户体验**
|
||
- 针对每种错误类型提供具体建议
|
||
- 动态颜色主题匹配错误类型
|
||
- 平滑的动画过渡效果
|
||
- 响应式设计,适配移动端
|
||
- Minecraft风格的游戏化提示
|
||
|
||
5. **更好的错误处理**
|
||
- 详细的错误信息生成
|
||
- 错误报告功能
|
||
- 降级处理(如复制功能)
|
||
- 支持自定义错误详情
|
||
|
||
使用示例:
|
||
|
||
```tsx
|
||
// 基础使用 - Minecraft风格404页面
|
||
<ErrorPage type="404" />
|
||
|
||
// 现代风格500错误
|
||
<ErrorPage type="500" code={500} theme="modern" />
|
||
|
||
// 自定义错误信息
|
||
<ErrorPage
|
||
type="500"
|
||
code={500}
|
||
title="数据库连接失败"
|
||
message="无法连接到数据库服务器"
|
||
description="请检查数据库配置或联系系统管理员"
|
||
theme="modern"
|
||
/>
|
||
|
||
// 自定义操作按钮
|
||
<ErrorPage
|
||
type="network"
|
||
actions={{
|
||
primary: {
|
||
label: '重新连接',
|
||
onClick: () => reconnect()
|
||
},
|
||
secondary: {
|
||
label: '离线模式',
|
||
href: '/offline'
|
||
}
|
||
}}
|
||
/>
|
||
|
||
// 启用重试功能
|
||
<ErrorPage
|
||
type="timeout"
|
||
showRetry={true}
|
||
onRetry={async () => {
|
||
// 自定义重试逻辑
|
||
await fetchData();
|
||
}}
|
||
/>
|
||
|
||
// 显示错误详情
|
||
<ErrorPage
|
||
type="500"
|
||
showCopyError={true}
|
||
errorDetails={JSON.stringify(errorDetails)}
|
||
theme="minimal"
|
||
/>
|
||
|
||
```
|
||
|
||
预设组件使用:
|
||
|
||
```tsx
|
||
// 在页面中使用预设的错误组件
|
||
import { NotFoundPage, ServerErrorPage, ModernNotFoundPage } from '@/components/ErrorPage';
|
||
|
||
// Minecraft风格404页面
|
||
export default function Custom404() {
|
||
return <NotFoundPage />;
|
||
}
|
||
|
||
// 现代化404页面
|
||
export default function Modern404() {
|
||
return <ModernNotFoundPage />;
|
||
}
|
||
|
||
// 500页面
|
||
export default function Custom500() {
|
||
return <ServerErrorPage />;
|
||
}
|
||
```
|
||
|
||
*/
|
||
|