ling-series-spaces / static /catch-error.js
GitHub Action
Sync ling-space changes (filtered) from commit 127300e
b931367
raw
history blame
6.35 kB
(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: &lt;${details.selector}&gt; (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)。');
})();