Initial commit: CarrotSkin project setup

This commit is contained in:
2025-12-05 20:07:50 +08:00
parent a9ff72a9bf
commit f5e4c2a04b
24 changed files with 5389 additions and 2145 deletions

View File

@@ -1,7 +1,8 @@
'use client';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { motion, AnimatePresence, useScroll, useTransform } from 'framer-motion';
import { useState, useCallback, useEffect } from 'react';
import {
HomeIcon,
ArrowLeftIcon,
@@ -9,8 +10,15 @@ import {
XCircleIcon,
ClockIcon,
ServerIcon,
WifiIcon
WifiIcon,
ClipboardDocumentIcon,
ArrowPathIcon,
CubeIcon,
QuestionMarkCircleIcon,
SparklesIcon,
RocketLaunchIcon
} from '@heroicons/react/24/outline';
import { messageManager } from './MessageNotification';
export interface ErrorPageProps {
code?: number;
@@ -31,47 +39,122 @@ export interface ErrorPageProps {
};
};
showContact?: boolean;
showRetry?: boolean;
onRetry?: () => void;
showCopyError?: boolean;
errorDetails?: string;
className?: string;
}
const errorConfigs = {
'404': {
icon: <XCircleIcon className="w-16 h-16" />,
title: '页面不见了',
message: '抱歉,我们找不到您要访问的页面。',
description: '可能已被移动、删除,或者您输入的链接不正确。'
icon: <CubeIcon className="w-20 h-20" />,
title: '页面未找到',
message: '这个页面似乎不存在于我们的世界中',
description: '页面可能已被移除、重命名,或者您输入的地址不正确。',
suggestions: [
'检查网址拼写是否正确',
'返回主页重新探索',
'使用搜索功能寻找内容'
]
},
'500': {
icon: <ServerIcon className="w-16 h-16" />,
icon: <ServerIcon className="w-20 h-20" />,
title: '服务器错误',
message: '抱歉,服务器遇到了一些问题',
description: '我们的团队正在努力解决这个问题,请稍后再试。'
message: '我们的服务器遇到了一些技术问题',
description: '工程师们正在紧急修复中,请稍后再试。',
suggestions: [
'稍后刷新页面重试',
'清除浏览器缓存',
'检查网络连接'
]
},
'403': {
icon: <ExclamationTriangleIcon className="w-16 h-16" />,
icon: <ExclamationTriangleIcon className="w-20 h-20" />,
title: '访问被拒绝',
message: '抱歉,您没有权限访问此页面。',
description: '请检查您的账户权限或联系管理员。'
message: '您没有权限进入这个区域',
description: '请检查您的权限等级或联系管理员获取访问权限。',
suggestions: [
'确认您是否已登录',
'检查账户权限等级',
'联系管理员申请权限'
]
},
'network': {
icon: <WifiIcon className="w-16 h-16" />,
title: '网络连接错误',
message: '无法连接到服务器。',
description: '请检查您的网络连接,然后重试。'
network: {
icon: <WifiIcon className="w-20 h-20" />,
title: '网络连接问题',
message: '与我们的连接出现了问题',
description: '请检查您的网络连接,然后重新尝试。',
suggestions: [
'检查网络连接状态',
'尝试重新连接',
'检查防火墙设置'
]
},
'timeout': {
icon: <ClockIcon className="w-16 h-16" />,
title: '请求超时',
message: '请求处理时间过长',
description: '请刷新页面或稍后再试。'
timeout: {
icon: <ClockIcon className="w-20 h-20" />,
title: '连接超时',
message: '服务器响应时间过长',
description: '服务器响应缓慢,请稍后再试。',
suggestions: [
'检查网络连接状态',
'稍后重新尝试连接',
'联系技术支持团队'
]
},
'maintenance': {
icon: <ServerIcon className="w-16 h-16" />,
maintenance: {
icon: <ServerIcon className="w-20 h-20" />,
title: '系统维护中',
message: '我们正在进行系统维护。',
description: '请稍后再试,我们会尽快恢复服务。'
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,
@@ -79,176 +162,372 @@ export function ErrorPage({
description,
type = 'custom',
actions,
showContact = true
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: '返回主',
label: '返回主',
href: '/'
},
secondary: {
label: '返回上页',
onClick: () => window.history.back()
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 = () => {
switch (type) {
case '404': return 'text-orange-500';
case '500': return 'text-red-500';
case '403': return 'text-yellow-500';
case 'network': return 'text-blue-500';
case 'timeout': return 'text-purple-500';
case 'maintenance': return 'text-gray-500';
default: return 'text-orange-500';
}
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 = () => {
switch (type) {
case '404': return 'from-orange-400 via-orange-500 to-amber-500';
case '500': return 'from-red-400 via-red-500 to-pink-500';
case '403': return 'from-yellow-400 via-yellow-500 to-orange-500';
case 'network': return 'from-blue-400 via-blue-500 to-cyan-500';
case 'timeout': return 'from-purple-400 via-purple-500 to-pink-500';
case 'maintenance': return 'from-gray-400 via-gray-500 to-slate-500';
default: return 'from-orange-400 via-orange-500 to-amber-500';
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 flex items-center justify-center bg-gradient-to-br from-orange-50 via-white to-amber-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
<motion.div
className="text-center px-4 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: 'easeOut' }}
>
{/* 错误代码 */}
{code && (
<motion.div
className="mb-8"
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, duration: 0.6, type: 'spring', stiffness: 200 }}
>
<h1 className={`text-9xl font-black bg-gradient-to-r ${getCodeColor()} bg-clip-text text-transparent mb-4`}>
{code}
</h1>
<div className={`w-24 h-1 bg-gradient-to-r ${getCodeColor()} mx-auto rounded-full`} />
</motion.div>
)}
{/* 图标 */}
<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="mb-8 flex justify-center"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.4, duration: 0.6 }}
>
<div className={`${getIconColor()}`}>
{config.icon || <ExclamationTriangleIcon className="w-16 h-16" />}
</div>
</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="mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.6 }}
>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
{displayTitle}
</h2>
<p className="text-xl text-gray-700 dark:text-gray-300 mb-2">
{displayMessage}
</p>
{displayDescription && (
<p className="text-lg text-gray-600 dark:text-gray-400 leading-relaxed">
{displayDescription}
</p>
)}
</motion.div>
{/* 操作按钮 */}
<motion.div
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.6 }}
>
{finalActions.primary && (
finalActions.primary.href ? (
<Link
href={finalActions.primary.href}
className="inline-flex items-center px-6 py-3 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-semibold rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105"
>
<HomeIcon className="w-5 h-5 mr-2" />
{finalActions.primary.label}
</Link>
) : (
<button
onClick={finalActions.primary.onClick}
className="inline-flex items-center px-6 py-3 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-semibold rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105"
>
<HomeIcon className="w-5 h-5 mr-2" />
{finalActions.primary.label}
</button>
)
)}
{finalActions.secondary && (
finalActions.secondary.href ? (
<Link
href={finalActions.secondary.href}
className="inline-flex items-center px-6 py-3 border-2 border-orange-500 text-orange-500 hover:bg-orange-500 hover:text-white font-semibold rounded-xl transition-all duration-200"
>
<ArrowLeftIcon className="w-5 h-5 mr-2" />
{finalActions.secondary.label}
</Link>
) : (
<button
onClick={finalActions.secondary.onClick}
className="inline-flex items-center px-6 py-3 border-2 border-orange-500 text-orange-500 hover:bg-orange-500 hover:text-white font-semibold rounded-xl transition-all duration-200"
>
<ArrowLeftIcon className="w-5 h-5 mr-2" />
{finalActions.secondary.label}
</button>
)
)}
</motion.div>
{/* 联系信息 */}
{showContact && (
<motion.div
className="mt-8 text-sm text-gray-500 dark:text-gray-400"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1, duration: 0.6 }}
>
<p>
<Link href="/contact" className="text-orange-500 hover:text-orange-600 underline mx-1">
</Link>
</p>
</motion.div>
)}
</motion.div>
{/* 背景装饰 */}
<div className="fixed inset-0 -z-10 overflow-hidden">
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-orange-200/20 dark:bg-orange-900/20 rounded-full blur-3xl" />
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-amber-200/20 dark:bg-amber-900/20 rounded-full blur-3xl" />
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>
);
}
@@ -277,3 +556,127 @@ export function TimeoutErrorPage() {
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 />;
}
```
*/