Spaces:
Sleeping
Sleeping
| /** | |
| * Error Boundary Module | |
| * 全局错误捕获与处理,防止未处理异常导致页面崩溃 | |
| */ | |
| // ========== 全局错误边界 ========== | |
| const ErrorBoundary = { | |
| _toastContainer: null, | |
| init() { | |
| // 捕获未处理的 Promise 拒绝 | |
| window.addEventListener('unhandledrejection', (event) => { | |
| console.error('[ErrorBoundary] Unhandled promise rejection:', event.reason); | |
| // 如果是离线错误,不显示 toast | |
| if (event.reason && event.reason.message === 'OFFLINE') { | |
| return; | |
| } | |
| this.showToast('操作失败,请重试', 'error'); | |
| event.preventDefault(); | |
| }); | |
| // 捕获全局 JS 错误 | |
| window.addEventListener('error', (event) => { | |
| console.error('[ErrorBoundary] Uncaught error:', event.error || event.message); | |
| // 忽略资源加载错误(由网络问题引起) | |
| if (event.target && event.target !== window) { | |
| return; | |
| } | |
| this.showToast('页面出现错误,请刷新重试', 'error'); | |
| }); | |
| console.log('[ErrorBoundary] 全局错误边界已初始化'); | |
| }, | |
| /** | |
| * 显示 Toast 通知 | |
| * @param {string} message - 消息内容 | |
| * @param {'info'|'success'|'warning'|'error'} type - 消息类型 | |
| * @param {number} duration - 显示时长(毫秒) | |
| */ | |
| showToast(message, type = 'info', duration = 3000) { | |
| if (!this._toastContainer) { | |
| this._toastContainer = document.createElement('div'); | |
| this._toastContainer.id = 'toast-container'; | |
| this._toastContainer.style.cssText = ` | |
| position: fixed; top: 20px; right: 20px; z-index: 10000; | |
| display: flex; flex-direction: column; gap: 8px; pointer-events: none; | |
| `; | |
| document.body.appendChild(this._toastContainer); | |
| } | |
| const colors = { | |
| info: 'var(--deep-blue, #1a73e8)', | |
| success: 'var(--success, #34a853)', | |
| warning: 'var(--warning, #fbbc04)', | |
| error: 'var(--danger, #ea4335)' | |
| }; | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast-notification'; | |
| toast.style.cssText = ` | |
| background: ${colors[type] || colors.info}; color: white; | |
| padding: 12px 20px; border-radius: 8px; font-size: 14px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); pointer-events: auto; | |
| opacity: 0; transform: translateX(20px); | |
| transition: opacity 0.3s, transform 0.3s; | |
| max-width: 320px; word-break: break-word; | |
| `; | |
| toast.textContent = message; | |
| this._toastContainer.appendChild(toast); | |
| // Animate in | |
| requestAnimationFrame(() => { | |
| toast.style.opacity = '1'; | |
| toast.style.transform = 'translateX(0)'; | |
| }); | |
| // Auto dismiss | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| toast.style.transform = 'translateX(20px)'; | |
| setTimeout(() => toast.remove(), 300); | |
| }, duration); | |
| }, | |
| /** | |
| * 安全执行异步函数,捕获错误并显示 toast | |
| * @param {Function} fn - 异步函数 | |
| * @param {string} errorMsg - 错误时显示的消息 | |
| * @returns {Promise<any>} | |
| */ | |
| async wrap(fn, errorMsg = '操作失败') { | |
| try { | |
| return await fn(); | |
| } catch (error) { | |
| console.error('[ErrorBoundary] Wrapped function error:', error); | |
| if (error.message !== 'OFFLINE') { | |
| this.showToast(errorMsg, 'error'); | |
| } | |
| return null; | |
| } | |
| } | |
| }; | |
| // 暴露为全局 | |
| window.ErrorBoundary = ErrorBoundary; | |
| // 自动初始化 | |
| document.addEventListener('DOMContentLoaded', () => { | |
| ErrorBoundary.init(); | |
| }); |