-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathErrorBoundary.tsx
More file actions
194 lines (169 loc) · 5.45 KB
/
ErrorBoundary.tsx
File metadata and controls
194 lines (169 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import React, { Component } from "react";
import { Button, Modal } from "antd";
import { useTranslation } from "react-i18next";
interface ErrorContextType {
hasError: boolean;
error: Error | null;
errorInfo: { componentStack: string } | null;
}
const ErrorContext = React.createContext<ErrorContextType>({
hasError: false,
error: null,
errorInfo: null,
});
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: { componentStack: string } | null;
errorTimestamp: string | null;
}
interface ErrorBoundaryProps {
children?: React.ReactNode;
onReset?: () => void;
showDetails?: boolean;
t?: (key: string, options?: any) => string;
}
export default class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorTimestamp: null,
};
}
static getDerivedStateFromError(error: any) {
// 更新 state 使下一次渲染能够显示降级 UI
return {
hasError: true,
error: error,
errorTimestamp: new Date().toISOString(),
};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// 错误统计
this.setState({
error,
errorInfo,
hasError: true,
});
// 在实际应用中,这里可以集成错误报告服务
this.logErrorToService(error, errorInfo);
// 开发环境下在控制台显示详细错误
if (import.meta.env.DEV) {
console.error("ErrorBoundary 捕获到错误:", error);
console.error("错误详情:", errorInfo);
}
}
logErrorToService = (error: Error, errorInfo: React.ErrorInfo) => {
// 这里可以集成 Sentry、LogRocket 等错误监控服务
const errorData = {
error: error.toString(),
errorInfo: errorInfo.componentStack,
timestamp: this.state.errorTimestamp,
url: window.location.href,
userAgent: navigator.userAgent,
};
// 模拟发送错误日志
console.log("发送错误日志到监控服务:", errorData);
// 实际使用时取消注释并配置您的错误监控服务
/*
if (window.Sentry) {
window.Sentry.captureException(error, { extra: errorInfo });
}
*/
};
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
errorTimestamp: null,
});
// 可选:重新加载页面或执行其他恢复操作
if (this.props.onReset) {
this.props.onReset();
}
};
handleReload = () => {
window.location.reload();
};
handleGoHome = () => {
window.location.href = "/";
};
renderErrorDetails = () => {
const { error, errorInfo } = this.state;
const t = this.props.t || ((key: string) => key);
if (!this.props.showDetails) return null;
return (
<div className="bg-gray-100 p-4 mt-4 text-left rounded">
<div className="mt-2">
<strong>{t('components.errorBoundary.errorMessage')}</strong>
<pre className="bg-gray-600 px-4 py-2 rounded text-white overflow-auto">
{error?.toString()}
</pre>
</div>
{errorInfo && (
<div className="mt-2">
<strong>{t('components.errorBoundary.componentStack')}</strong>
<pre className="bg-gray-600 max-h-100 px-4 py-2 rounded text-white overflow-auto">
{errorInfo.componentStack}
</pre>
</div>
)}
</div>
);
};
render() {
if (this.state.hasError) {
return (
<Modal visible width={1000} footer={null} closable={false}>
<div className="text-center p-6">
<div className="text-3xl">⚠️</div>
<h1 className="text-xl p-2">{this.props.t?.('components.errorBoundary.title') || '出了点问题'}</h1>
<p className="text-sm text-gray-400">{this.props.t?.('components.errorBoundary.description') || '应用程序遇到了意外错误。'}</p>
<div className="flex justify-center gap-4 my-4">
<Button onClick={this.handleReload}>{this.props.t?.('components.errorBoundary.reloadPage') || '刷新页面'}</Button>
<Button type="primary" onClick={this.handleGoHome}>
{this.props.t?.('components.errorBoundary.goHome') || '返回首页'}
</Button>
</div>
{this.renderErrorDetails()}
<div className="mt-4 border-t border-gray-100 pt-4 text-center">
<p className="text-sm text-gray-500">
{this.props.t?.('components.errorBoundary.contactSupport') || '如果问题持续存在,请联系技术支持'}
</p>
<small className="text-xs text-gray-400">
{this.props.t?.('components.errorBoundary.errorId', { timestamp: this.state.errorTimestamp }) || `错误 ID: ${this.state.errorTimestamp}`}
</small>
</div>
</div>
</Modal>
);
}
return (
<ErrorContext.Provider
value={{
hasError: this.state.hasError,
error: this.state.error,
errorInfo: this.state.errorInfo,
}}
>
{this.props.children}
</ErrorContext.Provider>
);
}
}
export function withErrorBoundary(
Component: React.ComponentType
): React.ComponentType {
return (props) => (
<ErrorBoundary showDetails={import.meta.env.DEV}>
<Component {...props} />
</ErrorBoundary>
);
}