(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 = '

🚨 全局异常捕获监控

点击页面任意处可临时关闭蒙层'; 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 = `
[${time}] ${type}
${details.message || '无错误信息'}
${details.filename ? `
Location: ${details.filename}:${details.lineno}:${details.colno}
` : ''} ${details.stack ? `
${details.stack}
` : ''} ${details.selector ? `
Element: <${details.selector}> (src: ${details.src})
` : ''} `; // 插入到标题之后,内容的顶部 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)。'); })();