From 1dd86df621d9735202f9cf4b518f8ea19fe96846 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:16:36 +0800 Subject: [PATCH] refactor(shared): replace the traffic error boundary with ErrorBoundary TrafficErrorBoundary maintained a second boundary system beside BaseErrorBoundary: a report builder that only console.logged constants in the Tauri webview, an onError that re-logged what componentDidCatch already logged, and a fallback wrapper with a single consumer. Fold both mount points into react-error-boundary (the primitive behind BaseErrorBoundary) with local fallbacks that keep the panel styling, message text and retry reset, log each error once, and drop the retry attempt cap and the stack-details collapse along with it. --- .../home/enhanced-traffic-stats.tsx | 61 +++- src/components/layout/layout-traffic.tsx | 36 ++- .../shared/traffic-error-boundary.tsx | 301 ------------------ 3 files changed, 88 insertions(+), 310 deletions(-) delete mode 100644 src/components/shared/traffic-error-boundary.tsx diff --git a/src/components/home/enhanced-traffic-stats.tsx b/src/components/home/enhanced-traffic-stats.tsx index 39d68a850..dd5c5ebd4 100644 --- a/src/components/home/enhanced-traffic-stats.tsx +++ b/src/components/home/enhanced-traffic-stats.tsx @@ -3,10 +3,14 @@ import { ArrowUpwardRounded, CloudDownloadRounded, CloudUploadRounded, + ErrorOutlineRounded, LinkRounded, MemoryRounded, } from '@mui/icons-material' import { + Alert, + Box, + Button, Grid, PaletteColor, Paper, @@ -15,9 +19,9 @@ import { useTheme, } from '@mui/material' import { ReactNode, memo, useMemo, useRef } from 'react' +import { ErrorBoundary, FallbackProps } from 'react-error-boundary' import { useTranslation } from 'react-i18next' -import { TrafficErrorBoundary } from '@/components/shared/traffic-error-boundary' import { useConnectionSummaryData } from '@/hooks/use-connection-data' import { useMemoryData } from '@/hooks/use-memory-data' import { useTrafficData } from '@/hooks/use-traffic-data' @@ -127,6 +131,50 @@ const CompactStatCard = memo( // 添加显示名称 CompactStatCard.displayName = 'CompactStatCard' +const fallbackSx = { + p: 2, + minHeight: 200, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + border: '1px dashed', + borderColor: 'error.main', + borderRadius: 2, + bgcolor: 'error.light', +} + +const reloadPage = () => window.location.reload() + +const TrafficFallback = ({ error, resetErrorBoundary }: FallbackProps) => { + const { t } = useTranslation() + + return ( + + + + {t('shared.feedback.errors.trafficStats')} + + + {t('shared.feedback.errors.trafficStatsDescription')} + + + {error instanceof Error + ? error.message + : t('shared.feedback.errors.unknown')} + + + + + + + ) +} + export const EnhancedTrafficStats = () => { const { t } = useTranslation() const theme = useTheme() @@ -254,10 +302,11 @@ export const EnhancedTrafficStats = () => { }, [t, parsedData, displayMemory]) return ( - { - console.error('[EnhancedTrafficStats] 组件错误:', error, errorInfo) - }} + + console.error('[EnhancedTrafficStats]', error, errorInfo) + } > {trafficGraph && ( @@ -273,6 +322,6 @@ export const EnhancedTrafficStats = () => { ))} - + ) } diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index b7d50e5d6..9c6564edc 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -1,14 +1,15 @@ import { ArrowDownwardRounded, ArrowUpwardRounded, + ErrorOutlineRounded, MemoryRounded, } from '@mui/icons-material' import type { BoxProps, SvgIconProps, TypographyProps } from '@mui/material' import { Box, Typography } from '@mui/material' import { useEffect, useRef } from 'react' +import { ErrorBoundary } from 'react-error-boundary' import { useTranslation } from 'react-i18next' -import { LightweightTrafficErrorBoundary } from '@/components/shared/traffic-error-boundary' import { useMemoryData } from '@/hooks/use-memory-data' import { useTrafficData } from '@/hooks/use-traffic-data' import { useVerge } from '@/hooks/use-verge' @@ -17,6 +18,30 @@ import parseTraffic from '@/utils/parse-traffic' import { TrafficGraph, type TrafficRef } from './traffic-graph' +const unavailableSx = { + p: 1, + minHeight: 60, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + bgcolor: 'error.light', + borderRadius: 1, + color: 'error.contrastText', +} + +const TrafficUnavailableFallback = () => { + const { t } = useTranslation() + + return ( + + + + {t('shared.feedback.errors.trafficUnavailable')} + + + ) +} + export const LayoutTraffic = () => { const { t } = useTranslation() const { verge } = useVerge() @@ -75,7 +100,12 @@ export const LayoutTraffic = () => { } return ( - + + console.error('[LayoutTraffic]', error, errorInfo) + } + > {trafficGraph && pageVisible && (
{ )} - + ) } diff --git a/src/components/shared/traffic-error-boundary.tsx b/src/components/shared/traffic-error-boundary.tsx deleted file mode 100644 index 75d77b110..000000000 --- a/src/components/shared/traffic-error-boundary.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import { - ErrorOutlineRounded, - RefreshRounded, - BugReportRounded, -} from '@mui/icons-material' -import { Box, Typography, Button, Alert, Collapse } from '@mui/material' -import React, { Component, ErrorInfo, ReactNode } from 'react' -import { useTranslation } from 'react-i18next' - -interface Props { - children: ReactNode - fallbackComponent?: ReactNode - onError?: (error: Error, errorInfo: ErrorInfo) => void -} - -interface State { - hasError: boolean - error: Error | null - errorInfo: ErrorInfo | null - showDetails: boolean -} - -export class TrafficErrorBoundary extends Component { - private retryCount = 0 - private maxRetries = 3 - - constructor(props: Props) { - super(props) - this.state = { - hasError: false, - error: null, - errorInfo: null, - showDetails: false, - } - } - - static getDerivedStateFromError(error: Error): Partial { - return { hasError: true, error } - } - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('[TrafficErrorBoundary] 捕获到组件错误:', error, errorInfo) - - this.setState({ - error, - errorInfo, - }) - - if (this.props.onError) { - this.props.onError(error, errorInfo) - } - - this.reportError(error, errorInfo) - } - - private reportError = (error: Error, errorInfo: ErrorInfo) => { - const errorReport = { - message: error.message, - stack: error.stack, - componentStack: errorInfo.componentStack, - timestamp: new Date().toISOString(), - userAgent: navigator.userAgent, - url: window.location.href, - } - - console.error('[TrafficErrorBoundary] 错误报告:', errorReport) - } - - private handleRetry = () => { - if (this.retryCount < this.maxRetries) { - this.retryCount++ - console.log( - `[TrafficErrorBoundary] 尝试重试 (${this.retryCount}/${this.maxRetries})`, - ) - - this.setState({ - hasError: false, - error: null, - errorInfo: null, - showDetails: false, - }) - } else { - console.warn('[TrafficErrorBoundary] 已达到最大重试次数') - } - } - - private handleRefresh = () => { - window.location.reload() - } - - private toggleDetails = () => { - this.setState((prev) => ({ showDetails: !prev.showDetails })) - } - - render() { - if (this.state.hasError) { - if (this.props.fallbackComponent) { - return this.props.fallbackComponent - } - - return ( - - ) - } - - return this.props.children - } -} - -interface TrafficErrorFallbackProps { - error: Error | null - errorInfo: ErrorInfo | null - showDetails: boolean - canRetry: boolean - retryCount: number - maxRetries: number - onRetry: () => void - onRefresh: () => void - onToggleDetails: () => void -} - -const TrafficErrorFallback: React.FC = ({ - error, - errorInfo, - showDetails, - canRetry, - retryCount, - maxRetries, - onRetry, - onRefresh, - onToggleDetails, -}) => { - const { t } = useTranslation() - - return ( - - - - - {t('shared.feedback.errors.trafficStats')} - - - - {t('shared.feedback.errors.trafficStatsDescription')} - - - - - {t('shared.feedback.errors.label')}:{' '} - {error instanceof Error - ? error.message - : t('shared.feedback.errors.unknown')} - - {retryCount > 0 && ( - - {t('shared.labels.retryAttempts')}: {retryCount}/{maxRetries} - - )} - - - - {canRetry && ( - - )} - - - - - - - - - - {t('shared.feedback.errors.details')}: - - - {error?.stack} - - - {errorInfo?.componentStack && ( - <> - - {t('shared.feedback.errors.componentStack')}: - - - {errorInfo.componentStack} - - - )} - - - - ) -} - -export const LightweightTrafficErrorBoundary: React.FC<{ - children: ReactNode -}> = ({ children }) => { - const { t } = useTranslation() - - return ( - - - - {t('shared.feedback.errors.trafficUnavailable')} - - - } - > - {children} - - ) -}