扫码自动打开套餐功能

main
岩仔88 3 weeks ago
parent 9a50a853ea
commit 04420a43f1

@ -4,17 +4,14 @@
* usersa0ChunLuyu
* date2024年8月7日 20:05:05
*/
import {
ref
} from 'vue'
import {
$api,
$response,
} from '@/api'
import {
onShow,
onHide,
onLoad,onError
onLoad,
onError
} from '@dcloudio/uni-app'
import {
@ -22,50 +19,107 @@
} from '@/store'
const $store = useStore()
//
try {
if (typeof uni.$lu !== 'undefined' && uni.$lu.errorReport) {
//
uni.$lu.errorReport.initErrorHandler()
console.log('[错误上报] 已初始化')
// index.html
if (typeof window.flushErrorBuffer === 'function') {
try {
var earlyErrors = window.flushErrorBuffer()
if (earlyErrors && earlyErrors.length > 0) {
console.log('[错误上报] 早期错误数量:', earlyErrors.length)
//
earlyErrors.forEach(function(err) {
try {
uni.$lu.errorReport.reportError(err, 'early-error')
} catch (e) {
console.warn('[错误上报] 重新上报早期错误失败', e)
}
})
}
} catch (e) {
console.warn('[错误上报] 处理早期错误缓冲区失败', e)
}
}
}
} catch (e) {
console.warn('[错误上报] 初始化失败', e)
}
const setConfigStore = () => {
let config = {}
try {
const config_str = uni.getStorageSync('CONFIG_CONFIG')
if (config_str) config = JSON.parse(config_str)
} catch (e) {
uni.showToast({
icon:"none",
title:e.message || '解析配置失败'
})
console.warn('CONFIG_CONFIG 解析失败', e)
}
$store.config = config
if (!config.color) {
try {
// document.body
if (document && document.body && !config.color) {
document.body.classList.toggle('grayscale');
}
const openid_str = uni.getStorageSync('OPENID')
let url = window.location.href
} catch (e) {
console.warn('设置样式失败', e)
}
let openid_str = ''
try {
openid_str = uni.getStorageSync('OPENID')
} catch (e) {
console.warn('获取 OPENID 失败', e)
}
let url = ''
try {
url = window.location.href
} catch (e) {
console.warn('获取 URL 失败', e)
}
if (!openid_str) {
if (url.indexOf('/pages/main/login/login') === -1) {
if (url && url.indexOf('/pages/main/login/login') === -1) {
uni.redirectTo({
url: '/pages/main/login/login'
})
}
}
try {
const save_info_str = uni.getStorageSync('SAVE_INFO')
if (!!save_info_str) {
$store.save_info = JSON.parse(save_info_str)
} else {
$store.resetSaveInfo()
}
} catch (e) {
console.warn('解析 SAVE_INFO 失败', e)
$store.resetSaveInfo()
}
}
const getConfigConfig = async () => {
try {
const response = await $api('ConfigConfig')
$response(response, () => {
try {
uni.setStorageSync('CONFIG_CONFIG', JSON.stringify(response.data.config))
setConfigStore()
} catch (e) {
console.error('保存配置失败', e)
}
})
} catch (e) {
console.error('获取配置接口失败', e)
}
}
const getConfigVersion = async () => {
try {
const response = await $api('ConfigVersion')
$response(response, () => {
try {
const config_version = uni.getStorageSync('CONFIG_VERSION')
let get_config = false
if (!config_version) {
@ -82,9 +136,16 @@
} else {
setConfigStore()
}
} catch (e) {
console.error('处理版本配置失败', e)
}
})
} catch (e) {
console.error('获取版本接口失败', e)
}
}
const handleFontSize = () => {
try {
//
WeixinJSBridge.invoke('setFontSizeCallback', {
'fontSize': 0
@ -95,23 +156,61 @@
'fontSize': 0
});
});
} catch (e) {
console.warn('设置字体大小失败', e)
}
}
onShow(() => {
try {
if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
handleFontSize();
} else {
if (document.addEventListener) {
try {
if (document && document.addEventListener) {
document.addEventListener("WeixinJSBridgeReady", handleFontSize, false);
} else if (document.attachEvent) {
} else if (document && document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", handleFontSize);
document.attachEvent("onWeixinJSBridgeReady", handleFontSize);
}
} catch (e) {
console.warn('监听 WeixinJSBridge 失败', e)
}
}
} catch (e) {
console.warn('onShow 初始化失败', e)
}
console.log(`\n %c 鹿和 %c https://sa0.online/ \n\n`, 'color: #ffffff; background: #fd6b60; padding:5px 0;',
'color: #fd6b60;background: #ffffff; padding:5px 0;')
try {
getConfigVersion()
} catch (e) {
console.error('获取配置版本失败', e)
}
})
onError((err) => {
console.error('[全局错误]', err)
// iOS WebView loading
const isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent)
if (isIOS) {
try {
uni.hideLoading()
console.log('[iOS清理] 强制关闭loading')
} catch (e) {
console.warn('[iOS清理loading失败]', e)
}
}
//
try {
if (typeof uni.$lu !== 'undefined' && uni.$lu.errorReport) {
uni.$lu.errorReport.reportError(err, 'uni-app-error')
}
} catch (e) {
console.warn('[错误上报] 上报失败', e)
}
})
onLoad(()=>{

@ -0,0 +1,282 @@
# H5 错误上报功能说明
## 概述
已为H5项目添加了全局错误上报功能能够捕获和上报前端运行时错误即使发生白屏也能获取到错误信息。
## 错误上报接口
- **接口地址**: `https://tj-h5.hnxdfe.com/api/H5/test`
- **请求方式**: POST
- **Content-Type**: application/json
## 上报的错误类型
### 1. JavaScript 运行时错误 (type: 'error')
- 语法错误
- 引用错误
- 类型错误
- 范围错误等
### 2. Promise 未处理错误 (type: 'promise')
- Promise rejection 错误
- async/await 未捕获的异常
### 3. uni-app 错误 (type: 'uni-app-error')
- uni-app 生命周期错误
- 页面加载错误
### 4. 早期错误 (type: 'error' / phase: 'early')
- 应用初始化前的错误
- 脚本加载错误
- 白屏问题的主要来源
### 5. 手动上报错误 (type: 'manual')
- 业务逻辑中的自定义错误
## 错误数据格式
```json
{
"type": "error", // 错误类型
"message": "xxx is not defined", // 错误信息
"stack": "Error: xxx...", // 堆栈信息
"url": "https://...", // 当前页面URL
"ua": "Mozilla/5.0...", // 用户代理
"timestamp": "2024-01-01T00:00:00.000Z", // 时间戳
"openId": "xxxxx", // 用户OpenID如果存在
"phase": "early" // 阶段标识(仅早期错误)
}
```
## 功能特性
### 1. 多重上报保障
错误上报采用三层保障机制,确保即使白屏也能上报:
1. **优先使用 navigator.sendBeacon**
- 即使页面关闭也能发送
- 不阻塞页面卸载
- 最可靠的方式
2. **备用方案 fetch with keepalive**
- keepalive 选项保证请求完成
- 适用于大多数浏览器
3. **兜底方案 uni.request**
- 使用 uni-app 的请求方法
- 100ms 延迟避免阻塞
### 2. 早期错误捕获
- 在 `index.html` 中尽早初始化错误监听
- 捕获应用初始化前的错误
- 使用错误缓冲区存储早期错误
- 应用加载后统一重新上报
### 3. 性能监控
自动上报以下性能指标:
- `loadTime`: 页面总加载时间
- `domReadyTime`: DOM就绪时间
- `firstPaint`: 首次渲染时间
## 使用方法
### 自动捕获(默认开启)
所有错误会自动上报,无需额外配置。
### 手动上报错误
```javascript
// 在业务代码中手动上报
uni.$lu.errorReport.report('自定义错误信息', {
// 额外的业务数据
userId: '123',
action: 'submitOrder'
})
```
### 手动上报错误对象
```javascript
try {
// 可能出错的操作
} catch (error) {
uni.$lu.errorReport.reportError(error, 'custom-error')
}
```
### 上报性能数据
```javascript
uni.$lu.errorReport.reportPerformance({
customMetric: 1234,
operation: 'loadData'
})
```
## 日志输出
在开发环境中,可以在控制台看到以下日志:
```
[早期错误捕获] 已初始化
[错误上报] 已初始化
[错误上报] 早期错误数量: 2
[错误上报] { type: 'error', message: '...', ... }
```
## 后端处理建议
### 1. 数据存储
建议将错误数据存储到数据库或日志系统:
- MongoDB: 适合存储灵活的JSON数据
- MySQL/PostgreSQL: 可以使用JSON字段
- ELK Stack: 适合日志分析和检索
### 2. 错误统计
可以统计以下指标:
- 错误类型分布
- 错误发生频率
- 受影响的用户数
- 错误来源页面
- iOS vs Android 错误对比
### 3. 告警机制
可以设置告警规则:
- 某类错误短时间内激增
- 同一用户连续报错
- 白屏类型错误phase: 'early'
## 调试方法
### 1. 测试错误上报
在浏览器控制台执行:
```javascript
// 触发一个错误
throw new Error('测试错误上报')
```
### 2. 查看网络请求
打开开发者工具 -> Network -> 查看POST请求到 `/api/H5/test` 的数据
### 3. 查看早期错误缓冲区
```javascript
console.log(window.__ERROR_BUFFER__)
```
### 4. 小程序 WebView 设备调试(重要)
由于小程序 WebView 无法查看控制台,已添加以下调试功能:
#### 调试按钮
页面右下角会显示三个调试按钮:
1. **🐛 查看日志**(红色)- 切换日志面板显示/隐藏
2. **🧪 测试**(绿色)- 测试日志功能是否正常工作
3. **🗑️ 清空**(橙色)- 清空所有日志
#### 自动显示日志
- 发生错误时,页面顶部会自动显示黑色半透明的调试面板
- 面板中会显示完整的错误信息和调试日志
- 点击 "🐛 查看日志" 按钮可手动切换显示状态
#### 日志内容
日志面板中会显示:
- `[时间] [原始error事件]` - 原始错误事件的详细信息
- `[时间] [早期错误捕获]` - 处理后的错误信息
- `[时间] 调试系统已初始化` - 调试系统启动时的测试日志
- 所有调试日志的时间戳和详细信息
#### 测试日志功能
1. 点击 **🧪 测试** 按钮
2. 如果日志面板出现并显示测试信息,说明日志功能正常
3. 如果点击没反应,可能是被小程序 WebView 的安全策略限制
#### 获取完整日志
如果需要将日志发送给开发者:
方法1截图
- 将调试面板的内容截图
方法2复制日志
```javascript
// 在浏览器控制台执行(需要能访问控制台)
console.log(JSON.stringify(window.__DEBUG_LOGS__, null, 2))
```
方法3通过小程序调试工具
- 如果小程序有调试模式,可以通过调试工具访问 `window.__DEBUG_LOGS__`
#### 调试面板特性
- 自动滚动到最新日志
- 每条日志有分隔线,方便阅读
- 自动换行,长文本不会溢出
- 点击 "🐛 查看日志" 按钮切换显示/隐藏
## 注意事项
1. **隐私保护**: 错误数据不包含敏感信息,但建议后端做好脱敏处理
2. **性能影响**: 错误上报使用异步方式,不影响页面性能
3. **网络限制**: 如果网络不可用,错误会失败但不影响应用运行
4. **跨域问题**: 确保接口支持 CORS 或在同一域名下
## iOS 白屏问题排查
通过错误上报,可以收集以下信息来排查 iOS 白屏问题:
1. **早期错误 (phase: 'early')**
- 查看应用初始化前的错误
- 可能是脚本加载或语法错误
2. **用户代理 (ua)**
- 识别具体的 iOS 版本
- 区分不同的 WebView 环境
3. **错误堆栈 (stack)**
- 精确定位错误位置
- 了解错误调用链
4. **URL 信息**
- 判断是否特定页面白屏
- 检查路由参数是否正确
## 实际案例分析
### 案例1: iOS 14.5 微信小程序白屏
```json
{
"type": "error",
"message": "{}",
"stack": null,
"url": "https://tj-h5.hnxdfe.com/h5/#/pages/main/login/login?openid=oosgJj-yr2IrwSmBb3_ERy1hGMos",
"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.63(0x18003f2f) NetType/4G Language/zh_CN miniProgram/wxece13dd0a5b22c73",
"timestamp": "2026-02-05T11:43:07.707Z",
"phase": "early"
}
```
**问题分析**
- 错误信息为 `{}`,堆栈为 `null`
- 说明错误对象构建时出错,可能是早期错误对象结构异常
- 发生在登录页面URL 中带有 openid 参数
- 环境是 iOS 14.5 + 微信 8.0.63 + 小程序 WebView
**可能原因**
1. 微信小程序 WebView 在早期阶段触发了 error 事件
2. error 事件对象的结构与预期不同
3. 某些初始化脚本在特定环境下执行失败
**已优化**
- 增强了 `buildErrorInfo` 函数,处理更多异常情况
- 添加了对 null/undefined 的特殊处理
- JSON.stringify 失败时的降级处理
- 确保 message 和 stack 永远不会是 null 或空对象
**建议后续观察**
- 检查是否还有类似 `{}``null` 的错误信息
- 统计 iOS 14.5 的错误频率
- 检查登录页面是否是白屏高发页面
- 检查 openid 参数处理是否正常
## 扩展建议
未来可以考虑:
1. 添加错误采样率(避免上报过多)
2. 添加用户反馈功能(用户主动报告问题)
3. 集成第三方错误监控(如 Sentry
4. 添加错误聚合和去重机制

@ -17,33 +17,66 @@ export const $url = (url_key) => {
}
}
export const $api = async (url_key, data = {}, opt = {}) => {
let loadingTimer = null
try {
const opt_data = {
...$config,
...opt,
}
const $store = useStore()
// 防止loading一直显示的兜底机制30秒后强制关闭
loadingTimer = setTimeout(() => {
try {
uni.hideLoading()
console.warn('[api] 强制关闭loading超时保护')
} catch (e) {
console.warn('[api] 强制关闭loading失败', e)
}
}, 30000)
if (!(url_key in $store.api_map)) {
const api_map = await $post({
url: opt_data.config.api_map_url
}, opt_data)
if (!api_map.status) {
if (typeof uni.$lu !== 'undefined' && uni.$lu.toast) {
uni.$lu.toast('获取接口失败')
}
if (loadingTimer) clearTimeout(loadingTimer)
return false
}
$store.api_map = api_map.data.list
}
if (!(url_key in $store.api_map)) {
if (typeof uni.$lu !== 'undefined' && uni.$lu.toast) {
uni.$lu.toast(`接口不存在 [${url_key}]`)
}
if (loadingTimer) clearTimeout(loadingTimer)
return false
}
const openid = uni.getStorageSync('OPENID')
if (!!openid) {
data.openid = openid
}
return await $post({
const result = await $post({
url: $store.api_map[url_key],
data
}, opt_data)
// 清除定时器
if (loadingTimer) clearTimeout(loadingTimer)
return result
} catch (e) {
console.error('[api] API调用失败', url_key, e)
// 清除定时器
if (loadingTimer) clearTimeout(loadingTimer)
throw e
}
}
export const $image = (path) => {

@ -8,7 +8,234 @@
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script><title></title>
var _window = typeof window !== 'undefined' ? window : {};
</script>
<!-- 早期错误捕获 - 在应用初始化之前就开始监听错误 -->
<script>
(function() {
'use strict';
// 错误上报接口
var ERROR_REPORT_API = 'https://tj-h5.hnxdfe.com/api/H5/test';
// 临时存储错误,等到应用初始化后统一上报
window.__ERROR_BUFFER__ = [];
// 判断是否应该忽略的错误
function 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;
}
// 发送错误到服务器
function sendError(errorInfo) {
try {
// 优先使用 sendBeacon即使页面关闭也能发送
if (navigator.sendBeacon) {
var data = JSON.stringify(errorInfo);
var blob = new Blob([data], { type: 'application/json' });
return navigator.sendBeacon(ERROR_REPORT_API, blob);
}
// 备用:使用 fetch
fetch(ERROR_REPORT_API, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errorInfo),
keepalive: true
}).catch(function() {
// 静默失败
});
} catch (e) {
// 静默失败,避免二次错误
}
}
// 构建错误信息
function buildErrorInfo(error, type) {
var errorInfo = {
type: type || 'error',
message: '',
stack: '',
url: '',
ua: '',
timestamp: new Date().toISOString(),
phase: 'early', // 标记为早期错误
rawError: null // 保留原始错误对象(用于调试)
};
try {
errorInfo.url = window.location.href || '';
errorInfo.ua = navigator.userAgent || '';
// 如果是 Error 实例
if (error instanceof Error) {
errorInfo.message = error.message || String(error);
errorInfo.stack = error.stack || '';
}
// 如果是字符串
else if (typeof error === 'string') {
errorInfo.message = error;
}
// 如果是对象
else if (error && typeof error === 'object') {
try {
// 检查 message 是否是 "{}" 或空字符串
var 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') {
var str = error.toString();
if (str && str !== '[object Object]') {
errorInfo.message = str;
} else {
errorInfo.message = '未知错误对象(无有效信息)';
}
} else {
// 尝试序列化对象
var 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;
if (error.lineno) {
errorInfo.stack += ':' + error.lineno;
if (error.colno) {
errorInfo.stack += ':' + error.colno;
}
}
}
} catch (e) {
errorInfo.message = '解析错误对象失败: ' + String(e);
errorInfo.stack = '';
}
}
// 如果是 null 或 undefined
else if (error === null || error === undefined) {
errorInfo.message = '错误对象为 ' + String(error);
}
// 其他情况
else {
errorInfo.message = String(error) || '未知错误';
errorInfo.stack = '';
}
// 保留原始错误对象的前几个键(用于调试)
if (error && typeof error === 'object') {
var keys = Object.keys(error).slice(0, 5);
if (keys.length > 0) {
errorInfo.rawError = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof error[key] !== 'function') {
errorInfo.rawError[key] = String(error[key]).substring(0, 100);
}
}
}
}
// 如果 message 还是空的或 {}
if (!errorInfo.message || errorInfo.message === '{}' || errorInfo.message === 'undefined') {
errorInfo.message = '无法获取错误详情(错误对象为空或无效)';
}
} catch (e) {
errorInfo.message = '构建错误信息异常: ' + String(e);
errorInfo.stack = '';
}
return errorInfo;
}
// 捕获 JavaScript 错误
window.addEventListener('error', function(event) {
// 检查是否应该忽略此错误
var errorToCheck = event.error || event;
if (shouldIgnoreError(errorToCheck)) {
console.warn('[早期错误捕获] 忽略请求中止错误', errorToCheck);
return;
}
var errorInfo;
if (event.error) {
errorInfo = buildErrorInfo(event.error, 'error');
} else {
errorInfo = buildErrorInfo({
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno
}, 'error');
}
// 发送错误
sendError(errorInfo);
// 同时存储到缓冲区
window.__ERROR_BUFFER__.push(errorInfo);
}, true);
// 捕获 Promise rejection 错误
window.addEventListener('unhandledrejection', function(event) {
// 检查是否应该忽略此错误
if (shouldIgnoreError(event.reason)) {
console.warn('[早期错误捕获] 忽略请求中止错误', event.reason);
return;
}
var errorInfo = buildErrorInfo({
message: 'Promise rejection',
reason: event.reason
}, 'promise');
// 发送错误
sendError(errorInfo);
// 同时存储到缓冲区
window.__ERROR_BUFFER__.push(errorInfo);
});
// 上报缓冲区的错误(供后续应用调用)
window.flushErrorBuffer = function() {
if (window.__ERROR_BUFFER__ && window.__ERROR_BUFFER__.length > 0) {
var buffer = window.__ERROR_BUFFER__;
window.__ERROR_BUFFER__ = [];
return buffer;
}
return [];
};
console.log('[早期错误捕获] 已初始化');
})();
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>

