]> acesimba.cloud Git - words.git/commitdiff
split: 修复无痕模式白屏(localStorage 防御 + 全局错误边界)
authorroot <root@localhost.localdomain>
Thu, 27 Aug 2026 10:14:47 +0000 (18:14 +0800)
committerroot <root@localhost.localdomain>
Thu, 27 Aug 2026 10:14:47 +0000 (18:14 +0800)
- auth.js: localStorage 读写包 try-catch, 无痕/隐私模式禁止访问时不再崩溃
- ErrorBoundary.js: 新增全局错误边界, 渲染期异常显示错误文字而非白屏
- index.js: 用 ErrorBoundary 包裹 <App/>

Co-Authored-By: CodeBuddy Code <noreply@codebuddy.ai>
frontend/src/ErrorBoundary.js [new file with mode: 0644]
frontend/src/auth.js
frontend/src/index.js

diff --git a/frontend/src/ErrorBoundary.js b/frontend/src/ErrorBoundary.js
new file mode 100644 (file)
index 0000000..cd0702e
--- /dev/null
@@ -0,0 +1,35 @@
+import React from 'react';
+
+// 全局错误边界:捕获渲染期异常并显示, 避免生产构建下整页白屏无提示
+export default class ErrorBoundary extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { hasError: false, error: null, info: null };
+  }
+
+  static getDerivedStateFromError(error) {
+    return { hasError: true, error };
+  }
+
+  componentDidCatch(error, info) {
+    this.setState({ info });
+    console.error('App crashed:', error, info);
+  }
+
+  render() {
+    if (this.state.hasError) {
+      return (
+        <div style={{ padding: '24px', fontFamily: 'sans-serif', color: '#c0392b' }}>
+          <h2>页面加载出错</h2>
+          <pre style={{ whiteSpace: 'pre-wrap', background: '#fdecea', padding: '12px', borderRadius: '6px' }}>
+            {this.state.error && (this.state.error.stack || this.state.error.toString())}
+          </pre>
+          <button onClick={() => window.location.reload()} style={{ marginTop: '12px', padding: '8px 16px' }}>
+            重新加载
+          </button>
+        </div>
+      );
+    }
+    return this.props.children;
+  }
+}
index 4a4bcf03b14cfd1bfaf5cc837a2afb736ced9e94..7051b55a08c423d46fd9e8b309451b2a1c37b17b 100644 (file)
@@ -4,16 +4,28 @@ import { API_BASE } from './api';
 
 const TOKEN_KEY = 'words_auth_token';
 
+// 无痕/隐私模式下部分浏览器会禁止 localStorage 访问并抛 SecurityError,
+// 这里统一兜底, 避免初始化时崩溃导致整页白屏。
+function safeGet(key) {
+    try { return localStorage.getItem(key); } catch (e) { return null; }
+}
+function safeSet(key, val) {
+    try { localStorage.setItem(key, val); } catch (e) {}
+}
+function safeRemove(key) {
+    try { localStorage.removeItem(key); } catch (e) {}
+}
+
 export function getToken() {
-    return localStorage.getItem(TOKEN_KEY) || '';
+    return safeGet(TOKEN_KEY) || '';
 }
 
 export function setToken(t) {
-    localStorage.setItem(TOKEN_KEY, t);
+    safeSet(TOKEN_KEY, t);
 }
 
 export function clearToken() {
-    localStorage.removeItem(TOKEN_KEY);
+    safeRemove(TOKEN_KEY);
 }
 
 export function isLoggedIn() {
index f24def92a5c2f8195229c2428343ab17145cbd41..3a56c9e100d72c062b74d2d49ad475d209e951f7 100644 (file)
@@ -2,13 +2,16 @@ import React from 'react';
 import ReactDOM from 'react-dom/client';
 import './index.css';
 import App from './App';
+import ErrorBoundary from './ErrorBoundary';
 import { isLoggedIn } from './auth';
 import reportWebVitals from './reportWebVitals';
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
   <React.StrictMode>
-    <App />
+    <ErrorBoundary>
+      <App />
+    </ErrorBoundary>
   </React.StrictMode>
 );