import { Component } from 'react' import { useTranslation } from 'react-i18next' import type { ErrorInfo, ReactNode } from 'react' import { AlertTriangle, RefreshCw, Home, ChevronDown, ChevronUp, Copy, Check, Bug } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Alert, AlertDescription } from '@/components/ui/alert' import { ScrollArea } from '@/components/ui/scroll-area' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { useState } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error: Error | null errorInfo: ErrorInfo | null } // 解析堆栈信息为结构化数据 interface StackFrame { functionName: string fileName: string lineNumber: string columnNumber: string raw: string } function parseStackTrace(stack: string): StackFrame[] { const lines = stack.split('\n').slice(1) // 跳过第一行(错误消息) const frames: StackFrame[] = [] for (const line of lines) { const trimmed = line.trim() if (!trimmed.startsWith('at ')) continue // 匹配格式: at functionName (fileName:line:column) 或 at fileName:line:column const match = trimmed.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/) if (match) { frames.push({ functionName: match[1] || '', fileName: match[2], lineNumber: match[3], columnNumber: match[4], raw: trimmed, }) } else { frames.push({ functionName: '', fileName: '', lineNumber: '', columnNumber: '', raw: trimmed, }) } } return frames } // 错误详情展示组件(函数组件,用于使用 hooks) function ErrorDetails({ error, errorInfo }: { error: Error; errorInfo: ErrorInfo | null }) { const [isStackOpen, setIsStackOpen] = useState(true) const [isComponentStackOpen, setIsComponentStackOpen] = useState(false) const [copied, setCopied] = useState(false) const { t } = useTranslation() const stackFrames = error.stack ? parseStackTrace(error.stack) : [] const copyErrorInfo = async () => { const errorText = ` Error: ${error.name} Message: ${error.message} Stack Trace: ${error.stack || 'No stack trace available'} Component Stack: ${errorInfo?.componentStack || 'No component stack available'} URL: ${window.location.href} User Agent: ${navigator.userAgent} Time: ${new Date().toISOString()} `.trim() try { await navigator.clipboard.writeText(errorText) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch (err) { console.error('Failed to copy:', err) } } return (
{/* 错误消息 */} {error.name}: {error.message} {/* 堆栈跟踪 */} {stackFrames.length > 0 && (
{stackFrames.map((frame, index) => (
{index + 1}.
{frame.functionName} {frame.fileName && (
{frame.fileName} {frame.lineNumber && ( :{frame.lineNumber}:{frame.columnNumber} )}
)}
))}
)} {/* 组件堆栈 */} {errorInfo?.componentStack && (
                {errorInfo.componentStack}
              
)} {/* 复制按钮 */}
) } // 错误回退 UI function ErrorFallback({ error, errorInfo, }: { error: Error errorInfo: ErrorInfo | null }) { const { t } = useTranslation() const handleGoHome = () => { window.location.href = '/' } const handleRefresh = () => { window.location.reload() } return (
{t('errorBoundary.title')} {t('errorBoundary.description')}
{/* 操作按钮 */}
{/* 提示信息 */}

{t('errorBoundary.footer')}

) } // 错误边界类组件 export class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false, error: null, errorInfo: null, } } static getDerivedStateFromError(error: Error): Partial { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo) this.setState({ errorInfo }) } handleReset = () => { this.setState({ hasError: false, error: null, errorInfo: null, }) } render() { if (this.state.hasError && this.state.error) { if (this.props.fallback) { return this.props.fallback } return ( ) } return this.props.children } } // 路由级别的错误边界组件(用于 TanStack Router) export function RouteErrorBoundary({ error }: { error: Error }) { return ( ) }