/** * 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} */ 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(); });