/** * 全局错误上报模块 * 用于捕获和上报H5前端错误 */ const ERROR_REPORT_API = 'https://tj-h5.hnxdfe.com/api/H5/test' /** * 构建错误信息对象 */ const buildErrorInfo = (error, type = 'error') => { const errorInfo = { type, // error: js错误, promise: promise错误, unhandled: 未捕获异常 message: '', stack: '', url: '', ua: '', timestamp: '', openId: '', rawError: null // 保留原始错误对象(用于调试) } try { errorInfo.timestamp = new Date().toISOString() errorInfo.url = window.location.href errorInfo.ua = navigator.userAgent // 获取用户信息 try { errorInfo.openId = uni.getStorageSync('OPENID') || '' } catch (e) { errorInfo.openId = '获取失败' } // 处理不同类型的错误 if (error instanceof Error) { errorInfo.message = error.message || String(error) errorInfo.stack = error.stack || '' } else if (typeof error === 'string') { errorInfo.message = error errorInfo.stack = '' } else if (error && typeof error === 'object') { try { // 检查 message 是否是 "{}" 或空字符串 const msg = error.message || '' if (msg === '{}' || msg === '' || msg === 'undefined' || msg === 'null') { // 尝试从其他字段获取信息 if (error.reason) { errorInfo.message = String(error.reason) } else if (error.name) { errorInfo.message = error.name } else if (error.toString && typeof error.toString === 'function') { const str = error.toString() if (str && str !== '[object Object]') { errorInfo.message = str } else { errorInfo.message = '未知错误对象(无有效信息)' } } else { // 尝试序列化对象 const jsonStr = JSON.stringify(error) if (jsonStr && jsonStr !== '{}') { errorInfo.message = jsonStr } else { errorInfo.message = '错误对象为空' } } } else { errorInfo.message = msg } errorInfo.stack = error.stack || '' // 如果没有 stack,尝试构建堆栈信息 if (!errorInfo.stack && error.filename) { errorInfo.stack = `at ${error.filename}:${error.lineno || 0}:${error.colno || 0}` } // 保留原始错误对象的前几个键(用于调试) const keys = Object.keys(error).slice(0, 5) if (keys.length > 0) { errorInfo.rawError = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] if (typeof error[key] !== 'function') { errorInfo.rawError[key] = String(error[key]).substring(0, 100) } } } } catch (e) { errorInfo.message = '解析错误对象失败: ' + String(e) errorInfo.stack = '' } } else if (error === null || error === undefined) { errorInfo.message = '错误对象为 ' + String(error) errorInfo.stack = '' } else { errorInfo.message = String(error) || '未知错误' errorInfo.stack = '' } // 如果 message 还是空的或 {} if (!errorInfo.message || errorInfo.message === '{}' || errorInfo.message === 'undefined') { errorInfo.message = '无法获取错误详情(错误对象为空或无效)' } } catch (e) { console.warn('[错误上报] 构建错误信息失败', e) } return errorInfo } /** * 使用 sendBeacon 上报(推荐) * 优势:即使页面关闭也能发送,不阻塞页面 */ const reportByBeacon = (errorInfo) => { try { if (navigator.sendBeacon) { const data = JSON.stringify(errorInfo) const blob = new Blob([data], { type: 'application/json' }) return navigator.sendBeacon(ERROR_REPORT_API, blob) } } catch (e) { console.warn('[错误上报] sendBeacon 失败', e) } return false } /** * 使用 fetch 上报(备用方案) */ const reportByFetch = (errorInfo) => { try { fetch(ERROR_REPORT_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(errorInfo), keepalive: true // 即使页面关闭也保持连接 }).catch(err => { console.warn('[错误上报] fetch 失败', err) }) } catch (e) { console.warn('[错误上报] fetch 执行失败', e) } } /** * 使用 uni.request 上报(最后备用) */ const reportByUni = (errorInfo) => { try { uni.request({ url: ERROR_REPORT_API, method: 'POST', data: errorInfo, fail: (err) => { console.warn('[错误上报] uni.request 失败', err) } }) } catch (e) { console.warn('[错误上报] uni.request 执行失败', e) } } /** * 判断是否应该忽略的错误 */ const shouldIgnoreError = (error) => { // 忽略请求中止错误(页面跳转时常见) if (error && error.errMsg === 'request:fail abort') { return true } if (error && error.message && error.message.includes('abort')) { return true } // 忽略取消的错误 if (error && error.name === 'AbortError') { return true } return false } /** * 上报错误到服务器 */ const reportError = (error, type = 'error') => { try { // 检查是否应该忽略此错误 if (shouldIgnoreError(error)) { console.warn('[错误上报] 忽略请求中止错误', error) return } const errorInfo = buildErrorInfo(error, type) // 打印到控制台 console.error('[错误上报]', errorInfo) // 优先使用 sendBeacon(最可靠) const beaconSuccess = reportByBeacon(errorInfo) if (beaconSuccess) { return } // 备用:使用 fetch reportByFetch(errorInfo) // 兜底:使用 uni.request(异步,可能失败) setTimeout(() => { reportByUni(errorInfo) }, 100) } catch (e) { console.error('[错误上报] 上报失败', e) } } /** * 初始化全局错误捕获 */ const initErrorHandler = () => { console.log('[错误上报] 初始化错误捕获') // 1. 捕获 JavaScript 运行时错误 window.addEventListener('error', (event) => { if (event.error) { reportError(event.error, 'error') } else { reportError({ message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno }, 'error') } }, true) // 2. 捕获 Promise 未处理的 rejection 错误 window.addEventListener('unhandledrejection', (event) => { reportError({ message: 'Promise rejection', reason: event.reason }, 'promise') // 阻止默认的控制台警告 // event.preventDefault() }) // 3. 捕获 Vue 错误(需要在 App.vue 中使用 app.config.errorHandler) } /** * 手动上报错误 * 用于主动上报一些业务错误 */ const report = (message, data = {}) => { const error = { message, ...data } reportError(error, 'manual') } export default { initErrorHandler, report, reportError }