befly-admin 3.14.5 → 3.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/main.js +1 -3
- package/src/plugins/report.js +372 -0
- package/src/plugins/router.js +0 -19
- package/src/plugins/errorReport.js +0 -128
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-admin",
|
|
3
|
-
"version": "3.14.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.14.7",
|
|
4
|
+
"gitHead": "c47d1e515dabe142531e0a8415979cccb2729259",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
|
|
7
7
|
"files": [
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"preview": "bunx --bun vite preview"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"befly-admin-ui": "^1.8.
|
|
31
|
+
"befly-admin-ui": "^1.8.34",
|
|
32
32
|
"befly-shared": "2.0.3",
|
|
33
33
|
"befly-vite": "^1.5.15",
|
|
34
34
|
"pinia": "^3.0.4",
|
package/src/main.js
CHANGED
|
@@ -5,7 +5,6 @@ import "befly-admin-ui/styles/variables.scss";
|
|
|
5
5
|
// 引入全局基础样式(reset、通用类、滚动条等)
|
|
6
6
|
import "@/styles/global.scss";
|
|
7
7
|
import App from "./App.vue";
|
|
8
|
-
import { setupErrorReport } from "@/plugins/errorReport.js";
|
|
9
8
|
|
|
10
9
|
const app = createApp(App);
|
|
11
10
|
|
|
@@ -14,7 +13,6 @@ app.use(createPinia());
|
|
|
14
13
|
|
|
15
14
|
// 使用路由
|
|
16
15
|
app.use($Router);
|
|
17
|
-
|
|
18
|
-
setupErrorReport(app);
|
|
16
|
+
setupReport(app);
|
|
19
17
|
|
|
20
18
|
app.mount("#app");
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { $Config } from "@/plugins/config.js";
|
|
2
|
+
import { $Http } from "@/plugins/http.js";
|
|
3
|
+
import { $Router } from "@/plugins/router.js";
|
|
4
|
+
|
|
5
|
+
const ONLINE_REPORT_MIN_GAP = 15 * 1000;
|
|
6
|
+
const ONLINE_REPORT_HEARTBEAT_INTERVAL = 60 * 1000;
|
|
7
|
+
const ERROR_REPORT_DEDUP_GAP = 3000;
|
|
8
|
+
|
|
9
|
+
let onlineReportHeartbeatStarted = false;
|
|
10
|
+
let lastOnlineReportTime = 0;
|
|
11
|
+
let onlineReportTimeoutId = 0;
|
|
12
|
+
let routeReportBound = false;
|
|
13
|
+
let isReportingError = false;
|
|
14
|
+
let lastErrorKey = "";
|
|
15
|
+
let lastErrorTime = 0;
|
|
16
|
+
let fetchWrapped = false;
|
|
17
|
+
|
|
18
|
+
function getRouteReportData(route) {
|
|
19
|
+
return {
|
|
20
|
+
pagePath: route?.fullPath || route?.path || "",
|
|
21
|
+
pageName: String(route?.name || route?.meta?.title || route?.path || ""),
|
|
22
|
+
source: "admin",
|
|
23
|
+
productName: $Config.productName,
|
|
24
|
+
productCode: $Config.productCode,
|
|
25
|
+
productVersion: $Config.productVersion
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getCurrentRouteInfo() {
|
|
30
|
+
const currentRoute = $Router.currentRoute.value || {};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
pagePath: String(currentRoute.fullPath || currentRoute.path || window.location.hash || window.location.pathname || ""),
|
|
34
|
+
pageName: String(currentRoute.name || currentRoute.meta?.title || currentRoute.path || document.title || "")
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function reportOnline(route) {
|
|
39
|
+
return $Http("/core/tongJi/onlineReport", getRouteReportData(route), [""]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function reportInfo(route) {
|
|
43
|
+
return $Http("/core/tongJi/infoReport", getRouteReportData(route), [""]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function triggerActiveOnlineReport(force = false) {
|
|
47
|
+
if (typeof document === "undefined") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (document.visibilityState !== "visible") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof document.hasFocus === "function" && document.hasFocus() !== true) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
|
|
61
|
+
if (!force && now - lastOnlineReportTime < ONLINE_REPORT_MIN_GAP) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lastOnlineReportTime = now;
|
|
66
|
+
|
|
67
|
+
const currentRoute = $Router.currentRoute.value;
|
|
68
|
+
|
|
69
|
+
void reportOnline(currentRoute).catch(() => {
|
|
70
|
+
// 静默失败:不阻断页面交互
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function queueOnlineReportHeartbeat() {
|
|
75
|
+
if (typeof window === "undefined") {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (onlineReportTimeoutId) {
|
|
80
|
+
window.clearTimeout(onlineReportTimeoutId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onlineReportTimeoutId = window.setTimeout(() => {
|
|
84
|
+
triggerActiveOnlineReport();
|
|
85
|
+
queueOnlineReportHeartbeat();
|
|
86
|
+
}, ONLINE_REPORT_HEARTBEAT_INTERVAL);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function setupOnlineReportHeartbeat() {
|
|
90
|
+
if (onlineReportHeartbeatStarted || typeof window === "undefined") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onlineReportHeartbeatStarted = true;
|
|
95
|
+
|
|
96
|
+
window.addEventListener("focus", () => {
|
|
97
|
+
triggerActiveOnlineReport();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
document.addEventListener("visibilitychange", () => {
|
|
101
|
+
triggerActiveOnlineReport();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
triggerActiveOnlineReport(true);
|
|
105
|
+
queueOnlineReportHeartbeat();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function bindRouteReport() {
|
|
109
|
+
if (routeReportBound) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
routeReportBound = true;
|
|
114
|
+
|
|
115
|
+
$Router.afterEach((to) => {
|
|
116
|
+
lastOnlineReportTime = Date.now();
|
|
117
|
+
|
|
118
|
+
void Promise.allSettled([reportOnline(to), reportInfo(to)]).catch(() => {
|
|
119
|
+
// 静默失败:不阻断路由切换
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getErrorMessage(error) {
|
|
125
|
+
if (typeof error === "string") {
|
|
126
|
+
return error;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (error && typeof error.message === "string") {
|
|
130
|
+
return error.message;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return "未知错误";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getErrorDetail(error) {
|
|
137
|
+
if (!error) {
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (typeof error === "string") {
|
|
142
|
+
return error;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (typeof error.stack === "string" && error.stack) {
|
|
146
|
+
return error.stack;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
return JSON.stringify(error);
|
|
151
|
+
} catch {
|
|
152
|
+
return String(error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function shouldSkipError(message, detail, errorType) {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
const key = `${errorType}|${message}|${detail}`;
|
|
159
|
+
|
|
160
|
+
if (key === lastErrorKey && now - lastErrorTime < ERROR_REPORT_DEDUP_GAP) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lastErrorKey = key;
|
|
165
|
+
lastErrorTime = now;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function reportClientError(errorType, message, detail) {
|
|
170
|
+
const safeMessage = String(message || "未知错误");
|
|
171
|
+
const safeDetail = String(detail || "");
|
|
172
|
+
|
|
173
|
+
if (shouldSkipError(safeMessage, safeDetail, errorType)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (isReportingError) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const routeInfo = getCurrentRouteInfo();
|
|
182
|
+
isReportingError = true;
|
|
183
|
+
|
|
184
|
+
void $Http(
|
|
185
|
+
"/core/tongJi/errorReport",
|
|
186
|
+
{
|
|
187
|
+
pagePath: routeInfo.pagePath,
|
|
188
|
+
pageName: routeInfo.pageName,
|
|
189
|
+
source: "admin",
|
|
190
|
+
productName: $Config.productName,
|
|
191
|
+
productCode: $Config.productCode,
|
|
192
|
+
productVersion: $Config.productVersion,
|
|
193
|
+
errorType: String(errorType || "unknown"),
|
|
194
|
+
message: safeMessage,
|
|
195
|
+
detail: safeDetail
|
|
196
|
+
},
|
|
197
|
+
[""]
|
|
198
|
+
)
|
|
199
|
+
.catch(() => {
|
|
200
|
+
// 静默失败:避免错误上报再次制造错误
|
|
201
|
+
})
|
|
202
|
+
.finally(() => {
|
|
203
|
+
isReportingError = false;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function setupFetchErrorReport() {
|
|
208
|
+
if (fetchWrapped) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (typeof window.fetch !== "function") {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const originalFetch = window.fetch.bind(window);
|
|
217
|
+
|
|
218
|
+
window.fetch = async (...args) => {
|
|
219
|
+
const input = args[0];
|
|
220
|
+
const url = typeof input === "string" ? input : input && typeof input.url === "string" ? input.url : "";
|
|
221
|
+
const apiPath = String($Config.apiPath || "");
|
|
222
|
+
const isApiRequest = Boolean(apiPath) && String(url || "").startsWith(apiPath);
|
|
223
|
+
const isErrorReportRequest = String(url || "").includes("/core/tongJi/errorReport");
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const response = await originalFetch(...args);
|
|
227
|
+
|
|
228
|
+
if (isApiRequest && !isErrorReportRequest) {
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const parts = [];
|
|
231
|
+
|
|
232
|
+
if (url) {
|
|
233
|
+
parts.push(`url: ${url}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (response.status) {
|
|
237
|
+
parts.push(`status: ${response.status}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
reportClientError("http", `请求失败:HTTP ${response.status}`, parts.join("\n"));
|
|
241
|
+
} else {
|
|
242
|
+
try {
|
|
243
|
+
const payload = await response.clone().json();
|
|
244
|
+
|
|
245
|
+
if (payload?.code !== 0) {
|
|
246
|
+
const parts = [];
|
|
247
|
+
|
|
248
|
+
if (url) {
|
|
249
|
+
parts.push(`url: ${url}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (response.status) {
|
|
253
|
+
parts.push(`status: ${response.status}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (payload?.code !== undefined) {
|
|
257
|
+
parts.push(`code: ${payload.code}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (payload?.msg) {
|
|
261
|
+
parts.push(`msg: ${payload.msg}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (payload?.detail) {
|
|
265
|
+
parts.push(`detail: ${payload.detail}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
reportClientError("http", payload.msg || "请求失败", parts.join("\n"));
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// 忽略非 JSON 响应
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return response;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (isApiRequest && !isErrorReportRequest) {
|
|
279
|
+
const parts = [];
|
|
280
|
+
|
|
281
|
+
if (url) {
|
|
282
|
+
parts.push(`url: ${url}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const errorDetail = getErrorDetail(error);
|
|
286
|
+
|
|
287
|
+
if (errorDetail) {
|
|
288
|
+
parts.push(`detail: ${errorDetail}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
reportClientError("http", getErrorMessage(error), parts.join("\n"));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
fetchWrapped = true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function setupErrorReport(app) {
|
|
302
|
+
setupFetchErrorReport();
|
|
303
|
+
|
|
304
|
+
app.config.errorHandler = (error, _instance, info) => {
|
|
305
|
+
const message = getErrorMessage(error);
|
|
306
|
+
const detail = `${String(info || "")}${info ? "\n" : ""}${getErrorDetail(error)}`;
|
|
307
|
+
|
|
308
|
+
reportClientError("vue", message, detail);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
window.addEventListener(
|
|
312
|
+
"error",
|
|
313
|
+
(event) => {
|
|
314
|
+
const target = event.target;
|
|
315
|
+
|
|
316
|
+
if (target && target !== window) {
|
|
317
|
+
const resourceName = target.tagName ? `${String(target.tagName).toLowerCase()} 资源加载失败` : "资源加载失败";
|
|
318
|
+
const parts = [];
|
|
319
|
+
|
|
320
|
+
if (target?.tagName) {
|
|
321
|
+
parts.push(`tag: ${String(target.tagName).toLowerCase()}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (target?.src) {
|
|
325
|
+
parts.push(`src: ${target.src}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (target?.href) {
|
|
329
|
+
parts.push(`href: ${target.href}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
reportClientError("resource", resourceName, parts.join("\n"));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const parts = [];
|
|
337
|
+
|
|
338
|
+
if (event.filename) {
|
|
339
|
+
parts.push(`file: ${event.filename}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (event.lineno || event.colno) {
|
|
343
|
+
parts.push(`line: ${event.lineno || 0}, col: ${event.colno || 0}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const stack = getErrorDetail(event.error);
|
|
347
|
+
|
|
348
|
+
if (stack) {
|
|
349
|
+
parts.push(stack);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
reportClientError("window", getErrorMessage(event.message || event.error), parts.join("\n"));
|
|
353
|
+
},
|
|
354
|
+
true
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
358
|
+
const reason = event.reason;
|
|
359
|
+
|
|
360
|
+
reportClientError("promise", getErrorMessage(reason), getErrorDetail(reason));
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
$Router.onError((error) => {
|
|
364
|
+
reportClientError("router", getErrorMessage(error), getErrorDetail(error));
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function setupReport(app) {
|
|
369
|
+
bindRouteReport();
|
|
370
|
+
setupOnlineReportHeartbeat();
|
|
371
|
+
setupErrorReport(app);
|
|
372
|
+
}
|
package/src/plugins/router.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Layouts } from "befly-vite";
|
|
|
2
2
|
import { createRouter, createWebHashHistory } from "vue-router";
|
|
3
3
|
import { routes } from "vue-router/auto-routes";
|
|
4
4
|
import { $Config } from "@/plugins/config.js";
|
|
5
|
-
import { $Http } from "@/plugins/http.js";
|
|
6
5
|
|
|
7
6
|
// 应用自定义布局系统(同时可选注入根路径重定向)
|
|
8
7
|
const finalRoutes = Layouts(routes, $Config.homePath, (layoutName) => {
|
|
@@ -53,21 +52,3 @@ $Router.beforeEach((to, _from) => {
|
|
|
53
52
|
|
|
54
53
|
return true;
|
|
55
54
|
});
|
|
56
|
-
|
|
57
|
-
// 路由就绪后处理
|
|
58
|
-
$Router.afterEach((to) => {
|
|
59
|
-
void $Http(
|
|
60
|
-
"/core/tongJi/visitReport",
|
|
61
|
-
{
|
|
62
|
-
pagePath: to.fullPath || to.path || "",
|
|
63
|
-
pageName: String(to.name || to.meta?.title || to.path || ""),
|
|
64
|
-
source: "admin",
|
|
65
|
-
productName: $Config.productName,
|
|
66
|
-
productCode: $Config.productCode,
|
|
67
|
-
productVersion: $Config.productVersion
|
|
68
|
-
},
|
|
69
|
-
[""]
|
|
70
|
-
).catch(() => {
|
|
71
|
-
// 静默失败:不阻断路由切换
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { $Http } from "@/plugins/http.js";
|
|
2
|
-
import { $Config } from "@/plugins/config.js";
|
|
3
|
-
import { $Router } from "@/plugins/router.js";
|
|
4
|
-
|
|
5
|
-
let isReportingError = false;
|
|
6
|
-
let lastErrorKey = "";
|
|
7
|
-
let lastErrorTime = 0;
|
|
8
|
-
|
|
9
|
-
function getRouteInfo() {
|
|
10
|
-
const currentRoute = $Router.currentRoute.value || {};
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
pagePath: String(currentRoute.fullPath || currentRoute.path || window.location.hash || window.location.pathname || ""),
|
|
14
|
-
pageName: String(currentRoute.name || currentRoute.meta?.title || currentRoute.path || document.title || "")
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getErrorMessage(error) {
|
|
19
|
-
if (typeof error === "string") {
|
|
20
|
-
return error;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (error && typeof error.message === "string") {
|
|
24
|
-
return error.message;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return "未知错误";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getErrorDetail(error) {
|
|
31
|
-
if (!error) {
|
|
32
|
-
return "";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (typeof error === "string") {
|
|
36
|
-
return error;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (typeof error.stack === "string" && error.stack) {
|
|
40
|
-
return error.stack;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
return JSON.stringify(error);
|
|
45
|
-
} catch {
|
|
46
|
-
return String(error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function shouldSkipError(message, detail, errorType) {
|
|
51
|
-
const now = Date.now();
|
|
52
|
-
const key = `${errorType}|${message}|${detail}`;
|
|
53
|
-
if (key === lastErrorKey && now - lastErrorTime < 3000) {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
lastErrorKey = key;
|
|
58
|
-
lastErrorTime = now;
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function reportClientError(errorType, message, detail) {
|
|
63
|
-
const safeMessage = String(message || "未知错误").slice(0, 500);
|
|
64
|
-
const safeDetail = String(detail || "").slice(0, 5000);
|
|
65
|
-
if (shouldSkipError(safeMessage, safeDetail, errorType)) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (isReportingError) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const routeInfo = getRouteInfo();
|
|
74
|
-
isReportingError = true;
|
|
75
|
-
|
|
76
|
-
void $Http(
|
|
77
|
-
"/core/tongJi/errorReport",
|
|
78
|
-
{
|
|
79
|
-
pagePath: routeInfo.pagePath,
|
|
80
|
-
pageName: routeInfo.pageName,
|
|
81
|
-
source: "admin",
|
|
82
|
-
productName: $Config.productName,
|
|
83
|
-
productCode: $Config.productCode,
|
|
84
|
-
productVersion: $Config.productVersion,
|
|
85
|
-
errorType: String(errorType || "unknown"),
|
|
86
|
-
message: safeMessage,
|
|
87
|
-
detail: safeDetail
|
|
88
|
-
},
|
|
89
|
-
[""]
|
|
90
|
-
)
|
|
91
|
-
.catch(() => {
|
|
92
|
-
// 静默失败:避免错误上报再次制造错误
|
|
93
|
-
})
|
|
94
|
-
.finally(() => {
|
|
95
|
-
isReportingError = false;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function setupErrorReport(app) {
|
|
100
|
-
app.config.errorHandler = (error, _instance, info) => {
|
|
101
|
-
const message = getErrorMessage(error);
|
|
102
|
-
const detail = `${String(info || "")}${info ? "\n" : ""}${getErrorDetail(error)}`;
|
|
103
|
-
|
|
104
|
-
reportClientError("vue", message, detail);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
window.addEventListener("error", (event) => {
|
|
108
|
-
const parts = [];
|
|
109
|
-
if (event.filename) {
|
|
110
|
-
parts.push(`file: ${event.filename}`);
|
|
111
|
-
}
|
|
112
|
-
if (event.lineno || event.colno) {
|
|
113
|
-
parts.push(`line: ${event.lineno || 0}, col: ${event.colno || 0}`);
|
|
114
|
-
}
|
|
115
|
-
const stack = getErrorDetail(event.error);
|
|
116
|
-
if (stack) {
|
|
117
|
-
parts.push(stack);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
reportClientError("window", getErrorMessage(event.message || event.error), parts.join("\n"));
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
window.addEventListener("unhandledrejection", (event) => {
|
|
124
|
-
const reason = event.reason;
|
|
125
|
-
|
|
126
|
-
reportClientError("promise", getErrorMessage(reason), getErrorDetail(reason));
|
|
127
|
-
});
|
|
128
|
-
}
|