nv-log-bw 1.0.0

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.
@@ -0,0 +1,1866 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NV Log Viewer 组件使用范例</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --primary-color: #1890ff;
16
+ --success-color: #52c41a;
17
+ --warning-color: #faad14;
18
+ --error-color: #ff4d4f;
19
+ --ignore-color: #999999;
20
+ --bg-color: #f5f5f5;
21
+ --text-color: #333;
22
+ --text-secondary: #666;
23
+ --border-color: #e8e8e8;
24
+ }
25
+
26
+ body {
27
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
28
+ line-height: 1.6;
29
+ color: var(--text-color);
30
+ background: var(--bg-color);
31
+ padding: 20px;
32
+ }
33
+
34
+ .container {
35
+ max-width: 1200px;
36
+ margin: 0 auto;
37
+ }
38
+
39
+ header {
40
+ text-align: center;
41
+ margin-bottom: 40px;
42
+ padding: 40px 0;
43
+ }
44
+
45
+ h1 {
46
+ font-size: 2.5em;
47
+ color: var(--primary-color);
48
+ margin-bottom: 10px;
49
+ font-weight: 600;
50
+ }
51
+
52
+ .subtitle {
53
+ font-size: 1.1em;
54
+ color: var(--text-secondary);
55
+ margin-bottom: 20px;
56
+ }
57
+
58
+ .tag-list {
59
+ display: flex;
60
+ justify-content: center;
61
+ flex-wrap: wrap;
62
+ gap: 8px;
63
+ margin-top: 20px;
64
+ }
65
+
66
+ .tag {
67
+ background: var(--primary-color);
68
+ color: white;
69
+ padding: 6px 12px;
70
+ border-radius: 20px;
71
+ font-size: 0.85em;
72
+ opacity: 0.9;
73
+ }
74
+
75
+ .tag.info { background: var(--primary-color); }
76
+ .tag.success { background: var(--success-color); }
77
+ .tag.warning { background: var(--warning-color); }
78
+ .tag.error { background: var(--error-color); }
79
+ .tag.ignore { background: var(--ignore-color); }
80
+
81
+ .demo-grid {
82
+ display: grid;
83
+ grid-template-columns: 1fr 1fr;
84
+ gap: 30px;
85
+ margin-bottom: 40px;
86
+ }
87
+
88
+ @media (max-width: 768px) {
89
+ .demo-grid {
90
+ grid-template-columns: 1fr;
91
+ }
92
+ }
93
+
94
+ .demo-section {
95
+ background: white;
96
+ border-radius: 12px;
97
+ padding: 30px;
98
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
99
+ border: 1px solid var(--border-color);
100
+ }
101
+
102
+ .section-header {
103
+ display: flex;
104
+ align-items: center;
105
+ margin-bottom: 20px;
106
+ padding-bottom: 15px;
107
+ border-bottom: 2px solid var(--border-color);
108
+ }
109
+
110
+ .section-header h2 {
111
+ font-size: 1.5em;
112
+ color: var(--text-color);
113
+ font-weight: 600;
114
+ }
115
+
116
+ .icon {
117
+ width: 40px;
118
+ height: 40px;
119
+ border-radius: 8px;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ margin-right: 12px;
124
+ font-size: 1.2em;
125
+ }
126
+
127
+ .icon.basic { background: var(--primary-color); color: white; }
128
+ .icon.theme { background: #722ed1; color: white; }
129
+ .icon.filter { background: #13c2c2; color: white; }
130
+ .icon.import { background: #fa8c16; color: white; }
131
+ .icon.export { background: var(--success-color); color: white; }
132
+ .icon.control { background: #f5222d; color: white; }
133
+
134
+ .control-group {
135
+ margin-bottom: 20px;
136
+ }
137
+
138
+ .control-group h3 {
139
+ font-size: 1.1em;
140
+ margin-bottom: 12px;
141
+ color: var(--text-color);
142
+ font-weight: 500;
143
+ }
144
+
145
+ .btn-group {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ gap: 10px;
149
+ margin-bottom: 15px;
150
+ }
151
+
152
+ button {
153
+ padding: 10px 20px;
154
+ border: none;
155
+ border-radius: 6px;
156
+ background: var(--primary-color);
157
+ color: white;
158
+ cursor: pointer;
159
+ font-size: 14px;
160
+ transition: all 0.3s;
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 8px;
164
+ font-weight: 500;
165
+ }
166
+
167
+ button:hover:not(:disabled) {
168
+ opacity: 0.9;
169
+ transform: translateY(-1px);
170
+ box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
171
+ }
172
+
173
+ button:disabled {
174
+ background: #d9d9d9;
175
+ cursor: not-allowed;
176
+ transform: none;
177
+ box-shadow: none;
178
+ }
179
+
180
+ button.info { background: var(--primary-color); }
181
+ button.success { background: var(--success-color); }
182
+ button.warning { background: var(--warning-color); }
183
+ button.error { background: var(--error-color); }
184
+ button.ignore { background: var(--ignore-color); }
185
+
186
+ .btn-small {
187
+ padding: 6px 12px;
188
+ font-size: 13px;
189
+ }
190
+
191
+ .checkbox-group {
192
+ display: flex;
193
+ flex-wrap: wrap;
194
+ gap: 15px;
195
+ margin: 15px 0;
196
+ }
197
+
198
+ .checkbox-label {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 6px;
202
+ cursor: pointer;
203
+ font-size: 14px;
204
+ }
205
+
206
+ input[type="checkbox"] {
207
+ width: 16px;
208
+ height: 16px;
209
+ }
210
+
211
+ .radio-group {
212
+ display: flex;
213
+ gap: 20px;
214
+ margin: 15px 0;
215
+ }
216
+
217
+ .radio-label {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 6px;
221
+ }
222
+
223
+ .code-block {
224
+ background: #2c3e50;
225
+ color: #ecf0f1;
226
+ padding: 20px;
227
+ border-radius: 8px;
228
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', Consolas, 'Courier New', monospace;
229
+ overflow-x: auto;
230
+ font-size: 13px;
231
+ line-height: 1.5;
232
+ margin-top: 20px;
233
+ }
234
+
235
+ .code-comment { color: #7f8c8d; }
236
+ .code-keyword { color: #3498db; }
237
+ .code-string { color: #2ecc71; }
238
+ .code-function { color: #f39c12; }
239
+
240
+ .demo-container {
241
+ margin: 20px 0;
242
+ border: 1px solid var(--border-color);
243
+ border-radius: 8px;
244
+ overflow: hidden;
245
+ }
246
+
247
+ .attribute-controls {
248
+ display: grid;
249
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
250
+ gap: 15px;
251
+ margin: 20px 0;
252
+ }
253
+
254
+ .control-item {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 6px;
258
+ }
259
+
260
+ .control-item label {
261
+ font-weight: 600;
262
+ font-size: 13px;
263
+ color: var(--text-secondary);
264
+ }
265
+
266
+ select, input[type="number"] {
267
+ padding: 8px 12px;
268
+ border: 1px solid var(--border-color);
269
+ border-radius: 4px;
270
+ font-size: 14px;
271
+ }
272
+
273
+ .file-input {
274
+ position: relative;
275
+ overflow: hidden;
276
+ display: inline-block;
277
+ }
278
+
279
+ .file-input input[type="file"] {
280
+ position: absolute;
281
+ left: 0;
282
+ top: 0;
283
+ opacity: 0;
284
+ width: 100%;
285
+ height: 100%;
286
+ cursor: pointer;
287
+ }
288
+
289
+ .notification {
290
+ background: #f6ffed;
291
+ border: 1px solid #b7eb8f;
292
+ color: #389e0d;
293
+ padding: 12px 16px;
294
+ border-radius: 6px;
295
+ margin: 15px 0;
296
+ font-size: 14px;
297
+ }
298
+
299
+ .notification.error {
300
+ background: #fff2f0;
301
+ border-color: #ffccc7;
302
+ color: #cf1322;
303
+ }
304
+
305
+ .divider {
306
+ height: 1px;
307
+ background: var(--border-color);
308
+ margin: 20px 0;
309
+ }
310
+
311
+ .log-stats-display {
312
+ display: grid;
313
+ grid-template-columns: repeat(3, 1fr);
314
+ gap: 10px;
315
+ margin: 20px 0;
316
+ font-size: 13px;
317
+ }
318
+
319
+ .stat-item {
320
+ background: #fafafa;
321
+ padding: 10px;
322
+ border-radius: 6px;
323
+ text-align: center;
324
+ }
325
+
326
+ .stat-value {
327
+ font-size: 1.2em;
328
+ font-weight: 600;
329
+ display: block;
330
+ }
331
+
332
+ .stat-value.total { color: var(--text-color); }
333
+ .stat-value.info { color: var(--primary-color); }
334
+ .stat-value.success { color: var(--success-color); }
335
+ .stat-value.warning { color: var(--warning-color); }
336
+ .stat-value.error { color: var(--error-color); }
337
+ .stat-value.ignore { color: var(--ignore-color); }
338
+
339
+ footer {
340
+ text-align: center;
341
+ padding: 30px 0;
342
+ color: var(--text-secondary);
343
+ font-size: 0.9em;
344
+ border-top: 1px solid var(--border-color);
345
+ margin-top: 40px;
346
+ }
347
+
348
+ .api-reference {
349
+ background: white;
350
+ border-radius: 12px;
351
+ padding: 30px;
352
+ margin-top: 30px;
353
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
354
+ }
355
+
356
+ .api-reference h2 {
357
+ margin-bottom: 20px;
358
+ color: var(--text-color);
359
+ }
360
+
361
+ table {
362
+ width: 100%;
363
+ border-collapse: collapse;
364
+ margin: 20px 0;
365
+ }
366
+
367
+ th, td {
368
+ padding: 12px 15px;
369
+ text-align: left;
370
+ border-bottom: 1px solid var(--border-color);
371
+ }
372
+
373
+ th {
374
+ background: #fafafa;
375
+ font-weight: 600;
376
+ }
377
+
378
+ tr:hover {
379
+ background: #fafafa;
380
+ }
381
+
382
+ .example-json {
383
+ background: #f8f9fa;
384
+ border: 1px solid #e9ecef;
385
+ border-radius: 4px;
386
+ padding: 15px;
387
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', Consolas, 'Courier New', monospace;
388
+ font-size: 12px;
389
+ margin: 10px 0;
390
+ }
391
+ </style>
392
+ </head>
393
+ <body>
394
+ <div class="container">
395
+ <header>
396
+ <h1>NV Log Viewer 组件</h1>
397
+ <p class="subtitle">功能强大、美观易用的Web日志查看器组件</p>
398
+ <div class="tag-list">
399
+ <span class="tag info">支持5种日志类型</span>
400
+ <span class="tag success">导入/导出功能</span>
401
+ <span class="tag warning">实时过滤</span>
402
+ <span class="tag error">主题切换</span>
403
+ <span class="tag ignore">自动滚动</span>
404
+ <span class="tag info">统计信息</span>
405
+ <span class="tag success">JSON/文本格式</span>
406
+ <span class="tag warning">响应式设计</span>
407
+ </div>
408
+ </header>
409
+
410
+ <div class="demo-grid">
411
+ <!-- 基础使用 -->
412
+ <section class="demo-section">
413
+ <div class="section-header">
414
+ <div class="icon basic">🔧</div>
415
+ <h2>基础使用</h2>
416
+ </div>
417
+
418
+ <div class="control-group">
419
+ <h3>添加不同类型的日志</h3>
420
+ <div class="btn-group">
421
+ <button class="info" onclick="addInfoLog()">📝 信息日志</button>
422
+ <button class="success" onclick="addSuccessLog()">✅ 成功日志</button>
423
+ <button class="warning" onclick="addWarningLog()">⚠️ 警告日志</button>
424
+ <button class="error" onclick="addErrorLog()">❌ 错误日志</button>
425
+ <button class="ignore" onclick="addIgnoreLog()">⚪ 忽略日志</button>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="control-group">
430
+ <h3>组件控制</h3>
431
+ <div class="btn-group">
432
+ <button onclick="toggleLogViewer()">👁️ 切换显示/隐藏</button>
433
+ <button onclick="clearLogs()">🗑️ 清空所有日志</button>
434
+ <button onclick="copyLogs()">📋 复制日志到剪贴板</button>
435
+ </div>
436
+ </div>
437
+
438
+ <div class="demo-container">
439
+ <nv-log-viewer id="basicLogger" max-logs="50" show-time="true" theme="dark"></nv-log-viewer>
440
+ </div>
441
+
442
+ <div class="code-block">
443
+ <span class="code-comment">&lt;!-- HTML 中使用 --&gt;</span><br>
444
+ &lt;nv-log-viewer<br>
445
+ &nbsp;&nbsp;<span class="code-keyword">id</span>=<span class="code-string">"basicLogger"</span><br>
446
+ &nbsp;&nbsp;<span class="code-keyword">max-logs</span>=<span class="code-string">"50"</span><br>
447
+ &nbsp;&nbsp;<span class="code-keyword">show-time</span>=<span class="code-string">"true"</span><br>
448
+ &nbsp;&nbsp;<span class="code-keyword">theme</span>=<span class="code-string">"dark"</span><br>
449
+ &gt;&lt;/nv-log-viewer&gt;<br><br>
450
+ <span class="code-comment">// JavaScript 中使用</span><br>
451
+ <span class="code-keyword">const</span> logger = document.<span class="code-function">getElementById</span>(<span class="code-string">'basicLogger'</span>);<br>
452
+ logger.<span class="code-function">addLog</span>(<span class="code-string">'这是一条信息日志'</span>, <span class="code-string">'info'</span>);<br>
453
+ logger.<span class="code-function">clearLogs</span>();<br>
454
+ logger.<span class="code-function">toggleVisibility</span>();
455
+ </div>
456
+ </section>
457
+
458
+ <!-- 主题配置 -->
459
+ <section class="demo-section">
460
+ <div class="section-header">
461
+ <div class="icon theme">🎨</div>
462
+ <h2>主题配置</h2>
463
+ </div>
464
+
465
+ <div class="control-group">
466
+ <h3>选择主题</h3>
467
+ <div class="radio-group">
468
+ <label class="radio-label">
469
+ <input type="radio" name="theme" value="dark" checked onchange="changeTheme(this.value)">
470
+ 🌙 暗色主题
471
+ </label>
472
+ <label class="radio-label">
473
+ <input type="radio" name="theme" value="light" onchange="changeTheme(this.value)">
474
+ ☀️ 亮色主题
475
+ </label>
476
+ </div>
477
+ </div>
478
+
479
+ <div class="control-group">
480
+ <h3>时间格式设置</h3>
481
+ <div class="attribute-controls">
482
+ <div class="control-item">
483
+ <label>时间格式:</label>
484
+ <select id="timeFormat" onchange="changeTimeFormat()">
485
+ <option value="HH:mm:ss">HH:mm:ss</option>
486
+ <option value="HH:mm:ss.SSS">HH:mm:ss.SSS</option>
487
+ <option value="HH:mm">HH:mm</option>
488
+ <option value="yyyy-MM-dd HH:mm:ss">yyyy-MM-dd HH:mm:ss</option>
489
+ </select>
490
+ </div>
491
+ <div class="control-item">
492
+ <label>显示时间:</label>
493
+ <select onchange="toggleShowTime(this.value)">
494
+ <option value="true">显示</option>
495
+ <option value="false">隐藏</option>
496
+ </select>
497
+ </div>
498
+ </div>
499
+ </div>
500
+
501
+ <div class="demo-container">
502
+ <nv-log-viewer id="themeLogger" max-logs="30" theme="dark" time-format="HH:mm:ss"></nv-log-viewer>
503
+ </div>
504
+
505
+ <div class="code-block">
506
+ <span class="code-comment">// 通过属性配置</span><br>
507
+ &lt;nv-log-viewer <span class="code-keyword">theme</span>=<span class="code-string">"light"</span> <span class="code-keyword">time-format</span>=<span class="code-string">"HH:mm:ss.SSS"</span>&gt;&lt;/nv-log-viewer&gt;<br><br>
508
+ <span class="code-comment">// 通过JavaScript配置</span><br>
509
+ logger.<span class="code-function">setTheme</span>(<span class="code-string">'light'</span>);<br>
510
+ logger.<span class="code-function">setTimeFormat</span>(<span class="code-string">'HH:mm:ss.SSS'</span>);<br>
511
+ logger.<span class="code-function">setShowTime</span>(<span class="code-keyword">false</span>);
512
+ </div>
513
+ </section>
514
+
515
+ <!-- 过滤器控制 -->
516
+ <section class="demo-section">
517
+ <div class="section-header">
518
+ <div class="icon filter">🔍</div>
519
+ <h2>过滤器控制</h2>
520
+ </div>
521
+
522
+ <div class="control-group">
523
+ <h3>日志类型过滤器</h3>
524
+ <div class="checkbox-group">
525
+ <label class="checkbox-label">
526
+ <input type="checkbox" id="filterInfo" checked onchange="updateFilters()">
527
+ <span style="color: #1890ff">● 信息</span>
528
+ </label>
529
+ <label class="checkbox-label">
530
+ <input type="checkbox" id="filterSuccess" checked onchange="updateFilters()">
531
+ <span style="color: #52c41a">● 成功</span>
532
+ </label>
533
+ <label class="checkbox-label">
534
+ <input type="checkbox" id="filterWarning" checked onchange="updateFilters()">
535
+ <span style="color: #faad14">● 警告</span>
536
+ </label>
537
+ <label class="checkbox-label">
538
+ <input type="checkbox" id="filterError" checked onchange="updateFilters()">
539
+ <span style="color: #ff4d4f">● 错误</span>
540
+ </label>
541
+ <label class="checkbox-label">
542
+ <input type="checkbox" id="filterIgnore" checked onchange="updateFilters()">
543
+ <span style="color: #999999">● 忽略</span>
544
+ </label>
545
+ </div>
546
+ </div>
547
+
548
+ <div class="control-group">
549
+ <h3>添加测试日志(用于过滤)</h3>
550
+ <div class="btn-group">
551
+ <button class="info btn-small" onclick="addFilterTestLog('info')">信息</button>
552
+ <button class="success btn-small" onclick="addFilterTestLog('success')">成功</button>
553
+ <button class="warning btn-small" onclick="addFilterTestLog('warning')">警告</button>
554
+ <button class="error btn-small" onclick="addFilterTestLog('error')">错误</button>
555
+ <button class="ignore btn-small" onclick="addFilterTestLog('ignore')">忽略</button>
556
+ </div>
557
+ </div>
558
+
559
+ <div class="demo-container">
560
+ <nv-log-viewer id="filterLogger" theme="dark" show-stats="true"></nv-log-viewer>
561
+ </div>
562
+
563
+ <div class="code-block">
564
+ <span class="code-comment">// 获取不同类型的日志</span><br>
565
+ <span class="code-keyword">const</span> infoLogs = logger.<span class="code-function">getLogsByType</span>(<span class="code-string">'info'</span>);<br>
566
+ <span class="code-keyword">const</span> errorLogs = logger.<span class="code-function">getLogsByType</span>([<span class="code-string">'error'</span>, <span class="code-string">'warning'</span>]);<br><br>
567
+ <span class="code-comment">// 获取统计信息</span><br>
568
+ <span class="code-keyword">const</span> stats = logger.<span class="code-function">getStats</span>();<br>
569
+ console.<span class="code-function">log</span>(<span class="code-string">`总计: <span class="code-keyword">${</span>stats.total<span class="code-keyword">}</span>, 错误: <span class="code-keyword">${</span>stats.error<span class="code-keyword">}</span>`</span>);
570
+ </div>
571
+ </section>
572
+
573
+ <!-- 导入功能 -->
574
+ <section class="demo-section">
575
+ <div class="section-header">
576
+ <div class="icon import">📤</div>
577
+ <h2>导入功能</h2>
578
+ </div>
579
+
580
+ <div class="control-group">
581
+ <h3>从文件导入</h3>
582
+ <div class="btn-group">
583
+ <button onclick="importFromFile()">📁 选择文件导入</button>
584
+ <button onclick="importJsonExample()">📄 导入JSON示例</button>
585
+ <button onclick="importTextExample()">📝 导入文本示例</button>
586
+ </div>
587
+ </div>
588
+
589
+ <div class="notification" id="importNotification" style="display: none;">
590
+ 导入成功!
591
+ </div>
592
+
593
+ <div class="control-group">
594
+ <h3>JSON格式示例</h3>
595
+ <div class="example-json">
596
+ [
597
+ &nbsp;&nbsp;{<br>
598
+ &nbsp;&nbsp;&nbsp;&nbsp;"time": 1700000000000,<br>
599
+ &nbsp;&nbsp;&nbsp;&nbsp;"type": "info",<br>
600
+ &nbsp;&nbsp;&nbsp;&nbsp;"message": "系统启动完成"<br>
601
+ &nbsp;&nbsp;},<br>
602
+ &nbsp;&nbsp;{<br>
603
+ &nbsp;&nbsp;&nbsp;&nbsp;"time": 1700000010000,<br>
604
+ &nbsp;&nbsp;&nbsp;&nbsp;"type": "success",<br>
605
+ &nbsp;&nbsp;&nbsp;&nbsp;"message": "数据加载成功"<br>
606
+ &nbsp;&nbsp;}<br>
607
+ ]
608
+ </div>
609
+ </div>
610
+
611
+ <div class="demo-container">
612
+ <nv-log-viewer id="importLogger" theme="dark"></nv-log-viewer>
613
+ </div>
614
+
615
+ <div class="code-block">
616
+ <span class="code-comment">// 从字符串导入JSON格式日志</span><br>
617
+ <span class="code-keyword">const</span> jsonLogs = <span class="code-string">`[<br>
618
+ &nbsp;&nbsp;{"time": 1700000000000, "type": "info", "message": "日志1"},<br>
619
+ &nbsp;&nbsp;{"time": 1700000010000, "type": "error", "message": "日志2"}<br>
620
+ ]`</span>;<br><br>
621
+ <span class="code-keyword">await</span> logger.<span class="code-function">importLogs</span>(jsonLogs, <span class="code-string">'json'</span>);<br><br>
622
+ <span class="code-comment">// 从字符串导入文本格式日志</span><br>
623
+ <span class="code-keyword">const</span> textLogs = <span class="code-string">`[14:30:25] [INFO] 系统启动<br>[14:30:30] [ERROR] 数据库连接失败`</span>;<br><br>
624
+ <span class="code-keyword">await</span> logger.<span class="code-function">importLogs</span>(textLogs, <span class="code-string">'text'</span>);
625
+ </div>
626
+ </section>
627
+
628
+ <!-- 导出功能 -->
629
+ <section class="demo-section">
630
+ <div class="section-header">
631
+ <div class="icon export">📥</div>
632
+ <h2>导出功能</h2>
633
+ </div>
634
+
635
+ <div class="control-group">
636
+ <h3>导出格式选择</h3>
637
+ <div class="btn-group">
638
+ <button onclick="exportAsJson()">📄 导出为JSON</button>
639
+ <button onclick="exportAsText()">📝 导出为文本</button>
640
+ </div>
641
+ </div>
642
+
643
+ <div class="control-group">
644
+ <h3>导出示例数据</h3>
645
+ <div class="btn-group">
646
+ <button onclick="addSampleLogs()">📊 添加示例数据</button>
647
+ <button onclick="showExportFormat()">👀 查看导出格式</button>
648
+ </div>
649
+ </div>
650
+
651
+ <div class="demo-container">
652
+ <nv-log-viewer id="exportLogger" theme="dark"></nv-log-viewer>
653
+ </div>
654
+
655
+ <div class="code-block">
656
+ <span class="code-comment">// 导出为JSON文件(带元数据)</span><br>
657
+ logger.<span class="code-function">exportLogs</span>(<span class="code-string">'json'</span>);<br>
658
+ <span class="code-comment">// 下载文件: logs_2024-01-01T12-00-00.json</span><br><br>
659
+ <span class="code-comment">// 导出为文本文件</span><br>
660
+ logger.<span class="code-function">exportLogs</span>(<span class="code-string">'text'</span>);<br>
661
+ <span class="code-comment">// 下载文件: logs_2024-01-01T12-00-00.txt</span><br><br>
662
+ <span class="code-comment">// JSON导出格式示例</span><br>
663
+ {<br>
664
+ &nbsp;&nbsp;<span class="code-string">"exportTime"</span>: <span class="code-string">"2024-01-01T12:00:00.000Z"</span>,<br>
665
+ &nbsp;&nbsp;<span class="code-string">"totalLogs"</span>: 5,<br>
666
+ &nbsp;&nbsp;<span class="code-string">"logs"</span>: [<br>
667
+ &nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 日志数组...</span><br>
668
+ &nbsp;&nbsp;]<br>
669
+ }
670
+ </div>
671
+ </section>
672
+
673
+ <!-- 高级控制 -->
674
+ <section class="demo-section">
675
+ <div class="section-header">
676
+ <div class="icon control">⚙️</div>
677
+ <h2>高级控制</h2>
678
+ </div>
679
+
680
+ <div class="control-group">
681
+ <h3>配置选项</h3>
682
+ <div class="attribute-controls">
683
+ <div class="control-item">
684
+ <label>最大日志数:</label>
685
+ <input type="number" id="maxLogs" value="100" min="1" max="1000" onchange="updateMaxLogs()">
686
+ </div>
687
+ <div class="control-item">
688
+ <label>自动滚动:</label>
689
+ <select onchange="updateAutoScroll(this.value)">
690
+ <option value="true">开启</option>
691
+ <option value="false">关闭</option>
692
+ </select>
693
+ </div>
694
+ <div class="control-item">
695
+ <label>显示统计:</label>
696
+ <select onchange="updateShowStats(this.value)">
697
+ <option value="true">显示</option>
698
+ <option value="false">隐藏</option>
699
+ </select>
700
+ </div>
701
+ </div>
702
+ </div>
703
+
704
+ <div class="control-group">
705
+ <h3>批量操作</h3>
706
+ <div class="btn-group">
707
+ <button onclick="addBulkLogs()">📦 批量添加日志</button>
708
+ <button onclick="getAllLogs()">🔍 获取所有日志</button>
709
+ <button onclick="getStats()">📊 获取统计信息</button>
710
+ </div>
711
+ </div>
712
+
713
+ <div class="log-stats-display" id="statsDisplay" style="display: none;">
714
+ <div class="stat-item">
715
+ <span>总计</span>
716
+ <span class="stat-value total" id="statTotal">0</span>
717
+ </div>
718
+ <div class="stat-item">
719
+ <span>信息</span>
720
+ <span class="stat-value info" id="statInfo">0</span>
721
+ </div>
722
+ <div class="stat-item">
723
+ <span>成功</span>
724
+ <span class="stat-value success" id="statSuccess">0</span>
725
+ </div>
726
+ <div class="stat-item">
727
+ <span>警告</span>
728
+ <span class="stat-value warning" id="statWarning">0</span>
729
+ </div>
730
+ <div class="stat-item">
731
+ <span>错误</span>
732
+ <span class="stat-value error" id="statError">0</span>
733
+ </div>
734
+ <div class="stat-item">
735
+ <span>忽略</span>
736
+ <span class="stat-value ignore" id="statIgnore">0</span>
737
+ </div>
738
+ </div>
739
+
740
+ <div class="demo-container">
741
+ <nv-log-viewer id="advancedLogger" theme="dark"></nv-log-viewer>
742
+ </div>
743
+
744
+ <div class="code-block">
745
+ <span class="code-comment">// 事件监听</span><br>
746
+ logger.<span class="code-function">addEventListener</span>(<span class="code-string">'nv-log-add'</span>, (e) => {<br>
747
+ &nbsp;&nbsp;console.<span class="code-function">log</span>(<span class="code-string">'添加了日志:'</span>, e.detail.log);<br>
748
+ &nbsp;&nbsp;console.<span class="code-function">log</span>(<span class="code-string">'总计:'</span>, e.detail.total);<br>
749
+ });<br><br>
750
+ <span class="code-comment">// 动态配置</span><br>
751
+ logger.<span class="code-function">setMaxLogs</span>(<span class="code-string">200</span>);<br>
752
+ logger.<span class="code-function">setAutoScroll</span>(<span class="code-keyword">false</span>);<br>
753
+ logger.<span class="code-function">setShowStats</span>(<span class="code-keyword">false</span>);<br><br>
754
+ <span class="code-comment">// 设置过滤器</span><br>
755
+ logger.<span class="code-function">setFilters</span>({<br>
756
+ &nbsp;&nbsp;info: <span class="code-keyword">true</span>,<br>
757
+ &nbsp;&nbsp;error: <span class="code-keyword">true</span>,<br>
758
+ &nbsp;&nbsp;ignore: <span class="code-keyword">false</span><br>
759
+ });
760
+ </div>
761
+ </section>
762
+ </div>
763
+
764
+ <!-- API参考 -->
765
+ <section class="api-reference">
766
+ <h2>📖 API 参考</h2>
767
+
768
+ <table>
769
+ <thead>
770
+ <tr>
771
+ <th>方法/属性</th>
772
+ <th>说明</th>
773
+ <th>示例</th>
774
+ </tr>
775
+ </thead>
776
+ <tbody>
777
+ <tr>
778
+ <td><code>addLog(message, type?, timestamp?)</code></td>
779
+ <td>添加日志</td>
780
+ <td><code>logger.addLog('消息', 'info')</code></td>
781
+ </tr>
782
+ <tr>
783
+ <td><code>clearLogs()</code></td>
784
+ <td>清空所有日志</td>
785
+ <td><code>logger.clearLogs()</code></td>
786
+ </tr>
787
+ <tr>
788
+ <td><code>toggleVisibility()</code></td>
789
+ <td>切换显示/隐藏</td>
790
+ <td><code>logger.toggleVisibility()</code></td>
791
+ </tr>
792
+ <tr>
793
+ <td><code>exportLogs(format?)</code></td>
794
+ <td>导出日志为文件</td>
795
+ <td><code>logger.exportLogs('json')</code></td>
796
+ </tr>
797
+ <tr>
798
+ <td><code>importLogs(content, format?)</code></td>
799
+ <td>导入日志</td>
800
+ <td><code>await logger.importLogs(json, 'json')</code></td>
801
+ </tr>
802
+ <tr>
803
+ <td><code>copyLogs()</code></td>
804
+ <td>复制日志到剪贴板</td>
805
+ <td><code>await logger.copyLogs()</code></td>
806
+ </tr>
807
+ <tr>
808
+ <td><code>getLogs()</code></td>
809
+ <td>获取所有日志</td>
810
+ <td><code>const logs = logger.getLogs()</code></td>
811
+ </tr>
812
+ <tr>
813
+ <td><code>getLogsByType(types)</code></td>
814
+ <td>按类型获取日志</td>
815
+ <td><code>const errors = logger.getLogsByType('error')</code></td>
816
+ </tr>
817
+ <tr>
818
+ <td><code>getStats()</code></td>
819
+ <td>获取统计信息</td>
820
+ <td><code>const stats = logger.getStats()</code></td>
821
+ </tr>
822
+ <tr>
823
+ <td><code>setMaxLogs(max)</code></td>
824
+ <td>设置最大日志数</td>
825
+ <td><code>logger.setMaxLogs(200)</code></td>
826
+ </tr>
827
+ <tr>
828
+ <td><code>setTheme(theme)</code></td>
829
+ <td>设置主题</td>
830
+ <td><code>logger.setTheme('light')</code></td>
831
+ </tr>
832
+ <tr>
833
+ <td><code>setShowTime(show)</code></td>
834
+ <td>是否显示时间</td>
835
+ <td><code>logger.setShowTime(false)</code></td>
836
+ </tr>
837
+ <tr>
838
+ <td><code>setTimeFormat(format)</code></td>
839
+ <td>设置时间格式</td>
840
+ <td><code>logger.setTimeFormat('HH:mm:ss.SSS')</code></td>
841
+ </tr>
842
+ <tr>
843
+ <td><code>setAutoScroll(scroll)</code></td>
844
+ <td>是否自动滚动</td>
845
+ <td><code>logger.setAutoScroll(false)</code></td>
846
+ </tr>
847
+ <tr>
848
+ <td><code>setShowStats(show)</code></td>
849
+ <td>是否显示统计</td>
850
+ <td><code>logger.setShowStats(false)</code></td>
851
+ </tr>
852
+ </tbody>
853
+ </table>
854
+ </section>
855
+
856
+ <footer>
857
+ <p>© 2024 NV Log Viewer 组件示例 | 版本 1.0.0</p>
858
+ </footer>
859
+ </div>
860
+
861
+ <script>
862
+ var nvlogbw=(()=>{var w=(c,t)=>()=>(t||c((t={exports:{}}).exports,t),t.exports);var L=w((S,y)=>{var b=class extends HTMLElement{static get observedAttributes(){return["max-logs","theme","show-time","time-format","auto-scroll","show-stats","hide-button","auto-destroy","draggable"]}constructor(){super(),this._state={logs:[],showLogs:!0,filters:{info:!0,success:!0,warning:!0,error:!0,ignore:!0},isOpen:!1,isMinimized:!1,originalPosition:{x:0,y:0},originalSize:{width:"800px",height:"600px"},dragInfo:{isDragging:!1,startX:0,startY:0,initialLeft:0,initialTop:0},isDraggingEnabled:!0},this._eventListeners=new Map,this._createFileInput(),this.attachShadow({mode:"open"}),this._config=this._getConfig(),this._render(),this._setupEventListeners()}attributeChangedCallback(t,e,o){if(e!==o)switch(t){case"max-logs":this._config.maxLogs=Math.max(1,parseInt(o,10)||100),this._trimLogs(),this._updateLogsDisplay(),this._updateStats();break;case"theme":this._config.theme=o==="light"?"light":"dark",this._updateTheme();break;case"show-time":this._config.showTime=o!=="false",this._updateLogsDisplay();break;case"time-format":this._config.timeFormat=o||"HH:mm:ss",this._updateLogsDisplay();break;case"auto-scroll":this._config.autoScroll=o!=="false";break;case"show-stats":this._config.showStats=o!=="false",this._updateStatsVisibility();break;case"hide-button":this._config.hideButton=o!=="false",this._updateButtonsVisibility();break;case"auto-destroy":this._config.autoDestroy=o!=="false",this._updateCloseButton();break;case"draggable":this._config.draggable=o!=="false",this._updateDraggableState();break}}connectedCallback(){this._config=this._getConfig(),this._render(),this._setupEventListeners(),this._config.autoDestroy&&window.addEventListener("beforeunload",()=>this._cleanup())}disconnectedCallback(){this._cleanup()}_createFileInput(){this._fileInput=document.createElement("input"),this._fileInput.type="file",this._fileInput.accept=".json,.txt,.log",this._fileInput.style.position="fixed",this._fileInput.style.top="-1000px",this._fileInput.style.left="-1000px",this._fileInput.style.opacity="0",this._fileInput.style.pointerEvents="none",this._fileInput.style.zIndex="-1000",this._fileInput.addEventListener("change",t=>this._handleFileImport(t)),document.body.appendChild(this._fileInput)}_getConfig(){return{maxLogs:Math.max(1,parseInt(this.getAttribute("max-logs")||100,10)),theme:this.getAttribute("theme")==="light"?"light":"dark",showTime:this.getAttribute("show-time")!=="false",timeFormat:this.getAttribute("time-format")||"HH:mm:ss",autoScroll:this.getAttribute("auto-scroll")!=="false",showStats:this.getAttribute("show-stats")!=="false",hideButton:this.getAttribute("hide-button")==="true",autoDestroy:this.getAttribute("auto-destroy")==="true",draggable:this.getAttribute("draggable")!=="false"}}_trimLogs(){this._state.logs.length>this._config.maxLogs&&(this._state.logs=this._state.logs.slice(-this._config.maxLogs))}_updateLogsDisplay(){let t=this.shadowRoot.querySelector("#logContent");if(!t)return;let e=this._state.logs.filter(o=>this._state.filters[o.type]);if(e.length===0){t.innerHTML='<div class="empty-state">\u{1F4DD} \u6CA1\u6709\u65E5\u5FD7\u8BB0\u5F55</div>';return}t.innerHTML="",e.forEach(o=>{let s=document.createElement("div");s.className=`log-entry ${o.type}`;let r="";if(this._config.showTime){let i=new Date(o.time);if(this._config.timeFormat==="relative")r=this._getRelativeTime(o.time);else if(this._config.timeFormat==="HH:mm:ss")r=i.toLocaleTimeString("zh-CN",{hour12:!1});else if(this._config.timeFormat==="HH:mm:ss.ms"){let n=i.toLocaleTimeString("zh-CN",{hour12:!1}),l=String(i.getMilliseconds()).padStart(3,"0");r=`${n}.${l}`}else r=i.toLocaleString("zh-CN")}s.innerHTML=`
863
+ ${this._config.showTime?`<span class="log-time">${r}</span>`:""}
864
+ <span class="log-message">${this._escapeHtml(String(o.message))}</span>
865
+ `,t.appendChild(s)}),this._config.autoScroll&&(t.scrollTop=t.scrollHeight)}_escapeHtml(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}_getRelativeTime(t){let o=Date.now()-t;return o<6e4?`${Math.floor(o/1e3)}\u79D2\u524D`:o<36e5?`${Math.floor(o/6e4)}\u5206\u949F\u524D`:o<864e5?`${Math.floor(o/36e5)}\u5C0F\u65F6\u524D`:`${Math.floor(o/864e5)}\u5929\u524D`}_updateDraggableState(){let t=this.shadowRoot.querySelector("#dragHandle"),e=this.shadowRoot.querySelector("#logHeader"),o=this.shadowRoot.querySelector("#minimizedTitle"),s=this.shadowRoot.querySelector("#popupContainer");t&&(this._config.draggable?(t.style.display="block",t.style.cursor="move",e&&(e.style.cursor="move"),o&&(o.style.cursor="move"),s&&(s.style.cursor="default")):(t.style.display="none",t.style.cursor="default",e&&(e.style.cursor="default"),o&&(o.style.cursor="default"),s&&(s.style.cursor="default")))}_render(){let t=document.createElement("style");t.textContent=this._getStyles(),this.shadowRoot.innerHTML="",this.shadowRoot.appendChild(t),this.shadowRoot.innerHTML+=`
866
+ <!-- \u6D6E\u52A8\u6309\u94AE -->
867
+ <div id="floatingContainer" class="floating-container" style="display: ${this._config.hideButton?"none":"block"}">
868
+ ${this._state.isOpen?"":`
869
+ <div class="floating-button" id="floatingButton" title="\u6253\u5F00\u65E5\u5FD7">
870
+ \u{1F4C4}
871
+ </div>
872
+ `}
873
+ </div>
874
+
875
+ <!-- \u5F39\u51FA\u7A97\u53E3 -->
876
+ <div class="popup-container" id="popupContainer" style="display: none;">
877
+ <!-- \u5B8C\u6574\u7684\u65E5\u5FD7\u5BB9\u5668 -->
878
+ <div class="log-container ${this._state.showLogs?"expanded":"collapsed"} ${this._config.theme}">
879
+ <div class="log-header" id="logHeader">
880
+ <div class="log-title">
881
+ ${this._config.draggable?'<span class="drag-handle" id="dragHandle" title="\u62D6\u52A8">\u2194</span>':""}
882
+ <span class="log-icon">\u{1F4C4}</span>
883
+ <h3>\u65E5\u5FD7\u67E5\u770B\u5668</h3>
884
+ <span class="log-count" id="logCount">0</span>
885
+ </div>
886
+ <div class="log-actions">
887
+ <button class="toggle-btn" title="${this._state.showLogs?"\u9690\u85CF\u65E5\u5FD7":"\u663E\u793A\u65E5\u5FD7"}">
888
+ ${this._state.showLogs?"\u{1F441}\uFE0F":"\u{1F441}\uFE0F\u200D\u{1F5E8}\uFE0F"}
889
+ </button>
890
+ <button class="clear-btn" title="\u6E05\u7A7A\u65E5\u5FD7">\u{1F5D1}\uFE0F</button>
891
+ <button class="export-btn" title="\u5BFC\u51FA\u65E5\u5FD7">\u{1F4E5}</button>
892
+ <button class="import-btn" title="\u5BFC\u5165\u65E5\u5FD7">\u{1F4E4}</button>
893
+ <button class="copy-btn" title="\u590D\u5236\u65E5\u5FD7">\u{1F4CB}</button>
894
+ <button class="popup-btn" id="minimizeBtn" title="\u6700\u5C0F\u5316">_</button>
895
+ <button class="popup-btn" id="closeBtn" title="${this._config.autoDestroy?"\u5173\u95ED\u5E76\u9500\u6BC1":"\u9690\u85CF"}">\u2715</button>
896
+ </div>
897
+ </div>
898
+
899
+ <div class="log-content" id="logContent">
900
+ <div class="empty-state">\u{1F4DD} \u6CA1\u6709\u65E5\u5FD7\u8BB0\u5F55</div>
901
+ </div>
902
+
903
+ <div class="log-footer">
904
+ <div class="log-filter">
905
+ <label class="filter-label">
906
+ <input type="checkbox" class="filter-checkbox" data-type="info" checked>
907
+ <span class="filter-badge info">\u4FE1\u606F</span>
908
+ </label>
909
+ <label class="filter-label">
910
+ <input type="checkbox" class="filter-checkbox" data-type="success" checked>
911
+ <span class="filter-badge success">\u6210\u529F</span>
912
+ </label>
913
+ <label class="filter-label">
914
+ <input type="checkbox" class="filter-checkbox" data-type="warning" checked>
915
+ <span class="filter-badge warning">\u8B66\u544A</span>
916
+ </label>
917
+ <label class="filter-label">
918
+ <input type="checkbox" class="filter-checkbox" data-type="error" checked>
919
+ <span class="filter-badge error">\u9519\u8BEF</span>
920
+ </label>
921
+ <label class="filter-label">
922
+ <input type="checkbox" class="filter-checkbox" data-type="ignore" checked>
923
+ <span class="filter-badge ignore">\u5FFD\u7565</span>
924
+ </label>
925
+ </div>
926
+ <div class="log-stats">
927
+ <div class="stat-row">
928
+ <span>\u603B\u8BA1: <span id="totalCount">0</span></span>
929
+ <span>\u4FE1\u606F: <span id="infoCount">0</span></span>
930
+ <span>\u6210\u529F: <span id="successCount">0</span></span>
931
+ <span>\u8B66\u544A: <span id="warningCount">0</span></span>
932
+ <span>\u9519\u8BEF: <span id="errorCount">0</span></span>
933
+ <span>\u5FFD\u7565: <span id="ignoreCount">0</span></span>
934
+ </div>
935
+ </div>
936
+ </div>
937
+ </div>
938
+
939
+ <!-- \u6700\u5C0F\u5316\u65F6\u7684\u6807\u9898\u680F -->
940
+ <div class="minimized-title" id="minimizedTitle" style="display: none;">
941
+ <div class="minimized-title-left">
942
+ <span>\u{1F4C4} \u65E5\u5FD7\u67E5\u770B\u5668</span>
943
+ <span class="minimized-log-count" id="minimizedLogCount">0</span>
944
+ <div class="minimized-actions">
945
+ <button class="minimized-popup-btn" id="maximizeBtn" title="\u6700\u5927\u5316">\u25A1</button>
946
+ <button class="minimized-popup-btn" id="closeMinimizedBtn" title="${this._config.autoDestroy?"\u5173\u95ED\u5E76\u9500\u6BC1":"\u9690\u85CF"}">\u2715</button>
947
+ </div>
948
+ </div>
949
+ </div>
950
+
951
+ <div class="resize-handle" id="resizeHandle"></div>
952
+ </div>
953
+ `,this._updateTheme(),this._updateDraggableState()}_getStyles(){return`
954
+ :host {
955
+ display: block;
956
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
957
+ font-size: 12px;
958
+ width: 100%;
959
+ }
960
+
961
+ /* \u6D6E\u52A8\u6309\u94AE\u6837\u5F0F */
962
+ .floating-container {
963
+ position: fixed;
964
+ bottom: 20px;
965
+ right: 20px;
966
+ z-index: 9998;
967
+ }
968
+
969
+ .floating-button {
970
+ width: 50px;
971
+ height: 50px;
972
+ border-radius: 50%;
973
+ background: #1890ff;
974
+ color: white;
975
+ display: flex;
976
+ align-items: center;
977
+ justify-content: center;
978
+ font-size: 20px;
979
+ cursor: pointer;
980
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
981
+ transition: all 0.3s ease;
982
+ user-select: none;
983
+ }
984
+
985
+ .floating-button:hover {
986
+ background: #40a9ff;
987
+ transform: scale(1.1);
988
+ }
989
+
990
+ .floating-button:active {
991
+ transform: scale(0.95);
992
+ }
993
+
994
+ /* \u5F39\u51FA\u7A97\u53E3\u6837\u5F0F */
995
+ .popup-container {
996
+ position: fixed;
997
+ z-index: 9999;
998
+ width: 800px;
999
+ height: 600px;
1000
+ display: none;
1001
+ border-radius: 8px;
1002
+ overflow: hidden;
1003
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
1004
+ resize: both;
1005
+ min-width: 400px;
1006
+ min-height: 300px;
1007
+ }
1008
+
1009
+ .popup-container.open {
1010
+ display: block;
1011
+ }
1012
+
1013
+ .popup-container.minimized {
1014
+ width: 320px !important;
1015
+ height: 40px !important;
1016
+ min-height: 40px !important;
1017
+ min-width: 320px !important;
1018
+ resize: none;
1019
+ border-radius: 6px;
1020
+ display: block;
1021
+ }
1022
+
1023
+ .popup-container.minimized .log-container {
1024
+ display: none !important;
1025
+ }
1026
+
1027
+ .popup-container.minimized .resize-handle {
1028
+ display: none;
1029
+ }
1030
+
1031
+ /* \u6700\u5C0F\u5316\u65F6\u7684\u6807\u9898\u680F\u6837\u5F0F */
1032
+ .minimized-title {
1033
+ position: absolute;
1034
+ top: 0;
1035
+ left: 0;
1036
+ right: 0;
1037
+ bottom: 0;
1038
+ display: none;
1039
+ align-items: center;
1040
+ padding: 0 12px;
1041
+ background: var(--header-bg);
1042
+ color: var(--text-color);
1043
+ font-size: 12px;
1044
+ font-weight: 600;
1045
+ cursor: move;
1046
+ border-radius: 6px;
1047
+ overflow: hidden;
1048
+ z-index: 1000;
1049
+ }
1050
+
1051
+ .popup-container.minimized .minimized-title {
1052
+ display: flex;
1053
+ }
1054
+
1055
+ .minimized-title-left {
1056
+ display: flex;
1057
+ align-items: center;
1058
+ gap: 8px;
1059
+ flex: 1;
1060
+ }
1061
+
1062
+ .minimized-log-count {
1063
+ background: var(--button-bg);
1064
+ color: var(--button-text);
1065
+ padding: 2px 8px;
1066
+ border-radius: 10px;
1067
+ font-size: 11px;
1068
+ font-weight: 500;
1069
+ min-width: 20px;
1070
+ text-align: center;
1071
+ }
1072
+
1073
+ .minimized-actions {
1074
+ display: flex;
1075
+ align-items: center;
1076
+ gap: 4px;
1077
+ margin-left: auto;
1078
+ }
1079
+
1080
+ .minimized-popup-btn {
1081
+ background: var(--button-bg);
1082
+ border: none;
1083
+ border-radius: 4px;
1084
+ color: var(--button-text);
1085
+ cursor: pointer;
1086
+ font-size: 12px;
1087
+ width: 24px;
1088
+ height: 24px;
1089
+ display: flex;
1090
+ align-items: center;
1091
+ justify-content: center;
1092
+ transition: all 0.2s;
1093
+ opacity: 0.8;
1094
+ font-family: monospace;
1095
+ }
1096
+
1097
+ .minimized-popup-btn:hover {
1098
+ background: var(--button-hover);
1099
+ transform: translateY(-1px);
1100
+ opacity: 1;
1101
+ }
1102
+
1103
+ .resize-handle {
1104
+ position: absolute;
1105
+ width: 20px;
1106
+ height: 20px;
1107
+ right: 0;
1108
+ bottom: 0;
1109
+ cursor: se-resize;
1110
+ z-index: 10000;
1111
+ }
1112
+
1113
+ .popup-container.minimized .resize-handle {
1114
+ display: none;
1115
+ }
1116
+
1117
+ /* \u6697\u8272\u4E3B\u9898 */
1118
+ .log-container.dark {
1119
+ --bg-color: #1e1e1e;
1120
+ --header-bg: #252526;
1121
+ --border-color: #3e3e42;
1122
+ --text-color: #d4d4d4;
1123
+ --text-muted: #8c8c8c;
1124
+ --log-entry-bg: #2d2d30;
1125
+ --hover-bg: #323234;
1126
+ --info-color: #3794ff;
1127
+ --info-bg: rgba(55, 148, 255, 0.1);
1128
+ --success-color: #4ec9b0;
1129
+ --success-bg: rgba(78, 201, 176, 0.1);
1130
+ --warning-color: #dcdcaa;
1131
+ --warning-bg: rgba(220, 220, 170, 0.1);
1132
+ --error-color: #f48771;
1133
+ --error-bg: rgba(244, 135, 113, 0.1);
1134
+ --ignore-color: #8a8a8a;
1135
+ --ignore-bg: rgba(138, 138, 138, 0.1);
1136
+ --button-bg: #3e3e42;
1137
+ --button-hover: #4a4a4e;
1138
+ --button-text: #d4d4d4;
1139
+ --filter-badge-bg: #3e3e42;
1140
+ }
1141
+
1142
+ /* \u4EAE\u8272\u4E3B\u9898 */
1143
+ .log-container.light {
1144
+ --bg-color: #ffffff;
1145
+ --header-bg: #f5f5f5;
1146
+ --border-color: #e0e0e0;
1147
+ --text-color: #333333;
1148
+ --text-muted: #666666;
1149
+ --log-entry-bg: #f9f9f9;
1150
+ --hover-bg: #f0f0f0;
1151
+ --info-color: #1890ff;
1152
+ --info-bg: rgba(24, 144, 255, 0.08);
1153
+ --success-color: #52c41a;
1154
+ --success-bg: rgba(82, 196, 26, 0.08);
1155
+ --warning-color: #faad14;
1156
+ --warning-bg: rgba(250, 173, 20, 0.08);
1157
+ --error-color: #ff4d4f;
1158
+ --error-bg: rgba(255, 77, 79, 0.08);
1159
+ --ignore-color: #999999;
1160
+ --ignore-bg: rgba(153, 153, 153, 0.08);
1161
+ --button-bg: #e8e8e8;
1162
+ --button-hover: #d9d9d9;
1163
+ --button-text: #595959;
1164
+ --filter-badge-bg: #e8e8e8;
1165
+ }
1166
+
1167
+ .log-container {
1168
+ border: 1px solid var(--border-color);
1169
+ border-radius: 8px;
1170
+ overflow: hidden;
1171
+ transition: all 0.3s ease;
1172
+ background: var(--bg-color);
1173
+ color: var(--text-color);
1174
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1175
+ width: 100%;
1176
+ height: 100%;
1177
+ display: flex;
1178
+ flex-direction: column;
1179
+ }
1180
+
1181
+ .log-container.collapsed {
1182
+ height: auto;
1183
+ min-height: 0;
1184
+ flex: 0 0 auto;
1185
+ }
1186
+
1187
+ .log-container.collapsed .log-header {
1188
+ border-bottom: none;
1189
+ }
1190
+
1191
+ .log-container.collapsed .log-content,
1192
+ .log-container.collapsed .log-footer {
1193
+ display: none;
1194
+ }
1195
+
1196
+ .log-container.expanded {
1197
+ flex: 1;
1198
+ min-height: 0;
1199
+ }
1200
+
1201
+ .log-container.expanded .log-content,
1202
+ .log-container.expanded .log-footer {
1203
+ display: block;
1204
+ }
1205
+
1206
+ .log-header {
1207
+ display: flex;
1208
+ justify-content: space-between;
1209
+ align-items: center;
1210
+ padding: 10px 16px;
1211
+ background: var(--header-bg);
1212
+ border-bottom: 1px solid var(--border-color);
1213
+ user-select: none;
1214
+ transition: background-color 0.2s;
1215
+ flex-shrink: 0;
1216
+ }
1217
+
1218
+ .log-header:hover {
1219
+ background: var(--hover-bg);
1220
+ }
1221
+
1222
+ .log-title {
1223
+ display: flex;
1224
+ align-items: center;
1225
+ gap: 10px;
1226
+ }
1227
+
1228
+ .drag-handle {
1229
+ cursor: move;
1230
+ opacity: 0.6;
1231
+ font-size: 16px;
1232
+ padding: 4px;
1233
+ border-radius: 4px;
1234
+ transition: all 0.2s;
1235
+ }
1236
+
1237
+ .drag-handle:hover {
1238
+ opacity: 1;
1239
+ background: var(--button-bg);
1240
+ }
1241
+
1242
+ .log-icon {
1243
+ font-size: 16px;
1244
+ opacity: 0.8;
1245
+ }
1246
+
1247
+ .log-title h3 {
1248
+ margin: 0;
1249
+ font-size: 14px;
1250
+ font-weight: 600;
1251
+ }
1252
+
1253
+ .log-count {
1254
+ background: var(--button-bg);
1255
+ color: var(--button-text);
1256
+ padding: 2px 8px;
1257
+ border-radius: 10px;
1258
+ font-size: 11px;
1259
+ font-weight: 500;
1260
+ min-width: 20px;
1261
+ text-align: center;
1262
+ }
1263
+
1264
+ .log-actions {
1265
+ display: flex;
1266
+ gap: 6px;
1267
+ }
1268
+
1269
+ /* \u6309\u94AE\u6837\u5F0F */
1270
+ .popup-btn,
1271
+ .toggle-btn,
1272
+ .clear-btn,
1273
+ .export-btn,
1274
+ .import-btn,
1275
+ .copy-btn {
1276
+ background: var(--button-bg);
1277
+ border: none;
1278
+ border-radius: 4px;
1279
+ color: var(--button-text);
1280
+ cursor: pointer;
1281
+ font-size: 13px;
1282
+ width: 28px;
1283
+ height: 28px;
1284
+ display: flex;
1285
+ align-items: center;
1286
+ justify-content: center;
1287
+ transition: all 0.2s;
1288
+ opacity: 0.8;
1289
+ }
1290
+
1291
+ .popup-btn:hover,
1292
+ .toggle-btn:hover,
1293
+ .clear-btn:hover,
1294
+ .export-btn:hover,
1295
+ .import-btn:hover,
1296
+ .copy-btn:hover {
1297
+ background: var(--button-hover);
1298
+ transform: translateY(-1px);
1299
+ opacity: 1;
1300
+ }
1301
+
1302
+ /* \u65E5\u5FD7\u5185\u5BB9\u6837\u5F0F */
1303
+ .log-content {
1304
+ overflow-y: auto;
1305
+ flex: 1;
1306
+ padding: 8px;
1307
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', Consolas, 'Courier New', monospace;
1308
+ line-height: 1.5;
1309
+ word-break: break-word;
1310
+ white-space: pre-wrap;
1311
+ scroll-behavior: smooth;
1312
+ min-height: 0;
1313
+ }
1314
+
1315
+ .log-entry {
1316
+ margin-bottom: 4px;
1317
+ padding: 8px 10px;
1318
+ border-radius: 4px;
1319
+ border-left: 3px solid transparent;
1320
+ background: var(--log-entry-bg);
1321
+ transition: all 0.2s ease;
1322
+ animation: fadeIn 0.3s ease;
1323
+ position: relative;
1324
+ }
1325
+
1326
+ .log-entry:hover {
1327
+ background: var(--hover-bg);
1328
+ transform: translateX(2px);
1329
+ }
1330
+
1331
+ .log-entry.info {
1332
+ border-left-color: var(--info-color);
1333
+ background: var(--info-bg);
1334
+ }
1335
+
1336
+ .log-entry.info:hover {
1337
+ background: var(--info-bg);
1338
+ filter: brightness(1.1);
1339
+ }
1340
+
1341
+ .log-entry.success {
1342
+ border-left-color: var(--success-color);
1343
+ background: var(--success-bg);
1344
+ }
1345
+
1346
+ .log-entry.success:hover {
1347
+ background: var(--success-bg);
1348
+ filter: brightness(1.1);
1349
+ }
1350
+
1351
+ .log-entry.warning {
1352
+ border-left-color: var(--warning-color);
1353
+ background: var(--warning-bg);
1354
+ }
1355
+
1356
+ .log-entry.warning:hover {
1357
+ background: var(--warning-bg);
1358
+ filter: brightness(1.1);
1359
+ }
1360
+
1361
+ .log-entry.error {
1362
+ border-left-color: var(--error-color);
1363
+ background: var(--error-bg);
1364
+ }
1365
+
1366
+ .log-entry.error:hover {
1367
+ background: var(--error-bg);
1368
+ filter: brightness(1.1);
1369
+ }
1370
+
1371
+ .log-entry.ignore {
1372
+ border-left-color: var(--ignore-color);
1373
+ background: var(--ignore-bg);
1374
+ opacity: 0.7;
1375
+ }
1376
+
1377
+ .log-entry.ignore:hover {
1378
+ background: var(--ignore-bg);
1379
+ filter: brightness(1.1);
1380
+ opacity: 0.9;
1381
+ }
1382
+
1383
+ .log-time {
1384
+ color: var(--text-muted);
1385
+ font-size: 10px;
1386
+ margin-right: 10px;
1387
+ user-select: none;
1388
+ font-variant-numeric: tabular-nums;
1389
+ }
1390
+
1391
+ .log-message {
1392
+ font-size: 12px;
1393
+ word-break: break-word;
1394
+ }
1395
+
1396
+ .log-footer {
1397
+ padding: 10px 16px;
1398
+ border-top: 1px solid var(--border-color);
1399
+ background: var(--header-bg);
1400
+ font-size: 11px;
1401
+ flex-shrink: 0;
1402
+ }
1403
+
1404
+ .log-filter {
1405
+ display: flex;
1406
+ flex-wrap: wrap;
1407
+ gap: 12px;
1408
+ margin-bottom: 8px;
1409
+ }
1410
+
1411
+ .filter-label {
1412
+ display: flex;
1413
+ align-items: center;
1414
+ gap: 5px;
1415
+ cursor: pointer;
1416
+ color: var(--text-muted);
1417
+ transition: opacity 0.2s;
1418
+ }
1419
+
1420
+ .filter-label:hover {
1421
+ opacity: 0.8;
1422
+ }
1423
+
1424
+ .filter-checkbox {
1425
+ margin: 0;
1426
+ cursor: pointer;
1427
+ }
1428
+
1429
+ .filter-badge {
1430
+ padding: 2px 8px;
1431
+ border-radius: 3px;
1432
+ font-size: 10px;
1433
+ font-weight: 500;
1434
+ background: var(--filter-badge-bg);
1435
+ }
1436
+
1437
+ .filter-badge.info {
1438
+ background: var(--info-bg);
1439
+ color: var(--info-color);
1440
+ }
1441
+
1442
+ .filter-badge.success {
1443
+ background: var(--success-bg);
1444
+ color: var(--success-color);
1445
+ }
1446
+
1447
+ .filter-badge.warning {
1448
+ background: var(--warning-bg);
1449
+ color: var(--warning-color);
1450
+ }
1451
+
1452
+ .filter-badge.error {
1453
+ background: var(--error-bg);
1454
+ color: var(--error-color);
1455
+ }
1456
+
1457
+ .filter-badge.ignore {
1458
+ background: var(--ignore-bg);
1459
+ color: var(--ignore-color);
1460
+ }
1461
+
1462
+ .log-stats {
1463
+ color: var(--text-muted);
1464
+ }
1465
+
1466
+ .stat-row {
1467
+ display: flex;
1468
+ flex-wrap: wrap;
1469
+ gap: 12px;
1470
+ justify-content: space-between;
1471
+ }
1472
+
1473
+ .stat-row span {
1474
+ display: inline-flex;
1475
+ align-items: center;
1476
+ gap: 4px;
1477
+ }
1478
+
1479
+ .stat-row #totalCount {
1480
+ font-weight: 600;
1481
+ color: var(--text-color);
1482
+ }
1483
+
1484
+ .stat-row #infoCount {
1485
+ color: var(--info-color);
1486
+ font-weight: 600;
1487
+ }
1488
+
1489
+ .stat-row #successCount {
1490
+ color: var(--success-color);
1491
+ font-weight: 600;
1492
+ }
1493
+
1494
+ .stat-row #warningCount {
1495
+ color: var(--warning-color);
1496
+ font-weight: 600;
1497
+ }
1498
+
1499
+ .stat-row #errorCount {
1500
+ color: var(--error-color);
1501
+ font-weight: 600;
1502
+ }
1503
+
1504
+ .stat-row #ignoreCount {
1505
+ color: var(--ignore-color);
1506
+ font-weight: 600;
1507
+ }
1508
+
1509
+ .empty-state {
1510
+ text-align: center;
1511
+ color: var(--text-muted);
1512
+ padding: 40px 20px;
1513
+ font-style: italic;
1514
+ user-select: none;
1515
+ }
1516
+
1517
+ .import-notice {
1518
+ background: var(--info-bg);
1519
+ border-left: 3px solid var(--info-color);
1520
+ color: var(--text-color);
1521
+ padding: 10px;
1522
+ margin: 8px;
1523
+ border-radius: 4px;
1524
+ font-size: 11px;
1525
+ animation: fadeIn 0.3s ease;
1526
+ }
1527
+
1528
+ .import-notice.success {
1529
+ background: var(--success-bg);
1530
+ border-left-color: var(--success-color);
1531
+ }
1532
+
1533
+ .import-notice.error {
1534
+ background: var(--error-bg);
1535
+ border-left-color: var(--error-color);
1536
+ }
1537
+
1538
+ @keyframes fadeIn {
1539
+ from {
1540
+ opacity: 0;
1541
+ transform: translateY(-5px);
1542
+ }
1543
+ to {
1544
+ opacity: 1;
1545
+ transform: translateY(0);
1546
+ }
1547
+ }
1548
+
1549
+ @keyframes pulse {
1550
+ 0%, 100% {
1551
+ opacity: 1;
1552
+ }
1553
+ 50% {
1554
+ opacity: 0.5;
1555
+ }
1556
+ }
1557
+
1558
+ /* \u6EDA\u52A8\u6761\u6837\u5F0F */
1559
+ .log-content::-webkit-scrollbar {
1560
+ width: 6px;
1561
+ }
1562
+
1563
+ .log-content::-webkit-scrollbar-track {
1564
+ background: var(--bg-color);
1565
+ }
1566
+
1567
+ .log-content::-webkit-scrollbar-thumb {
1568
+ background: var(--border-color);
1569
+ border-radius: 3px;
1570
+ }
1571
+
1572
+ .log-content::-webkit-scrollbar-thumb:hover {
1573
+ background: var(--text-muted);
1574
+ }
1575
+ `}_setupEventListeners(){this._eventListeners.forEach(({type:e,handler:o,target:s})=>{s&&s.removeEventListener&&s.removeEventListener(e,o)}),this._eventListeners.clear();let t=e=>{e.target.classList.contains("toggle-btn")?(e.stopPropagation(),e.preventDefault(),this.toggleVisibility()):e.target.classList.contains("clear-btn")?(e.stopPropagation(),e.preventDefault(),this.clearLogs()):e.target.classList.contains("export-btn")?(e.stopPropagation(),e.preventDefault(),this.exportLogs()):e.target.classList.contains("import-btn")?(e.stopPropagation(),e.preventDefault(),this._triggerImport()):e.target.classList.contains("copy-btn")?(e.stopPropagation(),e.preventDefault(),this.copyLogs()):e.target.classList.contains("filter-checkbox")?(e.stopPropagation(),e.preventDefault(),this._applyFilters()):e.target.id==="minimizeBtn"?(e.stopPropagation(),e.preventDefault(),this._minimize()):e.target.id==="closeBtn"?(e.stopPropagation(),e.preventDefault(),this.clos()):e.target.id==="closeMinimizedBtn"?(e.stopPropagation(),e.preventDefault(),this.clos()):e.target.id==="maximizeBtn"?(e.stopPropagation(),e.preventDefault(),this._restore()):e.target.id==="floatingButton"&&(e.stopPropagation(),e.preventDefault(),this.open())};this.shadowRoot.addEventListener("click",t),this._eventListeners.set("click",{type:"click",handler:t,target:this.shadowRoot}),this._setupFloatingButtonDrag(),this._setupDrag(),this._setupResize()}_setupFloatingButtonDrag(){let t=this.shadowRoot.querySelector("#floatingButton"),e=this.shadowRoot.querySelector("#floatingContainer");if(!t||!e)return;let o=!1,s,r,i,n;t.addEventListener("mousedown",l=>{l.preventDefault(),l.stopPropagation();let a=e.getBoundingClientRect();s=l.clientX,r=l.clientY,i=a.left,n=a.top,o=!0;let g=h=>{if(!o)return;let p=h.clientX-s,u=h.clientY-r,f=i+p,m=n+u,x=window.innerWidth-a.width,v=window.innerHeight-a.height;f=Math.max(0,Math.min(f,x)),m=Math.max(0,Math.min(m,v)),e.style.left=`${f}px`,e.style.top=`${m}px`,e.style.right="auto",e.style.bottom="auto"},d=()=>{o=!1,document.removeEventListener("mousemove",g),document.removeEventListener("mouseup",d)};document.addEventListener("mousemove",g),document.addEventListener("mouseup",d)})}_setupDrag(){if(!this._config.draggable)return;let t=this.shadowRoot.querySelector("#popupContainer"),e=this.shadowRoot.querySelector("#dragHandle"),o=this.shadowRoot.querySelector("#logHeader"),s=this.shadowRoot.querySelector("#minimizedTitle");if(!t)return;let r=i=>{i.preventDefault(),i.stopPropagation();let n=t.getBoundingClientRect();this._state.dragInfo.isDragging=!0,this._state.dragInfo.startX=i.clientX,this._state.dragInfo.startY=i.clientY,this._state.dragInfo.initialLeft=n.left,this._state.dragInfo.initialTop=n.top;let l=g=>{if(!this._state.dragInfo.isDragging)return;let d=g.clientX-this._state.dragInfo.startX,h=g.clientY-this._state.dragInfo.startY,p=this._state.dragInfo.initialLeft+d,u=this._state.dragInfo.initialTop+h,f=window.innerWidth-n.width,m=window.innerHeight-n.height;p=Math.max(0,Math.min(p,f)),u=Math.max(0,Math.min(u,m)),t.style.left=`${p}px`,t.style.top=`${u}px`,t.style.transform="none"},a=()=>{this._state.dragInfo.isDragging=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",a)};document.addEventListener("mousemove",l),document.addEventListener("mouseup",a)};e&&e.addEventListener("mousedown",r),o&&o.addEventListener("mousedown",i=>{this._config.draggable&&i.target!==e&&!i.target.classList.contains("toggle-btn")&&!i.target.classList.contains("clear-btn")&&!i.target.classList.contains("export-btn")&&!i.target.classList.contains("import-btn")&&!i.target.classList.contains("copy-btn")&&!i.target.classList.contains("popup-btn")&&r(i)}),s&&s.addEventListener("mousedown",i=>{this._config.draggable&&(i.target.classList.contains("minimized-popup-btn")||r(i))})}_setupResize(){let t=this.shadowRoot.querySelector("#popupContainer"),e=this.shadowRoot.querySelector("#resizeHandle");if(!t||!e)return;let o=!1,s,r,i,n,l=d=>{if(t.classList.contains("minimized"))return;o=!0;let h=t.getBoundingClientRect();s=h.width,r=h.height,i=d.clientX,n=d.clientY,d.preventDefault()},a=d=>{if(!o)return;let h=s+(d.clientX-i),p=r+(d.clientY-n);t.style.width=`${Math.max(400,h)}px`,t.style.height=`${Math.max(300,p)}px`,d.preventDefault()},g=()=>{o=!1};e.addEventListener("mousedown",l),document.addEventListener("mousemove",a),document.addEventListener("mouseup",g)}_cleanup(){this._eventListeners.forEach(({type:t,handler:e,target:o})=>{o&&o.removeEventListener&&o.removeEventListener(t,e)}),this._eventListeners.clear(),this._fileInput&&this._fileInput.parentNode&&this._fileInput.parentNode.removeChild(this._fileInput),this._config.autoDestroy&&this.remove()}_updateTheme(){let t=this.shadowRoot.querySelector(".log-container");t&&(t.className=`log-container ${this._state.showLogs?"expanded":"collapsed"} ${this._config.theme}`)}_updateStatsVisibility(){let t=this.shadowRoot.querySelector(".log-footer");t&&(t.style.display=this._config.showStats?"block":"none")}_updateButtonsVisibility(){let t=this.shadowRoot.querySelector("#floatingContainer");t&&(this._config.hideButton?t.style.display="none":t.style.display=this._state.isOpen?"none":"block")}_updateCloseButton(){let t=this.shadowRoot.querySelector("#closeBtn"),e=this.shadowRoot.querySelector("#closeMinimizedBtn"),o=this._config.autoDestroy?"\u5173\u95ED\u5E76\u9500\u6BC1":"\u9690\u85CF";t&&(t.title=o),e&&(e.title=o)}_updateStats(){let t={total:this._state.logs.length,info:0,success:0,warning:0,error:0,ignore:0};this._state.logs.forEach(g=>{t[g.type]!==void 0&&t[g.type]++});let e=this.shadowRoot.querySelector("#logCount"),o=this.shadowRoot.querySelector("#minimizedLogCount"),s=this.shadowRoot.querySelector("#totalCount"),r=this.shadowRoot.querySelector("#infoCount"),i=this.shadowRoot.querySelector("#successCount"),n=this.shadowRoot.querySelector("#warningCount"),l=this.shadowRoot.querySelector("#errorCount"),a=this.shadowRoot.querySelector("#ignoreCount");e&&(e.textContent=t.total),o&&(o.textContent=t.total),s&&(s.textContent=t.total),r&&(r.textContent=t.info),i&&(i.textContent=t.success),n&&(n.textContent=t.warning),l&&(l.textContent=t.error),a&&(a.textContent=t.ignore)}_applyFilters(){this.shadowRoot.querySelectorAll(".filter-checkbox").forEach(e=>{let o=e.dataset.type;this._state.filters[o]=e.checked}),this._updateLogsDisplay()}_triggerImport(){this._fileInput.click()}async _handleFileImport(t){let e=t.target.files[0];if(e)try{this.addLog("\u5F00\u59CB\u5BFC\u5165\u65E5\u5FD7\u6587\u4EF6...","info");let o=await e.text(),s=[],r="unknown";try{let n=JSON.parse(o);Array.isArray(n)?(s=n,r="json"):typeof n=="object"&&n.logs&&Array.isArray(n.logs)&&(s=n.logs,r="json-with-metadata")}catch{let l=o.split(`
1576
+ `).filter(a=>a.trim());s=this._parseTextLogs(l),r="text"}if(s.length===0)throw new Error("\u6587\u4EF6\u4E2D\u6CA1\u6709\u627E\u5230\u6709\u6548\u7684\u65E5\u5FD7\u6570\u636E");let i=s.map(n=>this._normalizeLogEntry(n)).filter(n=>n!==null);if(i.length===0)throw new Error("\u6587\u4EF6\u4E2D\u6CA1\u6709\u6709\u6548\u7684\u65E5\u5FD7\u6761\u76EE");this._state.logs.push(...i),this._trimLogs(),this._updateLogsDisplay(),this._updateStats(),this._showImportNotice(`\u6210\u529F\u5BFC\u5165 ${i.length} \u6761\u65E5\u5FD7 (${r}\u683C\u5F0F)`,"success"),this.addLog(`\u4ECE\u6587\u4EF6\u5BFC\u5165 ${i.length} \u6761\u65E5\u5FD7`,"success"),this.dispatchEvent(new CustomEvent("nv-log-import",{detail:{count:i.length,format:r,logs:i}}))}catch(o){console.error("\u5BFC\u5165\u65E5\u5FD7\u5931\u8D25:",o),this._showImportNotice(`\u5BFC\u5165\u5931\u8D25: ${o.message}`,"error"),this.addLog(`\u5BFC\u5165\u65E5\u5FD7\u5931\u8D25: ${o.message}`,"error")}}_parseTextLogs(t){let e=[],o=/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?)\]/,s=/^\[(\d{2}:\d{2}:\d{2}(?:\.\d+)?)\]/,r=/\[(INFO|SUCCESS|WARNING|ERROR|IGNORE)\]/i;return t.forEach((i,n)=>{if(!i.trim())return;let l=Date.now(),a="info",g=i,d=i.match(o)||i.match(s);if(d){try{l=new Date(d[1]).getTime()||Date.now()-(t.length-n)*1e3}catch{l=Date.now()-(t.length-n)*1e3}g=i.substring(d[0].length).trim()}let h=g.match(r);if(h){let p=h[1].toLowerCase();["info","success","warning","error","ignore"].includes(p)&&(a=p),g=g.replace(r,"").trim()}e.push({time:l,type:a,message:g})}),e}_normalizeLogEntry(t){if(t==null)return null;if(typeof t=="string")return{time:Date.now(),type:"info",message:t};if(typeof t=="object"){let e={time:t.time||t.timestamp||Date.now(),type:(t.type||t.level||"info").toLowerCase(),message:String(t.message||t.msg||t.text||JSON.stringify(t))};return["info","success","warning","error","ignore"].includes(e.type)||(e.type="info"),(typeof e.time!="number"||isNaN(e.time))&&(e.time=Date.now()),e}return null}_showImportNotice(t,e="info"){let o=this.shadowRoot.querySelector("#logContent");if(!o)return;let s=document.createElement("div");s.className=`import-notice ${e}`,s.textContent=t,o.appendChild(s),setTimeout(()=>{s.parentNode===o&&o.removeChild(s)},3e3)}addLog(t,e="info",o=Date.now()){let s;if(typeof t=="object"&&t!==null){if(s=this._normalizeLogEntry(t),!s){console.warn("\u65E0\u6548\u7684\u65E5\u5FD7\u5BF9\u8C61:",t);return}}else["info","success","warning","error","ignore"].includes(e)||(e="info"),s={message:String(t),type:e,time:o};this._state.logs.push(s),this._trimLogs(),this._updateLogsDisplay(),this._updateStats(),this.dispatchEvent(new CustomEvent("nv-log-add",{detail:{log:s,total:this._state.logs.length,stats:this._getStats()}}))}clearLogs(){let t=this._state.logs.length;this._state.logs=[],this._updateLogsDisplay(),this._updateStats(),this.dispatchEvent(new CustomEvent("nv-log-clear",{detail:{clearedCount:t}}))}toggleVisibility(){this._state.showLogs=!this._state.showLogs;let t=this.shadowRoot.querySelector(".log-container");t&&(t.className=`log-container ${this._state.showLogs?"expanded":"collapsed"} ${this._config.theme}`);let e=this.shadowRoot.querySelector(".toggle-btn");e&&(e.title=this._state.showLogs?"\u9690\u85CF\u65E5\u5FD7":"\u663E\u793A\u65E5\u5FD7",e.textContent=this._state.showLogs?"\u{1F441}\uFE0F":"\u{1F441}\uFE0F\u200D\u{1F5E8}\uFE0F"),this.dispatchEvent(new CustomEvent("nv-log-toggle",{detail:{visible:this._state.showLogs}}))}exportLogs(t="json"){try{if(this._state.logs.length===0){this.addLog("\u6CA1\u6709\u65E5\u5FD7\u53EF\u5BFC\u51FA","warning");return}let e="",o="",s="";if(t==="json"){let a={exportTime:new Date().toISOString(),totalLogs:this._state.logs.length,logs:this._state.logs};e=JSON.stringify(a,null,2),o="application/json",s="json"}else e=this._state.logs.map(a=>`[${new Date(a.time).toLocaleString("zh-CN")}] [${a.type.toUpperCase()}] ${a.message}`).join(`
1577
+ `),o="text/plain;charset=utf-8",s="txt";let r=new Blob([e],{type:o}),i=URL.createObjectURL(r),n=document.createElement("a"),l=new Date().toISOString().replace(/[:.]/g,"-");n.href=i,n.download=`logs_${l}.${s}`,n.click(),URL.revokeObjectURL(i),this.addLog(`\u65E5\u5FD7\u5DF2\u5BFC\u51FA (${this._state.logs.length} \u6761, ${t}\u683C\u5F0F)`,"success")}catch(e){this.addLog(`\u5BFC\u51FA\u65E5\u5FD7\u5931\u8D25: ${e.message}`,"error")}}async importLogs(t,e="auto"){try{let o=[],s="unknown";if(e==="auto"||e==="json")try{let i=JSON.parse(t);Array.isArray(i)?(o=i,s="json"):typeof i=="object"&&i.logs&&Array.isArray(i.logs)&&(o=i.logs,s="json-with-metadata")}catch{if(e==="json")throw new Error("JSON\u683C\u5F0F\u89E3\u6790\u5931\u8D25")}if(o.length===0){let i=t.split(`
1578
+ `).filter(n=>n.trim());o=this._parseTextLogs(i),s="text"}if(o.length===0)throw new Error("\u6CA1\u6709\u627E\u5230\u6709\u6548\u7684\u65E5\u5FD7\u6570\u636E");let r=o.map(i=>this._normalizeLogEntry(i)).filter(i=>i!==null);if(r.length===0)throw new Error("\u6CA1\u6709\u6709\u6548\u7684\u65E5\u5FD7\u6761\u76EE");return this._state.logs.push(...r),this._trimLogs(),this._updateLogsDisplay(),this._updateStats(),this.addLog(`\u5BFC\u5165 ${r.length} \u6761\u65E5\u5FD7 (${s}\u683C\u5F0F)`,"success"),this.dispatchEvent(new CustomEvent("nv-log-import",{detail:{count:r.length,format:s,logs:r}})),{success:!0,count:r.length,format:s}}catch(o){throw console.error("\u5BFC\u5165\u65E5\u5FD7\u5931\u8D25:",o),this.addLog(`\u5BFC\u5165\u65E5\u5FD7\u5931\u8D25: ${o.message}`,"error"),o}}async copyLogs(){try{let t=this._state.logs.map(e=>`[${new Date(e.time).toLocaleString("zh-CN")}] [${e.type.toUpperCase()}] ${e.message}`).join(`
1579
+ `);await navigator.clipboard.writeText(t),this.addLog("\u65E5\u5FD7\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F","success")}catch(t){this.addLog(`\u590D\u5236\u65E5\u5FD7\u5931\u8D25: ${t.message}`,"error")}}getLogs(){return[...this._state.logs]}getLogsByType(t){return Array.isArray(t)||(t=[t]),this._state.logs.filter(e=>t.includes(e.type))}_getStats(){let t={total:this._state.logs.length,info:0,success:0,warning:0,error:0,ignore:0};return this._state.logs.forEach(e=>{t[e.type]!==void 0&&t[e.type]++}),t}setMaxLogs(t){this._config.maxLogs=Math.max(1,parseInt(t,10)||100),this._trimLogs(),this._updateLogsDisplay(),this._updateStats()}setTheme(t){this._config.theme=t==="light"?"light":"dark",this._updateTheme()}setShowTime(t){this._config.showTime=!!t,this._updateLogsDisplay()}setTimeFormat(t){this._config.timeFormat=t,this._updateLogsDisplay()}setAutoScroll(t){this._config.autoScroll=!!t}setShowStats(t){this._config.showStats=!!t,this._updateStatsVisibility()}setHideButton(t){this._config.hideButton=!!t,this._updateButtonsVisibility()}setAutoDestroy(t){this._config.autoDestroy=!!t,this._updateCloseButton()}setDraggable(t){this._config.draggable=!!t,this._updateDraggableState()}open(){if(!this._state.isOpen){let t=this.shadowRoot.querySelector("#popupContainer");if(t){t.style.display="block",t.classList.add("open"),t.classList.remove("minimized");let e=this.shadowRoot.querySelector("#minimizedTitle");e&&(e.style.display="none");let o=800,s=600,r=Math.max(0,(window.innerWidth-o)/2),i=Math.max(0,(window.innerHeight-s)/2);if(t.style.width=`${o}px`,t.style.height=`${s}px`,t.style.left=`${r}px`,t.style.top=`${i}px`,t.style.transform="none",this._state.isMinimized=!1,this._state.showLogs=!0,this._updateLogsDisplay(),this._updateTheme(),this._updateDraggableState(),!this._config.hideButton){let n=this.shadowRoot.querySelector("#floatingContainer");n&&(n.style.display="none")}}this._state.isOpen=!0,this.addLog("\u{1F4C1} \u65E5\u5FD7\u67E5\u770B\u5668\u5DF2\u6253\u5F00","info")}}clos(){if(this._state.isOpen){let t=this.shadowRoot.querySelector("#popupContainer");if(t){this._state.originalPosition={x:parseInt(t.style.left)||0,y:parseInt(t.style.top)||0},t.classList.remove("open"),t.style.display="none";let e=this.shadowRoot.querySelector("#minimizedTitle");e&&(e.style.display="none")}if(this._state.isOpen=!1,!this._config.hideButton){let e=this.shadowRoot.querySelector("#floatingContainer");e&&(e.style.display="block")}this.addLog("\u{1F4C1} \u65E5\u5FD7\u67E5\u770B\u5668\u5DF2\u5173\u95ED","info"),this._config.autoDestroy&&setTimeout(()=>{this._cleanup()},100)}}_minimize(){let t=this.shadowRoot.querySelector("#popupContainer"),e=this.shadowRoot.querySelector("#minimizedTitle");t&&e&&(this._state.originalSize={width:t.style.width||"800px",height:t.style.height||"600px",minHeight:t.style.minHeight},t.classList.add("minimized"),t.style.width="320px",t.style.height="40px",t.style.minHeight="40px",t.style.minWidth="320px",e.style.display="flex",this._state.isMinimized=!0,this.addLog("\u{1F4F1} \u65E5\u5FD7\u67E5\u770B\u5668\u5DF2\u6700\u5C0F\u5316","info"))}_restore(){let t=this.shadowRoot.querySelector("#popupContainer"),e=this.shadowRoot.querySelector("#minimizedTitle");t&&e&&(t.classList.remove("minimized"),e.style.display="none",this._state.originalSize?(t.style.width=this._state.originalSize.width,t.style.height=this._state.originalSize.height,this._state.originalSize.minHeight?t.style.minHeight=this._state.originalSize.minHeight:t.style.minHeight="300px"):(t.style.width="800px",t.style.height="600px",t.style.minHeight="300px"),t.style.minWidth="400px",this._state.isMinimized=!1,this.addLog("\u{1F4F1} \u65E5\u5FD7\u67E5\u770B\u5668\u5DF2\u6062\u590D","info"))}};b.ELEMENT_NAME="nv-log-viewer";var _=async(c={})=>{let t=document.createElement("nv-log-viewer");return t.setAttribute("hide-button",c.hideButton!==!1?"true":"false"),t.setAttribute("auto-destroy",c.autoDestroy!==!1?"true":"false"),c.maxLogs&&t.setAttribute("max-logs",c.maxLogs),c.theme&&t.setAttribute("theme",c.theme),c.showTime!==void 0&&t.setAttribute("show-time",c.showTime),c.timeFormat&&t.setAttribute("time-format",c.timeFormat),c.showStats!==void 0&&t.setAttribute("show-stats",c.showStats),c.autoScroll!==void 0&&t.setAttribute("auto-scroll",c.autoScroll),document.body.appendChild(t),await new Promise(e=>setTimeout(e,100)),t.open(),t};customElements.get("nv-log-viewer")||customElements.define("nv-log-viewer",b);typeof y<"u"&&y.exports&&(y.exports={NvLogViewer:b,once:_})});return L();})();
1580
+
1581
+ </script>
1582
+ <script>
1583
+ // 获取所有日志组件实例
1584
+ const basicLogger = document.getElementById('basicLogger');
1585
+ const themeLogger = document.getElementById('themeLogger');
1586
+ const filterLogger = document.getElementById('filterLogger');
1587
+ const importLogger = document.getElementById('importLogger');
1588
+ const exportLogger = document.getElementById('exportLogger');
1589
+ const advancedLogger = document.getElementById('advancedLogger');
1590
+
1591
+ // 基础使用
1592
+ function addInfoLog() {
1593
+ basicLogger.addLog('这是一条普通的信息日志', 'info');
1594
+ }
1595
+
1596
+ function addSuccessLog() {
1597
+ basicLogger.addLog('操作成功完成!', 'success');
1598
+ }
1599
+
1600
+ function addWarningLog() {
1601
+ basicLogger.addLog('警告:资源使用率过高', 'warning');
1602
+ }
1603
+
1604
+ function addErrorLog() {
1605
+ basicLogger.addLog('错误:数据库连接失败', 'error');
1606
+ }
1607
+
1608
+ function addIgnoreLog() {
1609
+ basicLogger.addLog('跳过:已处理的重复记录', 'ignore');
1610
+ }
1611
+
1612
+ function toggleLogViewer() {
1613
+ basicLogger.toggleVisibility();
1614
+ }
1615
+
1616
+ function clearLogs() {
1617
+ basicLogger.clearLogs();
1618
+ }
1619
+
1620
+ async function copyLogs() {
1621
+ try {
1622
+ await basicLogger.copyLogs();
1623
+ alert('日志已复制到剪贴板!');
1624
+ } catch (error) {
1625
+ alert('复制失败:' + error.message);
1626
+ }
1627
+ }
1628
+
1629
+ // 主题配置
1630
+ function changeTheme(theme) {
1631
+ themeLogger.setTheme(theme);
1632
+ }
1633
+
1634
+ function changeTimeFormat() {
1635
+ const format = document.getElementById('timeFormat').value;
1636
+ themeLogger.setTimeFormat(format);
1637
+ }
1638
+
1639
+ function toggleShowTime(value) {
1640
+ themeLogger.setShowTime(value === 'true');
1641
+ }
1642
+
1643
+ // 过滤器控制
1644
+ function updateFilters() {
1645
+ const filters = {
1646
+ info: document.getElementById('filterInfo').checked,
1647
+ success: document.getElementById('filterSuccess').checked,
1648
+ warning: document.getElementById('filterWarning').checked,
1649
+ error: document.getElementById('filterError').checked,
1650
+ ignore: document.getElementById('filterIgnore').checked
1651
+ };
1652
+ filterLogger.setFilters(filters);
1653
+ }
1654
+
1655
+ function addFilterTestLog(type) {
1656
+ const messages = {
1657
+ info: '测试信息日志',
1658
+ success: '测试成功日志',
1659
+ warning: '测试警告日志',
1660
+ error: '测试错误日志',
1661
+ ignore: '测试忽略日志'
1662
+ };
1663
+ filterLogger.addLog(messages[type], type);
1664
+ }
1665
+
1666
+ // 导入功能
1667
+ function importFromFile() {
1668
+ const importInput = document.createElement('input');
1669
+ importInput.type = 'file';
1670
+ importInput.accept = '.json,.txt,.log';
1671
+
1672
+ importInput.onchange = async (e) => {
1673
+ const file = e.target.files[0];
1674
+ if (!file) return;
1675
+
1676
+ try {
1677
+ const text = await file.text();
1678
+ await importLogger.importLogs(text, 'auto');
1679
+ showNotification('导入成功!从文件导入了日志。', 'success');
1680
+ } catch (error) {
1681
+ showNotification('导入失败:' + error.message, 'error');
1682
+ }
1683
+ };
1684
+
1685
+ importInput.click();
1686
+ }
1687
+
1688
+ function importJsonExample() {
1689
+ const jsonExample = `[
1690
+ {
1691
+ "time": ${Date.now() - 10000},
1692
+ "type": "info",
1693
+ "message": "从JSON导入的日志1"
1694
+ },
1695
+ {
1696
+ "time": ${Date.now() - 5000},
1697
+ "type": "success",
1698
+ "message": "从JSON导入的日志2"
1699
+ },
1700
+ {
1701
+ "time": ${Date.now()},
1702
+ "type": "error",
1703
+ "message": "从JSON导入的日志3"
1704
+ }
1705
+ ]`;
1706
+
1707
+ importLogger.importLogs(jsonExample, 'json')
1708
+ .then(result => {
1709
+ showNotification(`从JSON导入成功,导入了 ${result.count} 条日志`, 'success');
1710
+ })
1711
+ .catch(error => {
1712
+ showNotification('导入失败:' + error.message, 'error');
1713
+ });
1714
+ }
1715
+
1716
+ function importTextExample() {
1717
+ const now = new Date();
1718
+ const time1 = now.toLocaleTimeString('zh-CN', { hour12: false });
1719
+ const time2 = new Date(now.getTime() - 5000).toLocaleTimeString('zh-CN', { hour12: false });
1720
+ const time3 = new Date(now.getTime() - 10000).toLocaleTimeString('zh-CN', { hour12: false });
1721
+
1722
+ const textExample = `[${time3}] [INFO] 从文本导入的日志1
1723
+ [${time2}] [ERROR] 从文本导入的日志2
1724
+ [${time1}] [SUCCESS] 从文本导入的日志3`;
1725
+
1726
+ importLogger.importLogs(textExample, 'text')
1727
+ .then(result => {
1728
+ showNotification(`从文本导入成功,导入了 ${result.count} 条日志`, 'success');
1729
+ })
1730
+ .catch(error => {
1731
+ showNotification('导入失败:' + error.message, 'error');
1732
+ });
1733
+ }
1734
+
1735
+ function showNotification(message, type) {
1736
+ const notification = document.getElementById('importNotification');
1737
+ notification.textContent = message;
1738
+ notification.className = type === 'error' ? 'notification error' : 'notification';
1739
+ notification.style.display = 'block';
1740
+
1741
+ setTimeout(() => {
1742
+ notification.style.display = 'none';
1743
+ }, 3000);
1744
+ }
1745
+
1746
+ // 导出功能
1747
+ function exportAsJson() {
1748
+ exportLogger.exportLogs('json');
1749
+ }
1750
+
1751
+ function exportAsText() {
1752
+ exportLogger.exportLogs('text');
1753
+ }
1754
+
1755
+ function addSampleLogs() {
1756
+ const types = ['info', 'success', 'warning', 'error', 'ignore'];
1757
+ const messages = [
1758
+ '系统启动完成',
1759
+ '用户登录成功',
1760
+ '磁盘空间不足警告',
1761
+ '网络请求超时',
1762
+ '跳过已缓存的数据',
1763
+ '数据处理完成',
1764
+ '文件上传成功',
1765
+ '内存使用率过高',
1766
+ '数据库连接失败',
1767
+ '忽略无效请求'
1768
+ ];
1769
+
1770
+ for (let i = 0; i < 10; i++) {
1771
+ const type = types[i % types.length];
1772
+ const message = messages[i];
1773
+ const time = Date.now() - (10 - i) * 1000;
1774
+ exportLogger.addLog(message, type, time);
1775
+ }
1776
+
1777
+ showNotification('已添加10条示例日志', 'success');
1778
+ }
1779
+
1780
+ function showExportFormat() {
1781
+ const logs = exportLogger.getLogs();
1782
+ if (logs.length === 0) {
1783
+ alert('请先添加一些示例数据');
1784
+ return;
1785
+ }
1786
+
1787
+ const jsonFormat = {
1788
+ exportTime: new Date().toISOString(),
1789
+ totalLogs: logs.length,
1790
+ logs: logs
1791
+ };
1792
+
1793
+ alert('JSON导出格式预览:\n\n' + JSON.stringify(jsonFormat, null, 2));
1794
+ }
1795
+
1796
+ // 高级控制
1797
+ function updateMaxLogs() {
1798
+ const max = parseInt(document.getElementById('maxLogs').value);
1799
+ advancedLogger.setMaxLogs(max);
1800
+ }
1801
+
1802
+ function updateAutoScroll(value) {
1803
+ advancedLogger.setAutoScroll(value === 'true');
1804
+ }
1805
+
1806
+ function updateShowStats(value) {
1807
+ advancedLogger.setShowStats(value === 'true');
1808
+ }
1809
+
1810
+ function addBulkLogs() {
1811
+ for (let i = 0; i < 20; i++) {
1812
+ const types = ['info', 'success', 'warning', 'error', 'ignore'];
1813
+ const type = types[Math.floor(Math.random() * types.length)];
1814
+ const time = Date.now() - Math.floor(Math.random() * 60000);
1815
+
1816
+ advancedLogger.addLog(`批量添加的日志 ${i + 1}`, type, time);
1817
+ }
1818
+
1819
+ showNotification('已批量添加20条日志', 'success');
1820
+ }
1821
+
1822
+ function getAllLogs() {
1823
+ const logs = advancedLogger.getLogs();
1824
+ alert(`当前共有 ${logs.length} 条日志\n\n最后5条日志:\n${
1825
+ logs.slice(-5).map(log =>
1826
+ `[${new Date(log.time).toLocaleTimeString()}] [${log.type.toUpperCase()}] ${log.message}`
1827
+ ).join('\n')
1828
+ }`);
1829
+ }
1830
+
1831
+ function getStats() {
1832
+ const stats = advancedLogger.getStats();
1833
+ document.getElementById('statTotal').textContent = stats.total;
1834
+ document.getElementById('statInfo').textContent = stats.info;
1835
+ document.getElementById('statSuccess').textContent = stats.success;
1836
+ document.getElementById('statWarning').textContent = stats.warning;
1837
+ document.getElementById('statError').textContent = stats.error;
1838
+ document.getElementById('statIgnore').textContent = stats.ignore;
1839
+
1840
+ document.getElementById('statsDisplay').style.display = 'grid';
1841
+ }
1842
+
1843
+ // 事件监听示例
1844
+ basicLogger.addEventListener('nv-log-add', (e) => {
1845
+ console.log('添加日志事件:', e.detail);
1846
+ });
1847
+
1848
+ basicLogger.addEventListener('nv-log-clear', (e) => {
1849
+ console.log('清空日志事件,清除了', e.detail.clearedCount, '条日志');
1850
+ });
1851
+
1852
+ // 初始化
1853
+ window.addEventListener('DOMContentLoaded', () => {
1854
+ // 添加一些初始日志
1855
+ basicLogger.addLog('日志查看器已初始化完成', 'success');
1856
+ themeLogger.addLog('主题配置示例已加载', 'info');
1857
+ filterLogger.addLog('过滤器功能已启用', 'info');
1858
+ importLogger.addLog('导入功能已就绪', 'info');
1859
+ exportLogger.addLog('导出功能已准备', 'info');
1860
+ advancedLogger.addLog('高级控制功能已激活', 'info');
1861
+
1862
+ console.log('NV Log Viewer 组件示例已加载完成');
1863
+ });
1864
+ </script>
1865
+ </body>
1866
+ </html>