diff --git a/frontend/components/ErrorBoundary.js b/frontend/components/ErrorBoundary.js new file mode 100644 index 0000000..6442b1b --- /dev/null +++ b/frontend/components/ErrorBoundary.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Icon } from './icons'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error('ErrorBoundary caught error:', error, errorInfo); + } + + handleReload = () => { + window.location.reload(); + }; + + handleGoHome = () => { + window.location.href = '/'; + }; + + render() { + if (this.state.hasError) { + return ( +
+
+ +

出错了

+

抱歉,应用遇到了问题。

+ {process.env.NODE_ENV === 'development' && ( +
+                {this.state.error?.toString()}
+              
+ )} +
+ + +
+
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/frontend/components/LoadingSpinner.js b/frontend/components/LoadingSpinner.js new file mode 100644 index 0000000..e4ab41f --- /dev/null +++ b/frontend/components/LoadingSpinner.js @@ -0,0 +1,10 @@ +import { Icon } from './icons'; + +export default function LoadingSpinner({ size = 24, text = '加载中...' }) { + return ( +
+ + {text && {text}} +
+ ); +} diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js index 815a2b3..798f429 100644 --- a/frontend/pages/_app.js +++ b/frontend/pages/_app.js @@ -1,13 +1,20 @@ -import '../styles/globals.css'; +import { SWRConfig } from 'swr'; +import ErrorBoundary from '../components/ErrorBoundary'; import { ToastProvider } from '../components/ToastProvider'; import { ConfirmProvider } from '../components/ConfirmProvider'; +import { swrConfig } from '../lib/swr-config'; +import '../styles/globals.css'; export default function App({ Component, pageProps }) { return ( - - - - - + + + + + + + + + ); } diff --git a/frontend/styles/globals.css b/frontend/styles/globals.css index b406697..7926c48 100644 --- a/frontend/styles/globals.css +++ b/frontend/styles/globals.css @@ -2136,3 +2136,61 @@ textarea:focus-visible { min-height: 54px; } } + +/* Error Boundary */ +.error-boundary { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 20px; +} + +.error-boundary__content { + text-align: center; + max-width: 400px; +} + +.error-boundary__content h2 { + margin: 16px 0 8px; + color: #1e293b; +} + +.error-boundary__content p { + color: #64748b; + margin-bottom: 24px; +} + +.error-boundary__details { + background: #f1f5f9; + padding: 12px; + border-radius: 8px; + font-size: 12px; + color: #64748b; + margin-bottom: 24px; + overflow-x: auto; +} + +.error-boundary__actions { + display: flex; + gap: 12px; + justify-content: center; +} + +/* Loading Spinner */ +.loading-spinner { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 20px; + color: #64748b; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.is-spinning { + animation: spin 1s linear infinite; +}