fl-web-component 1.3.7 → 1.3.8
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/README.md +4 -1
- package/dist/fl-web-component.common.js +463 -67
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -2
- package/packages/components/com-flcanvas/index.vue +1 -1
- package/packages/components/com-graphics/index.vue +9 -2
- package/src/main.js +2 -0
- package/src/utils/instance-parser.js +28 -28
- package/src/utils/mini-devtool.js +309 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
@charset "UTF-8";#fl-model[data-v-
|
|
1
|
+
@charset "UTF-8";#fl-model[data-v-67cfa148]{width:100%;height:100%;cursor:pointer}[data-v-67cfa148] .tips-label{width:60px;color:#000;font:12px Helvetica;margin-top:-3em;padding:5px;text-align:center;vertical-align:middle;background-color:khaki}[data-v-67cfa148] .measure-label{max-width:100px;margin-top:-1em;border:10px;border-radius:5px;padding:3px 10px;cursor:pointer;color:#009bea;background-color:#f4f4f4;box-shadow:0 1px 3px 1px rgba(0,0,0,.25)}[data-v-67cfa148] .circle-tag{width:10px;height:10px;margin-top:5px;border-radius:50%;background-color:#ff5000}[data-v-67cfa148] .measure-label-font{word-break:break-all}[data-v-67cfa148] .mark-label-img{padding-top:5px;width:20px;height:20px}#konva-container[data-v-2fa9e609]{z-index:3;width:100%;height:100%;cursor:pointer;overflow:hidden}span[data-v-f547d5c6]{font-weight:bolder}.text[data-v-f547d5c6]{margin-top:20px}.line[data-v-f547d5c6]{border-bottom:1px solid #dcdfe6;margin:20px 0}.center[data-v-f547d5c6]{display:flex;flex-direction:column;align-items:center}.center .cen span[data-v-f547d5c6],.center .top span[data-v-f547d5c6]{color:"#53a8ff";display:inline-block;width:30px;height:30px;text-align:center;line-height:30px;border:1px solid;padding:5px;margin-bottom:10px;background-color:#e9f3ff}.center .cen span[data-v-f547d5c6]{margin:10px}.button[data-v-f547d5c6]{display:flex;justify-content:end;margin-top:20px}@font-face{font-family:iconfont;src:url(//at.alicdn.com/t/font_3226805_qqvo3ag3r8.woff2?t=1646635700216) format("woff2"),url(//at.alicdn.com/t/font_3226805_qqvo3ag3r8.woff?t=1646635700216) format("woff"),url(//at.alicdn.com/t/font_3226805_qqvo3ag3r8.ttf?t=1646635700216) format("truetype")}.iconfont[data-v-f547d5c6]{font-family:iconfont!important;font-size:50px;font-style:normal;color:"#53a8ff";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-shubiao[data-v-f547d5c6]:before{content:""}#svg-tigger[data-v-0ec35ee4]{cursor:pointer;height:100%;width:100%}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fl-web-component",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"tip1": "仅调试本组件不涉及业务组件,请执行dev",
|
|
6
6
|
"dev": "vue-cli-service serve",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"watch": "vue-cli-service build --watch --mode production --target lib --name fl-web-component --formats commonjs ./src/main.js",
|
|
12
12
|
"build": "npm run lint && vue-cli-service build --target lib --name fl-web-component --formats commonjs ./src/main.js",
|
|
13
13
|
"build:test": "vue-cli-service build --target lib --name fl-web-component --formats commonjs ./src/main.js",
|
|
14
|
-
"registry:npm": "npm config set registry https://registry.npmjs.org/",
|
|
15
14
|
"publish:base": "npm run build && npm publish --registry https://registry.npmjs.org/",
|
|
16
15
|
"==": "=============================================================",
|
|
17
16
|
"tip3": "发版注意: 【修改bug执行publish:fix】【新增功能执行publish:feat】【新特性执行publish:perf】",
|
|
@@ -103,7 +103,7 @@ var activedSvgPan = {
|
|
|
103
103
|
var newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
|
|
104
104
|
this.scaleRatio = Math.ceil(newScale);
|
|
105
105
|
// 判断比例尺百分比
|
|
106
|
-
if (this.scaleRatio
|
|
106
|
+
if (this.scaleRatio <1 || this.scaleRatio >= 100) return;
|
|
107
107
|
// 设置放大缩小多少倍
|
|
108
108
|
konvaStage.scale({ x: newScale, y: newScale });
|
|
109
109
|
var newPos = {
|
|
@@ -279,7 +279,6 @@
|
|
|
279
279
|
});
|
|
280
280
|
// cameraControls.fitToSphere(scene, true); // TODO 待处理,先用 setModelCenter 进行定位
|
|
281
281
|
this.setModelCenter(modelGroup);
|
|
282
|
-
console.log(modelGroup);
|
|
283
282
|
this.$emit('modelLoaded');
|
|
284
283
|
// cameraControls.saveState();
|
|
285
284
|
}
|
|
@@ -390,7 +389,15 @@
|
|
|
390
389
|
},
|
|
391
390
|
setModelCenter(mesh) {
|
|
392
391
|
const box3 = new this.THREE.Box3();
|
|
393
|
-
|
|
392
|
+
mesh.traverseVisible(function (object) {
|
|
393
|
+
// 3. 只处理有几何体的网格 (Mesh)
|
|
394
|
+
if (object.isMesh) {
|
|
395
|
+
// 4. 使用 expandByObject 扩展包围盒
|
|
396
|
+
// 这个方法会计算 object 的世界坐标包围盒,并将其合并到 correctBoundingBox 中
|
|
397
|
+
box3.expandByObject(object);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
// box3.setFromObject(mesh);
|
|
394
401
|
const center = new this.THREE.Vector3();
|
|
395
402
|
box3.getCenter(center);
|
|
396
403
|
const size = box3.getSize(new this.THREE.Vector3());
|
package/src/main.js
CHANGED
|
@@ -4,6 +4,7 @@ import FLPerControl from '../packages/components/com-graphics/per-control.vue';
|
|
|
4
4
|
import FlSvg from '../packages/components/com-graphics/pid.vue';
|
|
5
5
|
import * as THREE from 'three';
|
|
6
6
|
import pkg from '../package.json';
|
|
7
|
+
import qtdevtools from './utils/mini-devtool'
|
|
7
8
|
|
|
8
9
|
const components = [FlModel, Fl2dcanvas, FLPerControl, FlSvg];
|
|
9
10
|
|
|
@@ -17,6 +18,7 @@ const install = Vue => {
|
|
|
17
18
|
// 支持浏览器环境直接引入
|
|
18
19
|
if (typeof window !== 'undefined') {
|
|
19
20
|
window.FLVersion = pkg.version;
|
|
21
|
+
window.FLDevTools = qtdevtools;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export default {
|
|
@@ -392,34 +392,34 @@ function draw3Dmodel(geom, instanceName, instanceCount, nColor, nOpacity) {
|
|
|
392
392
|
geometry.setAttribute('opacity', new THREE.InstancedBufferAttribute(opacities, 1));
|
|
393
393
|
|
|
394
394
|
// 自定义着色器逻辑
|
|
395
|
-
if (!customMaterial) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
395
|
+
// if (!customMaterial) {
|
|
396
|
+
// material.onBeforeCompile = shader => {
|
|
397
|
+
// // 添加顶点着色器输入
|
|
398
|
+
// shader.vertexShader = `
|
|
399
|
+
// in float opacity; // 实例透明度属性
|
|
400
|
+
// out float vAlpha;
|
|
401
|
+
// ${shader.vertexShader}
|
|
402
|
+
// `.replace(
|
|
403
|
+
// '#include <begin_vertex>',
|
|
404
|
+
// `
|
|
405
|
+
// #include <begin_vertex>
|
|
406
|
+
// vAlpha = opacity; // 传递透明度到片段着色器
|
|
407
|
+
// `
|
|
408
|
+
// );
|
|
409
|
+
|
|
410
|
+
// // 修改片段着色器
|
|
411
|
+
// shader.fragmentShader = `
|
|
412
|
+
// in float vAlpha;
|
|
413
|
+
// ${shader.fragmentShader}
|
|
414
|
+
// `.replace(
|
|
415
|
+
// '#include <alphatest_fragment>',
|
|
416
|
+
// `
|
|
417
|
+
// #include <alphatest_fragment>
|
|
418
|
+
// diffuseColor.a *= vAlpha; // 应用实例透明度
|
|
419
|
+
// `
|
|
420
|
+
// );
|
|
421
|
+
// };
|
|
422
|
+
// }
|
|
423
423
|
mesh = new THREE.InstancedMesh(geometry, material, instanceCount);
|
|
424
424
|
|
|
425
425
|
const { visible } = prop;
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// ========= Error & Console Panel (非阻塞右下角显示,包含 console.log 等) =========
|
|
2
|
+
export default function installErrorConsolePanel() {
|
|
3
|
+
// ========== 可配置项 ==========
|
|
4
|
+
const MAX_ENTRIES = 400;
|
|
5
|
+
const PANEL_ID = 'js-error-panel';
|
|
6
|
+
const COLLAPSE_LENGTH = 1200; // 超过多少字符时折叠显示
|
|
7
|
+
const CAPTURE_CONSOLE = true; // 是否捕获 console.log/info/warn/debug(保留原行为)
|
|
8
|
+
// ================================
|
|
9
|
+
|
|
10
|
+
// 安全 stringify(处理循环引用)
|
|
11
|
+
function getCircularReplacer() {
|
|
12
|
+
const seen = new WeakSet();
|
|
13
|
+
return (key, value) => {
|
|
14
|
+
if (typeof value === "object" && value !== null) {
|
|
15
|
+
if (seen.has(value)) return "[Circular]";
|
|
16
|
+
seen.add(value);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function safeStringify(x) {
|
|
22
|
+
try {
|
|
23
|
+
if (typeof x === "string") return x;
|
|
24
|
+
if (x instanceof Error) return x.stack || x.message || String(x);
|
|
25
|
+
return JSON.stringify(x, getCircularReplacer(), 2);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
try { return String(x); } catch (e2) { return "[unstringifiable]"; }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ========== Panel DOM 创建 ==========
|
|
32
|
+
function createPanel() {
|
|
33
|
+
if (document.getElementById(PANEL_ID)) return document.getElementById(PANEL_ID);
|
|
34
|
+
|
|
35
|
+
const panel = document.createElement('div');
|
|
36
|
+
panel.id = PANEL_ID;
|
|
37
|
+
Object.assign(panel.style, {
|
|
38
|
+
position: 'fixed',
|
|
39
|
+
right: '12px',
|
|
40
|
+
bottom: '12px',
|
|
41
|
+
width: '460px',
|
|
42
|
+
maxHeight: '45vh',
|
|
43
|
+
boxSizing: 'border-box',
|
|
44
|
+
overflow: 'hidden',
|
|
45
|
+
zIndex: 2147483647,
|
|
46
|
+
fontFamily: 'Menlo,Consolas,monospace',
|
|
47
|
+
fontSize: '12px',
|
|
48
|
+
color: '#fff',
|
|
49
|
+
borderRadius: '8px',
|
|
50
|
+
boxShadow: '0 6px 18px rgba(0,0,0,0.55)',
|
|
51
|
+
background: 'linear-gradient(180deg, rgba(0,0,0,0.86), rgba(24,24,24,0.86))',
|
|
52
|
+
display: 'flex',
|
|
53
|
+
flexDirection: 'column',
|
|
54
|
+
gap: '6px',
|
|
55
|
+
padding: '8px'
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// header
|
|
59
|
+
const header = document.createElement('div');
|
|
60
|
+
Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px' });
|
|
61
|
+
header.innerHTML = `<strong style="font-size:13px">JS Logs / Errors</strong><span style="opacity:.7;font-size:11px">(非阻塞)</span>`;
|
|
62
|
+
|
|
63
|
+
const btns = document.createElement('div');
|
|
64
|
+
|
|
65
|
+
const clearBtn = document.createElement('button');
|
|
66
|
+
clearBtn.textContent = '清空';
|
|
67
|
+
Object.assign(clearBtn.style, { marginLeft: '6px', cursor: 'pointer', fontSize: '12px' });
|
|
68
|
+
clearBtn.onclick = () => { entriesContainer.innerHTML = ''; entryCount = 0; updateStats(); };
|
|
69
|
+
|
|
70
|
+
const toggleBtn = document.createElement('button');
|
|
71
|
+
toggleBtn.textContent = '隐藏';
|
|
72
|
+
Object.assign(toggleBtn.style, { cursor: 'pointer', fontSize: '12px' });
|
|
73
|
+
toggleBtn.onclick = () => {
|
|
74
|
+
if (entriesContainer.style.display === 'none') { entriesContainer.style.display = ''; toggleBtn.textContent = '隐藏'; }
|
|
75
|
+
else { entriesContainer.style.display = 'none'; toggleBtn.textContent = '显示'; }
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const statsSpan = document.createElement('span');
|
|
79
|
+
statsSpan.style.opacity = '.8';
|
|
80
|
+
statsSpan.style.fontSize = '11px';
|
|
81
|
+
statsSpan.textContent = '0 条';
|
|
82
|
+
|
|
83
|
+
btns.appendChild(statsSpan);
|
|
84
|
+
btns.appendChild(toggleBtn);
|
|
85
|
+
btns.appendChild(clearBtn);
|
|
86
|
+
header.appendChild(btns);
|
|
87
|
+
|
|
88
|
+
// entries container (scrollable)
|
|
89
|
+
const entriesContainer = document.createElement('div');
|
|
90
|
+
Object.assign(entriesContainer.style, {
|
|
91
|
+
overflowY: 'auto',
|
|
92
|
+
padding: '6px',
|
|
93
|
+
borderRadius: '6px',
|
|
94
|
+
background: 'rgba(0,0,0,0.32)',
|
|
95
|
+
maxHeight: 'calc(45vh - 48px)',
|
|
96
|
+
boxSizing: 'border-box'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
panel.appendChild(header);
|
|
100
|
+
panel.appendChild(entriesContainer);
|
|
101
|
+
document.body.appendChild(panel);
|
|
102
|
+
|
|
103
|
+
// helper to update stats
|
|
104
|
+
function updateStats() {
|
|
105
|
+
statsSpan.textContent = `${entryCount} 条`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
panel._entriesContainer = entriesContainer;
|
|
109
|
+
panel._statsUpdater = updateStats;
|
|
110
|
+
return panel;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ========== 添加条目 ==========
|
|
114
|
+
let entryCount = 0;
|
|
115
|
+
function addEntry({ type = 'log', message = '', time = new Date().toLocaleString(), meta = null }) {
|
|
116
|
+
const panel = createPanel();
|
|
117
|
+
const entriesContainer = panel._entriesContainer;
|
|
118
|
+
const updateStats = panel._statsUpdater;
|
|
119
|
+
|
|
120
|
+
// 限制数量
|
|
121
|
+
if (entryCount >= MAX_ENTRIES) {
|
|
122
|
+
const first = entriesContainer.firstChild;
|
|
123
|
+
if (first) entriesContainer.removeChild(first);
|
|
124
|
+
entryCount--;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const entry = document.createElement('div');
|
|
128
|
+
Object.assign(entry.style, {
|
|
129
|
+
padding: '6px',
|
|
130
|
+
marginBottom: '6px',
|
|
131
|
+
borderRadius: '6px',
|
|
132
|
+
background: 'rgba(255,255,255,0.03)',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const header = document.createElement('div');
|
|
136
|
+
header.style.display = 'flex';
|
|
137
|
+
header.style.justifyContent = 'space-between';
|
|
138
|
+
header.style.alignItems = 'center';
|
|
139
|
+
header.style.gap = '8px';
|
|
140
|
+
|
|
141
|
+
const left = document.createElement('div');
|
|
142
|
+
// 彩色 type 标签
|
|
143
|
+
const colorMap = {
|
|
144
|
+
error: '#ff6b6b',
|
|
145
|
+
'window.onerror': '#ff6b6b',
|
|
146
|
+
resource: '#ff9966',
|
|
147
|
+
'unhandledrejection': '#ff8c66',
|
|
148
|
+
'console.error': '#ff6b6b',
|
|
149
|
+
'console.warn': '#ffd966',
|
|
150
|
+
'console.info': '#66bfff',
|
|
151
|
+
'console.log': '#bfbfbf',
|
|
152
|
+
'console.debug': '#a0a0a0',
|
|
153
|
+
'Vue.error (2)': '#ff6b6b',
|
|
154
|
+
'Vue.error (3)': '#ff6b6b'
|
|
155
|
+
};
|
|
156
|
+
const tagColor = colorMap[type] || '#bfbfbf';
|
|
157
|
+
left.innerHTML = `<span style="font-weight:700;color:${tagColor}">${type}</span> <span style="opacity:.7;font-size:11px"> ${time}</span>`;
|
|
158
|
+
|
|
159
|
+
const right = document.createElement('div');
|
|
160
|
+
right.style.display = 'flex';
|
|
161
|
+
right.style.gap = '6px';
|
|
162
|
+
|
|
163
|
+
const copyBtn = document.createElement('button');
|
|
164
|
+
copyBtn.textContent = '复制';
|
|
165
|
+
Object.assign(copyBtn.style, { cursor: 'pointer', fontSize: '11px' });
|
|
166
|
+
|
|
167
|
+
const expandBtn = document.createElement('button');
|
|
168
|
+
expandBtn.textContent = '展开';
|
|
169
|
+
Object.assign(expandBtn.style, { cursor: 'pointer', fontSize: '11px' });
|
|
170
|
+
|
|
171
|
+
right.appendChild(copyBtn);
|
|
172
|
+
right.appendChild(expandBtn);
|
|
173
|
+
header.appendChild(left);
|
|
174
|
+
header.appendChild(right);
|
|
175
|
+
|
|
176
|
+
const content = document.createElement('pre');
|
|
177
|
+
Object.assign(content.style, {
|
|
178
|
+
whiteSpace: 'pre-wrap',
|
|
179
|
+
wordBreak: 'break-word',
|
|
180
|
+
margin: '6px 0 0 0',
|
|
181
|
+
maxHeight: '240px',
|
|
182
|
+
overflow: 'auto',
|
|
183
|
+
fontSize: '12px',
|
|
184
|
+
lineHeight: '1.3',
|
|
185
|
+
display: 'block'
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// 合成文本(message + meta)
|
|
189
|
+
let txt = typeof message === 'string' ? message : safeStringify(message);
|
|
190
|
+
if (meta) {
|
|
191
|
+
try {
|
|
192
|
+
const metaStr = safeStringify(meta);
|
|
193
|
+
txt = `${txt}\n\n[meta]\n${metaStr}`;
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
}
|
|
196
|
+
const fullText = txt;
|
|
197
|
+
|
|
198
|
+
if (txt.length > COLLAPSE_LENGTH) {
|
|
199
|
+
content.textContent = txt.slice(0, COLLAPSE_LENGTH) + '\n\n...(已折叠,点击展开查看全部)';
|
|
200
|
+
expandBtn.onclick = () => { content.textContent = fullText; expandBtn.style.display = 'none'; entriesContainer.scrollTop = entriesContainer.scrollHeight; };
|
|
201
|
+
} else {
|
|
202
|
+
content.textContent = txt;
|
|
203
|
+
expandBtn.style.display = 'none';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
copyBtn.onclick = () => {
|
|
207
|
+
try { navigator.clipboard.writeText(fullText); } catch (e) { try { prompt('复制文本:Ctrl+C, Enter', fullText); } catch (ee) {} }
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
entry.appendChild(header);
|
|
211
|
+
entry.appendChild(content);
|
|
212
|
+
entriesContainer.appendChild(entry);
|
|
213
|
+
entryCount++;
|
|
214
|
+
updateStats();
|
|
215
|
+
|
|
216
|
+
// 自动滚到底部
|
|
217
|
+
entriesContainer.scrollTop = entriesContainer.scrollHeight;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 报告包装
|
|
221
|
+
function report(obj) {
|
|
222
|
+
try {
|
|
223
|
+
addEntry({ type: obj.type || 'log', message: obj.message || safeStringify(obj), time: new Date().toLocaleString(), meta: obj.meta || null });
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.warn('Error panel report failed', e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ========== 捕获逻辑 ==========
|
|
230
|
+
|
|
231
|
+
// 传统同步错误
|
|
232
|
+
window.onerror = function(message, source, lineno, colno, error) {
|
|
233
|
+
const msg = `${message}\n at ${source}:${lineno}:${colno}\n${error && error.stack ? error.stack : ''}`;
|
|
234
|
+
report({ type: 'window.onerror', message: msg });
|
|
235
|
+
return false;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// 资源加载错误(图片/script/css 等)
|
|
239
|
+
window.addEventListener('error', function(event) {
|
|
240
|
+
const target = event.target || event.srcElement;
|
|
241
|
+
if (target && (target.src || target.href)) {
|
|
242
|
+
const tag = target.tagName;
|
|
243
|
+
const url = target.src || target.href;
|
|
244
|
+
report({ type: 'resource', message: `Failed to load resource: <${tag}> ${url}` });
|
|
245
|
+
}
|
|
246
|
+
}, true);
|
|
247
|
+
|
|
248
|
+
// 未处理的 Promise 拒绝
|
|
249
|
+
window.addEventListener('unhandledrejection', function(event) {
|
|
250
|
+
report({ type: 'unhandledrejection', message: safeStringify(event.reason) });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 捕获 console.*(可选)
|
|
254
|
+
if (CAPTURE_CONSOLE && typeof console !== 'undefined') {
|
|
255
|
+
const methods = ['log', 'info', 'warn', 'error', 'debug'];
|
|
256
|
+
methods.forEach((m) => {
|
|
257
|
+
if (!(m in console)) return;
|
|
258
|
+
const orig = console[m].bind(console);
|
|
259
|
+
console[m] = function(...args) {
|
|
260
|
+
try {
|
|
261
|
+
// 生成调用栈(方便定位是谁调用了 console)
|
|
262
|
+
let stack = null;
|
|
263
|
+
try {
|
|
264
|
+
const err = new Error();
|
|
265
|
+
if (err.stack) {
|
|
266
|
+
// 移除前两行(Error + 本函数)
|
|
267
|
+
stack = err.stack.split('\n').slice(2).join('\n');
|
|
268
|
+
}
|
|
269
|
+
} catch (e) { stack = null; }
|
|
270
|
+
|
|
271
|
+
const payload = {
|
|
272
|
+
type: `console.${m}`,
|
|
273
|
+
message: args.map(a => safeStringify(a)).join(' '),
|
|
274
|
+
meta: stack
|
|
275
|
+
};
|
|
276
|
+
report(payload);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
// noop
|
|
279
|
+
}
|
|
280
|
+
// 保持原有行为
|
|
281
|
+
try { orig(...args); } catch (e) {}
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Vue 2 支持(若使用全局 Vue)
|
|
287
|
+
if (typeof Vue !== 'undefined' && Vue && Vue.config) {
|
|
288
|
+
try {
|
|
289
|
+
Vue.config.errorHandler = function(err, vm, info) {
|
|
290
|
+
report({ type: 'Vue.error (2)', message: `${info}\n${err && err.stack ? err.stack : safeStringify(err)}` });
|
|
291
|
+
};
|
|
292
|
+
} catch (e) { /* ignore */ }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Vue 3 注册函数(在 main.js 创建 app 后调用)
|
|
296
|
+
window.registerVue3ErrorHandler = function(app) {
|
|
297
|
+
if (!app || !app.config) return;
|
|
298
|
+
app.config.errorHandler = (err, instance, info) => {
|
|
299
|
+
report({ type: 'Vue.error (3)', message: `${info}\n${err && err.stack ? err.stack : safeStringify(err)}` });
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// 立即创建 panel(或可改为按需)
|
|
304
|
+
if (document.readyState === 'loading') {
|
|
305
|
+
document.addEventListener('DOMContentLoaded', createPanel);
|
|
306
|
+
} else createPanel();
|
|
307
|
+
|
|
308
|
+
}
|
|
309
|
+
// ========= End Panel =========
|