bedee3ba8d
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
153 lines
4.0 KiB
TypeScript
153 lines
4.0 KiB
TypeScript
import { Component, type ReactNode } from "react";
|
||
import { reportError } from "../utils/errorReporting";
|
||
|
||
interface Props {
|
||
children: ReactNode;
|
||
}
|
||
|
||
interface State {
|
||
hasError: boolean;
|
||
error: Error | null;
|
||
}
|
||
|
||
class ErrorBoundary extends Component<Props, State> {
|
||
constructor(props: Props) {
|
||
super(props);
|
||
this.state = { hasError: false, error: null };
|
||
}
|
||
|
||
static getDerivedStateFromError(error: Error): State {
|
||
return { hasError: true, error };
|
||
}
|
||
|
||
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||
console.error("[ErrorBoundary] Uncaught error:", error, info.componentStack);
|
||
reportError(error, "boundary");
|
||
}
|
||
|
||
handleReset = () => {
|
||
this.setState({ hasError: false, error: null });
|
||
};
|
||
|
||
handleReload = () => {
|
||
window.location.reload();
|
||
};
|
||
|
||
render() {
|
||
if (!this.state.hasError) {
|
||
return this.props.children;
|
||
}
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
minHeight: "100vh",
|
||
background: "var(--bg-base, #f5f5f5)",
|
||
padding: 24,
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
maxWidth: 480,
|
||
padding: "48px 40px",
|
||
borderRadius: 16,
|
||
background: "var(--surface-panel, #fff)",
|
||
boxShadow: "0 4px 24px rgba(0,0,0,0.08)",
|
||
textAlign: "center",
|
||
}}
|
||
>
|
||
<div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
|
||
<h2
|
||
style={{
|
||
margin: "0 0 12px",
|
||
fontSize: 20,
|
||
fontWeight: 600,
|
||
color: "var(--text-primary, #1a1a1a)",
|
||
}}
|
||
>
|
||
页面出错了
|
||
</h2>
|
||
<p
|
||
style={{
|
||
margin: "0 0 24px",
|
||
fontSize: 14,
|
||
color: "var(--text-secondary, #666)",
|
||
lineHeight: 1.6,
|
||
}}
|
||
>
|
||
应用遇到了意外错误,请尝试刷新页面。
|
||
<br />
|
||
如果问题持续出现,请联系技术支持。
|
||
</p>
|
||
{this.state.error && (
|
||
<details
|
||
style={{
|
||
marginBottom: 24,
|
||
padding: 12,
|
||
borderRadius: 8,
|
||
background: "var(--bg-elevated, #f0f0f0)",
|
||
textAlign: "left",
|
||
fontSize: 12,
|
||
color: "var(--text-secondary, #666)",
|
||
}}
|
||
>
|
||
<summary style={{ cursor: "pointer", fontWeight: 500 }}>
|
||
错误详情
|
||
</summary>
|
||
<pre
|
||
style={{
|
||
marginTop: 8,
|
||
whiteSpace: "pre-wrap",
|
||
wordBreak: "break-word",
|
||
fontFamily: "monospace",
|
||
fontSize: 11,
|
||
}}
|
||
>
|
||
{this.state.error.message}
|
||
</pre>
|
||
</details>
|
||
)}
|
||
<div style={{ display: "flex", gap: 12, justifyContent: "center" }}>
|
||
<button
|
||
type="button"
|
||
onClick={this.handleReset}
|
||
style={{
|
||
padding: "10px 24px",
|
||
borderRadius: 8,
|
||
border: "1px solid var(--border-normal, #ddd)",
|
||
background: "transparent",
|
||
color: "var(--text-primary, #1a1a1a)",
|
||
fontSize: 14,
|
||
cursor: "pointer",
|
||
}}
|
||
>
|
||
重试
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={this.handleReload}
|
||
style={{
|
||
padding: "10px 24px",
|
||
borderRadius: 8,
|
||
border: "none",
|
||
background: "var(--accent, #0d9488)",
|
||
color: "#fff",
|
||
fontSize: 14,
|
||
fontWeight: 500,
|
||
cursor: "pointer",
|
||
}}
|
||
>
|
||
刷新页面
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default ErrorBoundary;
|