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;
|