@ -8,6 +8,7 @@ export const $post = async ({
url,
data = {}
}, opt) => {
try {
const $store = useStore()
let header = {}
if ('delete_token' in opt && !!opt.delete_token) {
@ -32,26 +33,53 @@ export const $post = async ({
} else {
data['UNIAPP_APPTYPE'] = opt.app_type
}
let isShowLoading = false
try {
if (!!opt.loading) {
$store.loadingStart()
if ($store.loading === 1) uni.showLoading({
isShowLoading = true
if ($store.loading === 1) {
try {
uni.showLoading({
title: opt.loading_text
})
} catch (loadingErr) {
console.warn('[axios] showLoading失败', loadingErr)
}
}
}
const res = await uni.request({
url,
method: 'POST',
data,
header
});
if (!!opt.loading) {
return res.data
} finally {
// 使用finally确保一定会清理loading
if (isShowLoading) {
try {
$store.loadingDone()
if ($store.loading === 0) uni.hideLoading()
if ($store.loading === 0) {
uni.hideLoading()
}
if (!!res && res.data != '') {
return res.data
} else {
} catch (cleanupErr) {
console.warn('[axios] 清理loading失败', cleanupErr)
}
}
}
} catch (e) {
console.error('[axios] 请求失败', e)
if (typeof uni.$lu !== 'undefined' && uni.$lu.toast) {
try {
uni.$lu.toast("请求发生错误")
return false
} catch (toastErr) {
console.warn('[axios] toast失败', toastErr)
}
}
throw e
}
}

@ -0,0 +1,270 @@
/**
* 全局错误上报模块
* 用于捕获和上报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
}

@ -1,8 +1,10 @@
import config from '@/config.js'
import toast from './toast.js';
import format from './format.js';
import errorReport from './errorReport.js';
export default {
toast,
format,
config,
errorReport,
};

@ -38,6 +38,7 @@ import TabBar from "@/common/TabBar.vue";
};
const gettjbgInfo = async (index) => {
try {
tabIndex.value = index ? index : 0;
//
console.log(tabIndex.value);
@ -51,12 +52,15 @@ import TabBar from "@/common/TabBar.vue";
title: "加载中",
});
const response = await $api("GetReportList", obj);
uni.hideLoading();
$response(response, () => {
uni.hideLoading()
console.log(response, "response");
bgcx_list.value = response.data.list;
uni.hideLoading();
});
} catch (e) {
uni.hideLoading();
console.error('[获取体检报告列表失败]', e);
}
};
const config_ref = ref(null);

@ -38,12 +38,20 @@
});
const getUserInfo = async () => {
try {
const response = await $api('UserInfo')
$response(response, () => {
// $store.user = response.data.info
$store.setUser(response.data.info);
GetNoticeFunc()
})
} catch (e) {
console.error('[获取用户信息失败]', e)
//
if (typeof uni.$lu !== 'undefined' && uni.$lu.errorReport) {
uni.$lu.errorReport.reportError(e, 'UserInfo-error')
}
}
}
const switch_arr = [
// '/pages/main/index/index',
@ -233,10 +241,17 @@
//
let NoticeInfo=ref([])
const GetNoticeFunc = async () => {
try {
// uni.showLoading()
// axiosloading
const response = await $api("GetHomeNotice",{
person_id:$store.getUser().person_id,
id_number:$store.getUser().id_number
}, {
loading: true,
loading_text: '加载中...'
});
uni.showLoading();
const response = await $api("GetHomeNotice",{person_id:$store.getUser().person_id,id_number:$store.getUser().id_number});
uni.hideLoading();
$response(response, () => {
NoticeInfo.value=response.data
if (!!$props.path && $props.path=='order') {
@ -246,8 +261,18 @@
uni.navigateTo({
url: "/pages/main/order/order"+canshu
})
}else if(!!$props.path && $props.path=='combo'){
uni.navigateTo({
url: "/pages/main/combo/combo"
})
}
});
} catch (e) {
console.error('[获取首页通知失败]', e);
if (typeof uni.$lu !== 'undefined' && uni.$lu.errorReport) {
uni.$lu.errorReport.reportError(e, 'GetHomeNotice-error')
}
}
};
const getItemValue=(name) =>{
const item = NoticeInfo.value.find(item => item.label === name && item.value > 0);

@ -43,6 +43,7 @@ import TabBar from "@/common/TabBar.vue";
const $store = useStore()
const order_list = ref([])
const getOrderList = async () => {
try {
uni.showLoading()
const response = await $api('OrderList',{
searchInfo:searchInfo.value
@ -65,6 +66,14 @@ import TabBar from "@/common/TabBar.vue";
})
} catch (e) {
uni.hideLoading()
console.error('[订单列表加载失败]', e)
//
if (typeof uni.$lu !== 'undefined' && uni.$lu.errorReport) {
uni.$lu.errorReport.reportError(e, 'OrderList-error')
}
}
}
const getStatusAllow = (statusActive) => {
switch (statusActive) {

@ -805,7 +805,7 @@
}
.order_info_line_label_wrapper {
width: 140rpx;
width: 160rpx;
font-weight: 400;
font-size: 28rpx;
color: #101010;

Loading…
Cancel
Save