/** * ErrorPage - 通用错误页面组件 * 用于显示加载失败、网络错误、404等异常状态 * 设计风格:黑色背景 + 金色边框 */ import React from 'react'; import { Box, Text, Button, VStack, HStack, Collapse, useDisclosure, } from '@chakra-ui/react'; import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { trackEventAsync } from '@/lib/posthog'; // 主题色(保持原来的配色) const GOLD_COLOR = '#D4A574'; const BG_COLOR = '#1A202C'; // 错误原因项配置 export interface ErrorReasonItem { /** 图标(emoji 或自定义组件) */ icon: string | React.ReactNode; /** 原因标题 */ title: string; /** 原因描述 */ description: string; } // 操作按钮配置 export interface ActionButton { /** 按钮文本 */ label: string; /** 按钮图标(可选,放在文本前) */ icon?: string; /** 按钮类型:primary(主要)、secondary(次要)、outline(轮廓) */ variant?: 'primary' | 'secondary' | 'outline'; /** 点击回调 */ onClick?: () => void; /** 跳转链接(与 onClick 二选一) */ href?: string; } // 技术详情配置 export interface TechDetails { /** 请求 URL */ requestUrl?: string; /** 错误类型 */ errorType?: string; /** 错误信息 */ errorMessage?: string; /** 时间戳 */ timestamp?: string; /** 相关 ID */ relatedId?: string; /** 自定义字段 */ customFields?: Record; } // 完整的 ErrorPage 配置 export interface ErrorPageProps { // ===== 基础配置 ===== /** 错误标题 */ title?: string; /** 错误副标题(如显示错误 ID) */ subtitle?: string; /** 错误描述信息 */ description?: string; // ===== 详细信息 ===== /** 详细信息值 */ detail?: string; /** 详细信息标签 */ detailLabel?: string; // ===== 错误原因列表 ===== /** 错误原因列表 */ reasons?: ErrorReasonItem[]; // ===== 技术详情 ===== /** 技术详情(可展开查看) */ techDetails?: TechDetails; // ===== 操作按钮 ===== /** 自定义操作按钮列表 */ actions?: ActionButton[]; /** 快捷配置:是否显示重试按钮 */ showRetry?: boolean; /** 重试回调 */ onRetry?: () => void; /** 快捷配置:是否显示返回按钮 */ showBack?: boolean; /** 返回回调 */ onBack?: () => void; /** 快捷配置:是否显示返回首页按钮 */ showHome?: boolean; /** 首页路径 */ homePath?: string; // ===== 布局配置 ===== /** 是否全屏显示 */ fullScreen?: boolean; /** 最大宽度 */ maxWidth?: string; // ===== 网络状态 ===== /** 是否检查网络状态并显示离线提示 */ checkOffline?: boolean; // ===== 错误上报 ===== /** 是否启用内置 PostHog 错误上报(默认 true) */ enableBuiltInReport?: boolean; /** 自定义错误上报回调(可选,与内置上报同时生效) */ onErrorReport?: (errorInfo: Record) => void; } // 默认错误原因 const DEFAULT_REASONS: ErrorReasonItem[] = [ { icon: '🔍', title: 'ID 可能输入错误', description: '请检查 URL 中的 ID 是否正确', }, { icon: '🗑️', title: '内容可能已被删除', description: '该内容可能因过期或调整而被下架', }, { icon: '🔄', title: '系统暂时无法访问', description: '请稍后重试或联系技术支持', }, ]; const ErrorPage: React.FC = ({ title = '加载失败', subtitle, description = '我们无法找到您请求的内容,这可能是因为:', detail, detailLabel = 'ID', reasons = DEFAULT_REASONS, techDetails, actions, showRetry = false, onRetry, showBack = false, onBack, showHome = false, homePath = '/', fullScreen = true, maxWidth = '500px', checkOffline = true, enableBuiltInReport = true, onErrorReport, }) => { const navigate = useNavigate(); const { isOpen: isTechOpen, onToggle: onTechToggle } = useDisclosure(); const [isOffline, setIsOffline] = React.useState(!navigator.onLine); // 监听网络状态 React.useEffect(() => { if (!checkOffline) return; const handleOnline = () => setIsOffline(false); const handleOffline = () => setIsOffline(true); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, [checkOffline]); // 错误上报 React.useEffect(() => { const errorInfo = { error_title: title, error_detail: detail, error_type: techDetails?.errorType, error_message: techDetails?.errorMessage, page_url: window.location.href, referrer: document.referrer, user_agent: navigator.userAgent, event_id: techDetails?.relatedId, }; // 内置 PostHog 上报(异步,不阻塞渲染) if (enableBuiltInReport) { trackEventAsync('error_page_view', errorInfo); } // 自定义上报回调(保持兼容) if (onErrorReport) { onErrorReport({ ...errorInfo, timestamp: new Date().toISOString(), ...techDetails, }); } }, [enableBuiltInReport, onErrorReport, title, detail, techDetails]); // 构建操作按钮列表 const buildActionButtons = (): ActionButton[] => { if (actions) return actions; const buttons: ActionButton[] = []; if (showBack) { buttons.push({ label: '返回', variant: 'outline', onClick: onBack || (() => window.history.back()), }); } if (showRetry && onRetry) { buttons.push({ label: '重试', variant: 'primary', onClick: onRetry, }); } if (showHome) { buttons.push({ label: '返回首页', variant: 'outline', onClick: () => navigate(homePath), }); } return buttons; }; // 获取按钮样式(保持原来的金色风格) const getButtonStyle = (variant: ActionButton['variant']) => { switch (variant) { case 'primary': return { bg: GOLD_COLOR, color: BG_COLOR, border: '1px solid', borderColor: GOLD_COLOR, _hover: { bg: '#C49A6C' }, }; case 'outline': default: return { variant: 'outline' as const, borderColor: GOLD_COLOR, color: GOLD_COLOR, _hover: { bg: GOLD_COLOR, color: 'black' }, }; } }; const actionButtons = buildActionButtons(); const hasButtons = actionButtons.length > 0; return ( {/* 金色圆形感叹号图标 */} {/* 金色标题 */} {title} {/* 副标题(ID 显示) */} {(subtitle || detail) && ( {subtitle || `${detailLabel}: ${detail}`} )} {/* 离线提示 */} {checkOffline && isOffline && ( 当前处于离线状态,请检查网络连接 )} {/* 错误原因列表 */} {reasons.length > 0 && ( {description} {reasons.map((reason, index) => ( {typeof reason.icon === 'string' ? reason.icon : reason.icon} {reason.title} {reason.description} ))} )} {/* 技术详情(可展开) */} {techDetails && ( {techDetails.requestUrl && ( 请求URL: {techDetails.requestUrl} )} {techDetails.errorType && ( 错误类型: {techDetails.errorType} )} {techDetails.errorMessage && ( 错误信息: {techDetails.errorMessage} )} {techDetails.timestamp && ( 时间戳: {techDetails.timestamp} )} {techDetails.relatedId && ( 相关ID: {techDetails.relatedId} )} {techDetails.customFields && Object.entries(techDetails.customFields).map(([key, value]) => ( {key}: {value} ))} )} {/* 按钮组 */} {hasButtons && ( {actionButtons.map((btn, index) => ( ))} )} {/* 底部帮助提示 */} 点击右下角 联系客服 ); }; export default ErrorPage;