fl-web-component 1.3.8 → 1.3.11
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 +1 -1
- package/dist/fl-web-component.common.js +5 -375
- package/dist/fl-web-component.common.js.map +1 -1
- package/package.json +1 -1
- package/src/main.js +0 -2
- package/src/utils/index.js +382 -1
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -4,7 +4,6 @@ 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'
|
|
8
7
|
|
|
9
8
|
const components = [FlModel, Fl2dcanvas, FLPerControl, FlSvg];
|
|
10
9
|
|
|
@@ -18,7 +17,6 @@ const install = Vue => {
|
|
|
18
17
|
// 支持浏览器环境直接引入
|
|
19
18
|
if (typeof window !== 'undefined') {
|
|
20
19
|
window.FLVersion = pkg.version;
|
|
21
|
-
window.FLDevTools = qtdevtools;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export default {
|
package/src/utils/index.js
CHANGED
|
@@ -13,4 +13,385 @@ async function screenshot(domElementID = 'fl-model') {
|
|
|
13
13
|
return { base64 };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
function miniDevtool() {
|
|
17
|
+
// ========== 可配置项 ==========
|
|
18
|
+
const MAX_ENTRIES = 400;
|
|
19
|
+
const PANEL_ID = 'js-error-panel';
|
|
20
|
+
const COLLAPSE_LENGTH = 1200; // 超过多少字符时折叠显示
|
|
21
|
+
const CAPTURE_CONSOLE = true; // 是否捕获 console.log/info/warn/debug(保留原行为)
|
|
22
|
+
// ================================
|
|
23
|
+
|
|
24
|
+
// 安全 stringify(处理循环引用)
|
|
25
|
+
function getCircularReplacer() {
|
|
26
|
+
const seen = new WeakSet();
|
|
27
|
+
return (key, value) => {
|
|
28
|
+
if (typeof value === 'object' && value !== null) {
|
|
29
|
+
if (seen.has(value)) return '[Circular]';
|
|
30
|
+
seen.add(value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function safeStringify(x) {
|
|
36
|
+
try {
|
|
37
|
+
if (typeof x === 'string') return x;
|
|
38
|
+
if (x instanceof Error) return x.stack || x.message || String(x);
|
|
39
|
+
return JSON.stringify(x, getCircularReplacer(), 2);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
try {
|
|
42
|
+
return String(x);
|
|
43
|
+
} catch (e2) {
|
|
44
|
+
return '[unstringifiable]';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ========== Panel DOM 创建 ==========
|
|
50
|
+
function createPanel() {
|
|
51
|
+
if (document.getElementById(PANEL_ID)) return document.getElementById(PANEL_ID);
|
|
52
|
+
|
|
53
|
+
const panel = document.createElement('div');
|
|
54
|
+
panel.id = PANEL_ID;
|
|
55
|
+
Object.assign(panel.style, {
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
right: '12px',
|
|
58
|
+
bottom: '12px',
|
|
59
|
+
width: '460px',
|
|
60
|
+
maxHeight: '45vh',
|
|
61
|
+
boxSizing: 'border-box',
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
zIndex: 2147483647,
|
|
64
|
+
fontFamily: 'Menlo,Consolas,monospace',
|
|
65
|
+
fontSize: '12px',
|
|
66
|
+
color: '#fff',
|
|
67
|
+
borderRadius: '8px',
|
|
68
|
+
boxShadow: '0 6px 18px rgba(0,0,0,0.55)',
|
|
69
|
+
background: 'linear-gradient(180deg, rgba(0,0,0,0.86), rgba(24,24,24,0.86))',
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'column',
|
|
72
|
+
gap: '6px',
|
|
73
|
+
padding: '8px',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// header
|
|
77
|
+
const header = document.createElement('div');
|
|
78
|
+
Object.assign(header.style, {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
justifyContent: 'space-between',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
gap: '8px',
|
|
83
|
+
});
|
|
84
|
+
header.innerHTML = `<strong style="font-size:13px">JS Logs / Errors</strong><span style="opacity:.7;font-size:11px">(非阻塞)</span>`;
|
|
85
|
+
|
|
86
|
+
const btns = document.createElement('div');
|
|
87
|
+
|
|
88
|
+
const clearBtn = document.createElement('button');
|
|
89
|
+
clearBtn.textContent = '清空';
|
|
90
|
+
Object.assign(clearBtn.style, { marginLeft: '6px', cursor: 'pointer', fontSize: '12px' });
|
|
91
|
+
clearBtn.onclick = () => {
|
|
92
|
+
entriesContainer.innerHTML = '';
|
|
93
|
+
entryCount = 0;
|
|
94
|
+
updateStats();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const toggleBtn = document.createElement('button');
|
|
98
|
+
toggleBtn.textContent = '隐藏';
|
|
99
|
+
Object.assign(toggleBtn.style, { cursor: 'pointer', fontSize: '12px' });
|
|
100
|
+
toggleBtn.onclick = () => {
|
|
101
|
+
if (entriesContainer.style.display === 'none') {
|
|
102
|
+
entriesContainer.style.display = '';
|
|
103
|
+
toggleBtn.textContent = '隐藏';
|
|
104
|
+
} else {
|
|
105
|
+
entriesContainer.style.display = 'none';
|
|
106
|
+
toggleBtn.textContent = '显示';
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const statsSpan = document.createElement('span');
|
|
111
|
+
statsSpan.style.opacity = '.8';
|
|
112
|
+
statsSpan.style.fontSize = '11px';
|
|
113
|
+
statsSpan.textContent = '0 条';
|
|
114
|
+
|
|
115
|
+
btns.appendChild(statsSpan);
|
|
116
|
+
btns.appendChild(toggleBtn);
|
|
117
|
+
btns.appendChild(clearBtn);
|
|
118
|
+
header.appendChild(btns);
|
|
119
|
+
|
|
120
|
+
// entries container (scrollable)
|
|
121
|
+
const entriesContainer = document.createElement('div');
|
|
122
|
+
Object.assign(entriesContainer.style, {
|
|
123
|
+
overflowY: 'auto',
|
|
124
|
+
padding: '6px',
|
|
125
|
+
borderRadius: '6px',
|
|
126
|
+
background: 'rgba(0,0,0,0.32)',
|
|
127
|
+
maxHeight: 'calc(45vh - 48px)',
|
|
128
|
+
boxSizing: 'border-box',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
panel.appendChild(header);
|
|
132
|
+
panel.appendChild(entriesContainer);
|
|
133
|
+
document.body.appendChild(panel);
|
|
134
|
+
|
|
135
|
+
// helper to update stats
|
|
136
|
+
function updateStats() {
|
|
137
|
+
statsSpan.textContent = `${entryCount} 条`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
panel._entriesContainer = entriesContainer;
|
|
141
|
+
panel._statsUpdater = updateStats;
|
|
142
|
+
return panel;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ========== 添加条目 ==========
|
|
146
|
+
let entryCount = 0;
|
|
147
|
+
function addEntry({
|
|
148
|
+
type = 'log',
|
|
149
|
+
message = '',
|
|
150
|
+
time = new Date().toLocaleString(),
|
|
151
|
+
meta = null,
|
|
152
|
+
}) {
|
|
153
|
+
const panel = createPanel();
|
|
154
|
+
const entriesContainer = panel._entriesContainer;
|
|
155
|
+
const updateStats = panel._statsUpdater;
|
|
156
|
+
|
|
157
|
+
// 限制数量
|
|
158
|
+
if (entryCount >= MAX_ENTRIES) {
|
|
159
|
+
const first = entriesContainer.firstChild;
|
|
160
|
+
if (first) entriesContainer.removeChild(first);
|
|
161
|
+
entryCount--;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const entry = document.createElement('div');
|
|
165
|
+
Object.assign(entry.style, {
|
|
166
|
+
padding: '6px',
|
|
167
|
+
marginBottom: '6px',
|
|
168
|
+
borderRadius: '6px',
|
|
169
|
+
background: 'rgba(255,255,255,0.03)',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const header = document.createElement('div');
|
|
173
|
+
header.style.display = 'flex';
|
|
174
|
+
header.style.justifyContent = 'space-between';
|
|
175
|
+
header.style.alignItems = 'center';
|
|
176
|
+
header.style.gap = '8px';
|
|
177
|
+
|
|
178
|
+
const left = document.createElement('div');
|
|
179
|
+
// 彩色 type 标签
|
|
180
|
+
const colorMap = {
|
|
181
|
+
error: '#ff6b6b',
|
|
182
|
+
'window.onerror': '#ff6b6b',
|
|
183
|
+
resource: '#ff9966',
|
|
184
|
+
unhandledrejection: '#ff8c66',
|
|
185
|
+
'console.error': '#ff6b6b',
|
|
186
|
+
'console.warn': '#ffd966',
|
|
187
|
+
'console.info': '#66bfff',
|
|
188
|
+
'console.log': '#bfbfbf',
|
|
189
|
+
'console.debug': '#a0a0a0',
|
|
190
|
+
'Vue.error (2)': '#ff6b6b',
|
|
191
|
+
'Vue.error (3)': '#ff6b6b',
|
|
192
|
+
};
|
|
193
|
+
const tagColor = colorMap[type] || '#bfbfbf';
|
|
194
|
+
left.innerHTML = `<span style="font-weight:700;color:${tagColor}">${type}</span> <span style="opacity:.7;font-size:11px"> ${time}</span>`;
|
|
195
|
+
|
|
196
|
+
const right = document.createElement('div');
|
|
197
|
+
right.style.display = 'flex';
|
|
198
|
+
right.style.gap = '6px';
|
|
199
|
+
|
|
200
|
+
const copyBtn = document.createElement('button');
|
|
201
|
+
copyBtn.textContent = '复制';
|
|
202
|
+
Object.assign(copyBtn.style, { cursor: 'pointer', fontSize: '11px' });
|
|
203
|
+
|
|
204
|
+
const expandBtn = document.createElement('button');
|
|
205
|
+
expandBtn.textContent = '展开';
|
|
206
|
+
Object.assign(expandBtn.style, { cursor: 'pointer', fontSize: '11px' });
|
|
207
|
+
|
|
208
|
+
right.appendChild(copyBtn);
|
|
209
|
+
right.appendChild(expandBtn);
|
|
210
|
+
header.appendChild(left);
|
|
211
|
+
header.appendChild(right);
|
|
212
|
+
|
|
213
|
+
const content = document.createElement('pre');
|
|
214
|
+
Object.assign(content.style, {
|
|
215
|
+
whiteSpace: 'pre-wrap',
|
|
216
|
+
wordBreak: 'break-word',
|
|
217
|
+
margin: '6px 0 0 0',
|
|
218
|
+
maxHeight: '240px',
|
|
219
|
+
overflow: 'auto',
|
|
220
|
+
fontSize: '12px',
|
|
221
|
+
lineHeight: '1.3',
|
|
222
|
+
display: 'block',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// 合成文本(message + meta)
|
|
226
|
+
let txt = typeof message === 'string' ? message : safeStringify(message);
|
|
227
|
+
if (meta) {
|
|
228
|
+
try {
|
|
229
|
+
const metaStr = safeStringify(meta);
|
|
230
|
+
txt = `${txt}\n\n[meta]\n${metaStr}`;
|
|
231
|
+
} catch (e) {}
|
|
232
|
+
}
|
|
233
|
+
const fullText = txt;
|
|
234
|
+
|
|
235
|
+
if (txt.length > COLLAPSE_LENGTH) {
|
|
236
|
+
content.textContent = txt.slice(0, COLLAPSE_LENGTH) + '\n\n...(已折叠,点击展开查看全部)';
|
|
237
|
+
expandBtn.onclick = () => {
|
|
238
|
+
content.textContent = fullText;
|
|
239
|
+
expandBtn.style.display = 'none';
|
|
240
|
+
entriesContainer.scrollTop = entriesContainer.scrollHeight;
|
|
241
|
+
};
|
|
242
|
+
} else {
|
|
243
|
+
content.textContent = txt;
|
|
244
|
+
expandBtn.style.display = 'none';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
copyBtn.onclick = () => {
|
|
248
|
+
try {
|
|
249
|
+
navigator.clipboard.writeText(fullText);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
try {
|
|
252
|
+
prompt('复制文本:Ctrl+C, Enter', fullText);
|
|
253
|
+
} catch (ee) {}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
entry.appendChild(header);
|
|
258
|
+
entry.appendChild(content);
|
|
259
|
+
entriesContainer.appendChild(entry);
|
|
260
|
+
entryCount++;
|
|
261
|
+
updateStats();
|
|
262
|
+
|
|
263
|
+
// 自动滚到底部
|
|
264
|
+
entriesContainer.scrollTop = entriesContainer.scrollHeight;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 报告包装
|
|
268
|
+
function report(obj) {
|
|
269
|
+
try {
|
|
270
|
+
addEntry({
|
|
271
|
+
type: obj.type || 'log',
|
|
272
|
+
message: obj.message || safeStringify(obj),
|
|
273
|
+
time: new Date().toLocaleString(),
|
|
274
|
+
meta: obj.meta || null,
|
|
275
|
+
});
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.warn('Error panel report failed', e);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ========== 捕获逻辑 ==========
|
|
282
|
+
|
|
283
|
+
// 传统同步错误
|
|
284
|
+
window.onerror = function (message, source, lineno, colno, error) {
|
|
285
|
+
const msg = `${message}\n at ${source}:${lineno}:${colno}\n${
|
|
286
|
+
error && error.stack ? error.stack : ''
|
|
287
|
+
}`;
|
|
288
|
+
report({ type: 'window.onerror', message: msg });
|
|
289
|
+
return false;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// 资源加载错误(图片/script/css 等)
|
|
293
|
+
window.addEventListener(
|
|
294
|
+
'error',
|
|
295
|
+
function (event) {
|
|
296
|
+
const target = event.target || event.srcElement;
|
|
297
|
+
if (target && (target.src || target.href)) {
|
|
298
|
+
const tag = target.tagName;
|
|
299
|
+
const url = target.src || target.href;
|
|
300
|
+
report({ type: 'resource', message: `Failed to load resource: <${tag}> ${url}` });
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
true
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// 未处理的 Promise 拒绝
|
|
307
|
+
window.addEventListener('unhandledrejection', function (event) {
|
|
308
|
+
report({ type: 'unhandledrejection', message: safeStringify(event.reason) });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// 捕获 console.*(可选)
|
|
312
|
+
if (CAPTURE_CONSOLE && typeof console !== 'undefined') {
|
|
313
|
+
const methods = ['log', 'info', 'warn', 'error', 'debug'];
|
|
314
|
+
methods.forEach(m => {
|
|
315
|
+
if (!(m in console)) return;
|
|
316
|
+
const orig = console[m].bind(console);
|
|
317
|
+
console[m] = function (...args) {
|
|
318
|
+
try {
|
|
319
|
+
// 生成调用栈(方便定位是谁调用了 console)
|
|
320
|
+
let stack = null;
|
|
321
|
+
try {
|
|
322
|
+
const err = new Error();
|
|
323
|
+
if (err.stack) {
|
|
324
|
+
// 移除前两行(Error + 本函数)
|
|
325
|
+
stack = err.stack.split('\n').slice(2).join('\n');
|
|
326
|
+
}
|
|
327
|
+
} catch (e) {
|
|
328
|
+
stack = null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const payload = {
|
|
332
|
+
type: `console.${m}`,
|
|
333
|
+
message: args.map(a => safeStringify(a)).join(' '),
|
|
334
|
+
meta: stack,
|
|
335
|
+
};
|
|
336
|
+
report(payload);
|
|
337
|
+
} catch (e) {
|
|
338
|
+
// noop
|
|
339
|
+
}
|
|
340
|
+
// 保持原有行为
|
|
341
|
+
try {
|
|
342
|
+
orig(...args);
|
|
343
|
+
} catch (e) {}
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Vue 2 支持(若使用全局 Vue)
|
|
349
|
+
if (typeof Vue !== 'undefined' && Vue && Vue.config) {
|
|
350
|
+
try {
|
|
351
|
+
Vue.config.errorHandler = function (err, vm, info) {
|
|
352
|
+
report({
|
|
353
|
+
type: 'Vue.error (2)',
|
|
354
|
+
message: `${info}\n${err && err.stack ? err.stack : safeStringify(err)}`,
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
} catch (e) {
|
|
358
|
+
/* ignore */
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Vue 3 注册函数(在 main.js 创建 app 后调用)
|
|
363
|
+
window.registerVue3ErrorHandler = function (app) {
|
|
364
|
+
if (!app || !app.config) return;
|
|
365
|
+
app.config.errorHandler = (err, instance, info) => {
|
|
366
|
+
report({
|
|
367
|
+
type: 'Vue.error (3)',
|
|
368
|
+
message: `${info}\n${err && err.stack ? err.stack : safeStringify(err)}`,
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// 立即创建 panel(或可改为按需)
|
|
374
|
+
if (document.readyState === 'loading') {
|
|
375
|
+
document.addEventListener('DOMContentLoaded', createPanel);
|
|
376
|
+
} else createPanel();
|
|
377
|
+
|
|
378
|
+
// 是否支持 WebGL
|
|
379
|
+
function hasWebGL() {
|
|
380
|
+
console.log('flversion', window.FLVersion)
|
|
381
|
+
console.log('userAgent:', navigator.userAgent);
|
|
382
|
+
console.log('has WebGLRenderingContext:', !!window.WebGLRenderingContext);
|
|
383
|
+
const c = document.createElement('canvas');
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
const g1 = c.getContext('webgl');
|
|
387
|
+
const g2 = c.getContext('experimental-webgl');
|
|
388
|
+
const g3 = c.getContext('webgl2');
|
|
389
|
+
console.log('webgl contexts:', { g1: !!g1, g2: !!g2, g3: !!g3 });
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.error('getContext error', e);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
console.log('===========hasWebGL=======', hasWebGL());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export { screenshot, miniDevtool };
|