refactor(代码重复): 抽取共享工具函数与 UI 组件,消除多处重复实现

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
雨霖铃
2026-06-19 20:55:39 +08:00
parent 47d74f999e
commit 0f3060dcf3
24 changed files with 386 additions and 309 deletions
+9 -28
View File
@@ -1,6 +1,5 @@
import { Component, ErrorInfo, ReactNode } from 'react';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ErrorFallback } from '@/components/ErrorFallback';
interface Props {
children: ReactNode;
@@ -38,32 +37,14 @@ class ErrorBoundary extends Component<Props, State> {
render() {
if (this.state.hasError) {
return (
<div className="flex flex-col items-center justify-center mt-16 mx-auto max-w-md">
<div className="p-6 text-center rounded-xl border border-destructive/20 bg-destructive/5 shadow-sm">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10 text-destructive mx-auto mb-4">
<AlertCircle className="h-8 w-8" />
</div>
<h2 className="text-xl font-extrabold text-destructive mb-2"></h2>
<p className="text-sm text-muted-foreground mb-6">
</p>
{this.state.error && (
<div className="mb-6 p-4 rounded-lg bg-zinc-950 dark:bg-zinc-900 text-left max-h-[200px] overflow-auto border border-border/40">
<pre className="font-mono text-xs whitespace-pre-wrap break-all text-zinc-200 selection:bg-zinc-700">
{this.state.error.toString()}
</pre>
</div>
)}
<Button
variant="destructive"
onClick={this.handleReset}
className="rounded-lg font-bold shadow-sm"
>
<RefreshCw className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<ErrorFallback
variant="app"
title="糟糕,出了点问题"
description="应用遇到了一些意外错误。您可以尝试刷新页面或重置应用。"
error={this.state.error}
actionLabel="刷新应用"
onAction={this.handleReset}
/>
);
}
+94
View File
@@ -0,0 +1,94 @@
import { AlertCircle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
export interface ErrorFallbackProps {
title: string;
description: string;
error: Error | null;
actionLabel: string;
onAction: () => void;
variant?: 'app' | 'page';
showStack?: boolean;
className?: string;
}
export function ErrorFallback({
title,
description,
error,
actionLabel,
onAction,
variant = 'page',
showStack = false,
className,
}: ErrorFallbackProps) {
const isApp = variant === 'app';
const errorText = error ? (showStack ? error.stack || error.toString() : error.toString()) : null;
return (
<div
className={cn(
'flex flex-col items-center justify-center',
isApp
? 'mt-16 mx-auto max-w-md'
: 'flex-1 p-6 min-h-[300px] animate-in fade-in zoom-in-95 duration-200',
className,
)}
>
<div
className={cn(
'p-6 text-center rounded-xl border border-destructive/20 bg-destructive/5 shadow-sm',
!isApp && 'max-w-md w-full',
)}
>
<div
className={cn(
'flex items-center justify-center rounded-full bg-destructive/10 text-destructive mx-auto mb-4',
isApp ? 'h-16 w-16' : 'h-12 w-12',
)}
>
<AlertCircle className={isApp ? 'h-8 w-8' : 'h-6 w-6'} />
</div>
{isApp ? (
<h2 className="text-xl font-extrabold text-destructive mb-2">{title}</h2>
) : (
<h3 className="text-base font-semibold text-foreground mb-1.5">{title}</h3>
)}
<p className={cn('text-muted-foreground', isApp ? 'text-sm mb-6' : 'text-xs mb-5')}>
{description}
</p>
{errorText && (
<div
className={cn(
'rounded-lg bg-zinc-950 dark:bg-zinc-900 text-left border border-border/40',
isApp ? 'mb-6 p-4 max-h-[200px] overflow-auto' : 'mb-5 p-3 max-h-40 overflow-y-auto',
)}
>
<pre
className={cn(
'font-mono whitespace-pre-wrap break-all text-zinc-200 selection:bg-zinc-700',
isApp ? 'text-xs' : 'text-[11px] leading-relaxed',
)}
>
{errorText}
</pre>
</div>
)}
<Button
variant="destructive"
size={isApp ? 'default' : 'sm'}
onClick={onAction}
className={cn(isApp ? 'rounded-lg font-bold shadow-sm' : 'font-medium shadow-sm')}
>
<RefreshCw className={isApp ? 'mr-2 h-4 w-4' : 'mr-1.5 h-3.5 w-3.5'} />
{actionLabel}
</Button>
</div>
</div>
);
}
+10 -32
View File
@@ -1,6 +1,5 @@
import { Component, ErrorInfo, ReactNode } from 'react';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ErrorFallback } from '@/components/ErrorFallback';
interface Props {
children: ReactNode;
@@ -39,36 +38,15 @@ class PageErrorBoundary extends Component<Props, State> {
render() {
if (this.state.hasError) {
return (
<div className="flex flex-col items-center justify-center flex-1 p-6 min-h-[300px] animate-in fade-in zoom-in-95 duration-200">
<div className="p-6 text-center rounded-xl border border-destructive/20 bg-destructive/5 max-w-md w-full shadow-sm">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10 text-destructive mx-auto mb-4">
<AlertCircle className="h-6 w-6" />
</div>
<h3 className="text-base font-semibold text-foreground mb-1.5"></h3>
<p className="text-xs text-muted-foreground mb-5">
</p>
{this.state.error && (
<div className="mb-5 p-3 rounded-lg bg-zinc-950 dark:bg-zinc-900 text-left max-h-40 overflow-y-auto border border-border/40">
<pre className="font-mono text-[11px] leading-relaxed whitespace-pre-wrap break-all text-zinc-200 selection:bg-zinc-700">
{this.state.error.stack || this.state.error.toString()}
</pre>
</div>
)}
<Button
variant="destructive"
size="sm"
onClick={this.handleRetry}
className="font-medium shadow-sm"
>
<RefreshCw className="mr-1.5 h-3.5 w-3.5" />
</Button>
</div>
</div>
<ErrorFallback
variant="page"
title="该功能运行异常"
description="该页面在加载或渲染时遇到了内部脚本错误。您可以尝试重试,或者通过导航菜单切换到其他工具。"
error={this.state.error}
actionLabel="重新尝试"
onAction={this.handleRetry}
showStack
/>
);
}