Spaces:
Running
Running
| (function() { | |
| // Console 日志 | |
| console.info('全局异常捕获脚本已加载,开始监听错误...'); | |
| // 1. 初始化错误显示容器 (保留原有的 Visual Toast 功能) | |
| const errorContainer = document.createElement('div'); | |
| errorContainer.id = 'global-error'; | |
| // 设置样式:全屏、黑色背景、红色文字、置顶 | |
| errorContainer.style.cssText = ` | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| background-color: rgba(0, 0, 0, 0.9); | |
| color: #ff5555; | |
| z-index: 999999; | |
| overflow-y: auto; | |
| padding: 20px; | |
| font-family: 'Consolas', 'Monaco', monospace; | |
| font-size: 14px; | |
| white-space: pre-wrap; | |
| display: none; /* 默认隐藏,有错误时显示 */ | |
| box-sizing: border-box; | |
| `; | |
| // 添加标题和关闭提示 | |
| const header = document.createElement('div'); | |
| header.style.borderBottom = '1px solid #555'; | |
| header.style.paddingBottom = '10px'; | |
| header.style.marginBottom = '10px'; | |
| header.innerHTML = '<h2 style="margin:0; color:#fff;">🚨 全局异常捕获监控</h2><small style="color:#aaa">点击页面任意处可临时关闭蒙层</small>'; | |
| errorContainer.appendChild(header); | |
| // 点击关闭功能 | |
| errorContainer.addEventListener('click', () => { | |
| errorContainer.style.display = 'none'; | |
| }); | |
| // 确保 DOM 加载后插入 body,或者如果 body 存在直接插入 | |
| if (document.body) { | |
| document.body.appendChild(errorContainer); | |
| } else { | |
| window.addEventListener('DOMContentLoaded', () => document.body.appendChild(errorContainer)); | |
| } | |
| // 2. 核心处理函数:既显示在屏幕上,也发送给父窗口 | |
| function handleError(type, details) { | |
| // A. 显示在屏幕上 (Visual Toast) | |
| logErrorToScreen(type, details); | |
| // B. 发送给父窗口 (iframe 通信) | |
| postErrorToParent(type, details); | |
| } | |
| // 显示在屏幕上的具体实现 | |
| function logErrorToScreen(type, details) { | |
| // 显示蒙层 | |
| errorContainer.style.display = 'block'; | |
| const reportItem = document.createElement('div'); | |
| reportItem.style.marginBottom = '20px'; | |
| reportItem.style.borderBottom = '1px dashed #444'; | |
| reportItem.style.paddingBottom = '10px'; | |
| // 格式化时间 | |
| const time = new Date().toLocaleTimeString(); | |
| // 构建 HTML 内容 | |
| reportItem.innerHTML = ` | |
| <div style="color: #fff; font-weight: bold;">[${time}] <span style="background:#b00; padding:2px 5px; border-radius:3px;">${type}</span></div> | |
| <div style="margin-top:5px; color: #ffaaaa;">${details.message || '无错误信息'}</div> | |
| ${details.filename ? `<div style="color: #888;">Location: ${details.filename}:${details.lineno}:${details.colno}</div>` : ''} | |
| ${details.stack ? `<pre style="color: #aaa; background: #111; padding: 10px; overflow-x: auto; margin-top:5px;">${details.stack}</pre>` : ''} | |
| ${details.selector ? `<div style="color: #888;">Element: <${details.selector}> (src: ${details.src})</div>` : ''} | |
| `; | |
| // 插入到标题之后,内容的顶部 | |
| header.after(reportItem); | |
| } | |
| // 发送给父窗口的具体实现 | |
| function postErrorToParent(type, details) { | |
| if (window.parent && window.parent !== window) { | |
| const msg = { | |
| type: 'iframeError', | |
| payload: { | |
| errorType: type, | |
| message: details.message || 'Unknown Error', | |
| stack: details.stack || '', | |
| filename: details.filename || '', | |
| lineno: details.lineno || 0, | |
| colno: details.colno || 0, | |
| selector: details.selector || '', | |
| src: details.src || '', | |
| href: window.location.href, | |
| timestamp: new Date().toISOString() | |
| } | |
| }; | |
| // 使用 '*' 允许跨域传递,或者根据需要指定特定 origin | |
| window.parent.postMessage(msg, '*'); | |
| console.info('[CatchError] Posted error to parent:', payload); | |
| } | |
| } | |
| // 3. 监听器 A: 捕获 JS 运行时错误 + 资源加载错误 (img/script) | |
| // 注意:第三个参数 true (useCapture) 是捕获资源错误的关键 | |
| window.addEventListener('error', (event) => { | |
| // 情况 1: 资源加载错误 (img, script, link) | |
| // 资源错误没有冒泡,但在捕获阶段可以拦截,且 target 是 DOM 元素 | |
| if (event.target && (event.target instanceof HTMLElement)) { | |
| const target = event.target; | |
| handleError('Resource Error', { | |
| message: `资源加载失败 (${target.tagName})`, | |
| selector: target.tagName.toLowerCase(), | |
| src: target.src || target.href || 'unknown source', | |
| stack: 'N/A (Network Error)' | |
| }); | |
| } | |
| // 情况 2: 普通 JS 运行时错误 | |
| else { | |
| handleError('Runtime Error', { | |
| message: event.message, | |
| filename: event.filename, | |
| lineno: event.lineno, | |
| colno: event.colno, | |
| stack: event.error ? event.error.stack : '无堆栈信息' | |
| }); | |
| } | |
| }, true); // useCapture = true | |
| // 4. 监听器 B: 捕获未处理的 Promise Rejection | |
| window.addEventListener('unhandledrejection', (event) => { | |
| // 提取错误原因 | |
| let reason = event.reason; | |
| let stack = '无堆栈信息'; | |
| let message = ''; | |
| if (reason instanceof Error) { | |
| message = reason.message; | |
| stack = reason.stack; | |
| } else { | |
| // 如果 reject 的不是 Error 对象(例如 reject("foo")) | |
| message = typeof reason === 'object' ? JSON.stringify(reason) : String(reason); | |
| } | |
| handleError('Unhandled Promise', { | |
| message: `Promise 被 Reject 且未被 Catch: ${message}`, | |
| stack: stack | |
| }); | |
| }); | |
| console.info('全局异常捕获脚本已初始化完成 (Visual + PostMessage)。'); | |
| })(); | |