jacky-proxy 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,736 @@
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>Mock 场景管理</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
15
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
16
+ min-height: 100vh;
17
+ padding: 20px;
18
+ overflow-x: hidden;
19
+ box-sizing: border-box;
20
+ }
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+ .container {
25
+ max-width: 1400px;
26
+ margin: 0 auto;
27
+ background: white;
28
+ border-radius: 12px;
29
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2);
30
+ padding: 30px;
31
+ overflow-x: hidden;
32
+ box-sizing: border-box;
33
+ }
34
+ .layout {
35
+ display: flex;
36
+ gap: 30px;
37
+ margin-top: 20px;
38
+ overflow-x: hidden;
39
+ width: 100%;
40
+ box-sizing: border-box;
41
+ }
42
+ .left-panel {
43
+ flex: 0 0 400px;
44
+ min-width: 0;
45
+ overflow-x: hidden;
46
+ box-sizing: border-box;
47
+ }
48
+ .right-panel {
49
+ flex: 1;
50
+ min-width: 0;
51
+ max-height: calc(100vh - 200px);
52
+ overflow-y: auto;
53
+ overflow-x: hidden;
54
+ padding-right: 10px;
55
+ box-sizing: border-box;
56
+ }
57
+ .right-panel::-webkit-scrollbar {
58
+ width: 8px;
59
+ }
60
+ .right-panel::-webkit-scrollbar-track {
61
+ background: #f1f1f1;
62
+ border-radius: 4px;
63
+ }
64
+ .right-panel::-webkit-scrollbar-thumb {
65
+ background: #888;
66
+ border-radius: 4px;
67
+ }
68
+ .right-panel::-webkit-scrollbar-thumb:hover {
69
+ background: #555;
70
+ }
71
+ h1 {
72
+ color: #333;
73
+ margin-bottom: 10px;
74
+ font-size: 28px;
75
+ }
76
+ .subtitle {
77
+ color: #666;
78
+ margin-bottom: 30px;
79
+ font-size: 14px;
80
+ }
81
+ .section {
82
+ margin-bottom: 30px;
83
+ }
84
+ .right-panel .section {
85
+ margin-bottom: 20px;
86
+ }
87
+ .section-title {
88
+ font-size: 18px;
89
+ color: #333;
90
+ margin-bottom: 15px;
91
+ padding-bottom: 10px;
92
+ border-bottom: 2px solid #667eea;
93
+ }
94
+ .scenario-list {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 10px;
98
+ width: 100%;
99
+ overflow-x: hidden;
100
+ box-sizing: border-box;
101
+ }
102
+ .scenario-item {
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: space-between;
106
+ padding: 15px;
107
+ border: 2px solid #e0e0e0;
108
+ border-radius: 8px;
109
+ transition: all 0.3s;
110
+ background: #f9f9f9;
111
+ gap: 15px;
112
+ width: 100%;
113
+ box-sizing: border-box;
114
+ overflow: hidden;
115
+ min-height: 60px;
116
+ height: auto;
117
+ }
118
+ .scenario-item:hover {
119
+ border-color: #667eea;
120
+ background: #f0f0ff;
121
+ }
122
+ .scenario-item.active {
123
+ border-color: #667eea;
124
+ background: #e8e8ff;
125
+ }
126
+ .scenario-info {
127
+ flex: 1;
128
+ min-width: 0;
129
+ overflow: hidden;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 5px;
133
+ }
134
+ .scenario-name {
135
+ font-weight: 600;
136
+ color: #333;
137
+ margin-bottom: 5px;
138
+ display: flex;
139
+ align-items: center;
140
+ flex-wrap: wrap;
141
+ gap: 5px;
142
+ }
143
+ .scenario-label {
144
+ font-size: 14px;
145
+ color: #666;
146
+ white-space: nowrap;
147
+ overflow: hidden;
148
+ text-overflow: ellipsis;
149
+ max-width: 100%;
150
+ }
151
+ .scenario-actions {
152
+ display: flex;
153
+ gap: 10px;
154
+ flex-shrink: 0;
155
+ min-width: 100px;
156
+ }
157
+ button {
158
+ padding: 8px 20px;
159
+ border: none;
160
+ border-radius: 6px;
161
+ cursor: pointer;
162
+ font-size: 14px;
163
+ transition: all 0.3s;
164
+ font-weight: 500;
165
+ white-space: nowrap;
166
+ min-width: 80px;
167
+ text-align: center;
168
+ }
169
+ .btn-primary {
170
+ background: #667eea;
171
+ color: white;
172
+ }
173
+ .btn-primary:hover {
174
+ background: #5568d3;
175
+ transform: translateY(-2px);
176
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
177
+ }
178
+ .btn-primary:disabled {
179
+ background: #ccc;
180
+ cursor: not-allowed;
181
+ transform: none;
182
+ white-space: nowrap;
183
+ overflow: visible;
184
+ }
185
+ .btn-secondary {
186
+ background: #6c757d;
187
+ color: white;
188
+ }
189
+ .btn-secondary:hover {
190
+ background: #5a6268;
191
+ }
192
+ .status {
193
+ padding: 10px;
194
+ border-radius: 6px;
195
+ margin-bottom: 20px;
196
+ font-size: 14px;
197
+ }
198
+ .status.success {
199
+ background: #d4edda;
200
+ color: #155724;
201
+ border: 1px solid #c3e6cb;
202
+ }
203
+ .status.error {
204
+ background: #f8d7da;
205
+ color: #721c24;
206
+ border: 1px solid #f5c6cb;
207
+ }
208
+ .status.info {
209
+ background: #d1ecf1;
210
+ color: #0c5460;
211
+ border: 1px solid #bee5eb;
212
+ }
213
+ .loading {
214
+ text-align: center;
215
+ padding: 20px;
216
+ color: #666;
217
+ }
218
+ .badge {
219
+ display: inline-block;
220
+ padding: 4px 10px;
221
+ border-radius: 12px;
222
+ font-size: 12px;
223
+ font-weight: 600;
224
+ margin-left: 10px;
225
+ }
226
+ .badge-active {
227
+ background: #28a745;
228
+ color: white;
229
+ }
230
+ @media (max-width: 1024px) {
231
+ .layout {
232
+ flex-direction: column;
233
+ overflow-x: hidden;
234
+ }
235
+ .left-panel {
236
+ flex: 1;
237
+ width: 100%;
238
+ }
239
+ .right-panel {
240
+ max-height: none;
241
+ width: 100%;
242
+ }
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <div class="container">
248
+ <div id="status"></div>
249
+
250
+ <div class="layout">
251
+ <div class="left-panel">
252
+ <div class="section">
253
+ <h2 class="section-title">MockId 切换</h2>
254
+ <div id="mockid-info" style="margin-bottom: 15px; padding: 10px; background: #f0f0f0; border-radius: 6px;">
255
+ <div>当前 MockId: <strong id="current-mockid">加载中...</strong></div>
256
+ <div style="font-size: 12px; color: #666; margin-top: 5px;">路径: <span id="current-path">-</span></div>
257
+ </div>
258
+ <div style="margin-bottom: 15px;">
259
+ <input
260
+ type="text"
261
+ id="mockid-search"
262
+ placeholder="搜索 MockId 或路径..."
263
+ style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;"
264
+ />
265
+ <div id="mockid-count" style="font-size: 12px; color: #666; margin-top: 5px;"></div>
266
+ </div>
267
+ <div id="mockids" class="scenario-list" style="max-height: calc(100vh - 400px); overflow-y: auto; padding-right: 5px;">
268
+ <div class="loading">加载中...</div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ <div class="right-panel">
274
+ <div class="section">
275
+ <h2 class="section-title">接口场景切换</h2>
276
+ <div id="scenarios-container" style="width: 100%; overflow-x: hidden; box-sizing: border-box;">
277
+ <div class="loading">加载中...</div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <script>
285
+ let mockIdSearchTimeout = null;
286
+
287
+ // 复制文本到剪贴板(优先使用 Clipboard API,降级到 execCommand)
288
+ async function copyToClipboard(text) {
289
+ if (!text) {
290
+ showStatus('没有可复制的路径', 'error');
291
+ return false;
292
+ }
293
+
294
+ try {
295
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
296
+ await navigator.clipboard.writeText(text);
297
+ return true;
298
+ }
299
+ } catch (e) {
300
+ // 忽略,走降级方案
301
+ }
302
+
303
+ try {
304
+ const textarea = document.createElement('textarea');
305
+ textarea.value = text;
306
+ textarea.style.position = 'fixed';
307
+ textarea.style.top = '-9999px';
308
+ textarea.style.left = '-9999px';
309
+ textarea.setAttribute('readonly', '');
310
+ document.body.appendChild(textarea);
311
+ textarea.select();
312
+ textarea.setSelectionRange(0, textarea.value.length);
313
+ const ok = document.execCommand('copy');
314
+ document.body.removeChild(textarea);
315
+ return ok;
316
+ } catch (e) {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ // 加载 MockId 列表
322
+ async function loadMockIds(search = '') {
323
+ try {
324
+ const url = search ? `/mock-admin/mockids?search=${encodeURIComponent(search)}` : '/mock-admin/mockids';
325
+ const response = await fetch(url);
326
+ const data = await response.json();
327
+
328
+ document.getElementById('current-mockid').textContent = data.current || '未设置';
329
+
330
+ // 更新计数信息
331
+ const countDiv = document.getElementById('mockid-count');
332
+ if (data.total) {
333
+ if (search) {
334
+ countDiv.textContent = `显示 ${data.filtered} / ${data.total} 个配置`;
335
+ } else {
336
+ countDiv.textContent = `共 ${data.total} 个配置`;
337
+ }
338
+ }
339
+
340
+ const container = document.getElementById('mockids');
341
+ container.innerHTML = '';
342
+
343
+ if (data.available.length === 0) {
344
+ container.innerHTML = '<div class="status info">未找到匹配的 mockId</div>';
345
+ return;
346
+ }
347
+
348
+ // 显示所有匹配的结果(不再限制数量)
349
+ data.available.forEach(mock => {
350
+ const isActive = data.current === mock.id;
351
+ const existsBadge = mock.exists ? '' : '<span style="color: #dc3545; font-size: 11px; margin-left: 5px;">(目录不存在)</span>';
352
+ const item = document.createElement('div');
353
+ item.className = 'scenario-item' + (isActive ? ' active' : '');
354
+ if (!mock.exists) {
355
+ item.style.opacity = '0.6';
356
+ }
357
+ item.innerHTML = `
358
+ <div class="scenario-info">
359
+ <div class="scenario-name">
360
+ MockId: ${mock.id}
361
+ ${isActive ? '<span class="badge badge-active">当前</span>' : ''}
362
+ ${existsBadge}
363
+ </div>
364
+ <div class="scenario-label">${mock.path}</div>
365
+ </div>
366
+ <div class="scenario-actions">
367
+ <button
368
+ class="btn-primary"
369
+ ${isActive || !mock.exists ? 'disabled' : ''}
370
+ onclick="switchMockId(${mock.id})"
371
+ >
372
+ ${isActive ? '当前使用' : (!mock.exists ? '不可用' : '切换')}
373
+ </button>
374
+ </div>
375
+ `;
376
+ container.appendChild(item);
377
+ });
378
+ } catch (error) {
379
+ showStatus('加载 mockId 列表失败: ' + error.message, 'error');
380
+ }
381
+ }
382
+
383
+ // 搜索功能
384
+ document.addEventListener('DOMContentLoaded', function() {
385
+ const searchInput = document.getElementById('mockid-search');
386
+ if (searchInput) {
387
+ searchInput.addEventListener('input', function(e) {
388
+ const search = e.target.value.trim();
389
+
390
+ // 防抖处理
391
+ clearTimeout(mockIdSearchTimeout);
392
+ mockIdSearchTimeout = setTimeout(() => {
393
+ loadMockIds(search);
394
+ }, 300);
395
+ });
396
+ }
397
+ });
398
+
399
+ // 切换 MockId(静默操作,不显示提示)
400
+ async function switchMockId(mockId) {
401
+ try {
402
+ const response = await fetch(`/mock-admin/mockid/${mockId}`, { method: 'POST' });
403
+ const data = await response.json();
404
+
405
+ if (data.success) {
406
+ document.getElementById('current-mockid').textContent = data.mockId;
407
+ document.getElementById('current-path').textContent = data.path;
408
+ setTimeout(() => {
409
+ // 保留搜索状态
410
+ const searchInput = document.getElementById('mockid-search');
411
+ const search = searchInput ? searchInput.value.trim() : '';
412
+ loadMockIds(search);
413
+ loadScenarios();
414
+ }, 500);
415
+ }
416
+ } catch (error) {
417
+ console.error('切换 MockId 失败:', error);
418
+ }
419
+ }
420
+
421
+ // 加载场景列表(所有接口)
422
+ async function loadScenarios() {
423
+ try {
424
+ const response = await fetch('/mock-admin/scenarios');
425
+ const data = await response.json();
426
+
427
+ document.getElementById('current-path').textContent = data.mockPath || '-';
428
+
429
+ const container = document.getElementById('scenarios-container');
430
+ container.innerHTML = '';
431
+
432
+ const available = data.available || {};
433
+ if (Object.keys(available).length === 0) {
434
+ container.innerHTML = '<div class="status info">未找到可用的接口</div>';
435
+ return;
436
+ }
437
+
438
+ // 按接口名排序,多场景接口放在前面
439
+ const sortedInterfaceNames = Object.keys(available).sort((a, b) => {
440
+ const aHasMultiple = available[a].hasMultipleScenarios;
441
+ const bHasMultiple = available[b].hasMultipleScenarios;
442
+ if (aHasMultiple && !bHasMultiple) return -1;
443
+ if (!aHasMultiple && bHasMultiple) return 1;
444
+ return a.localeCompare(b);
445
+ });
446
+
447
+ sortedInterfaceNames.forEach(interfaceName => {
448
+ const interfaceData = available[interfaceName];
449
+ const scenarios = interfaceData.scenarios || [];
450
+ const isDisabled = interfaceData.disabled === true;
451
+ const hasMultipleScenarios = interfaceData.hasMultipleScenarios;
452
+
453
+ // 接口项容器
454
+ const interfaceItem = document.createElement('div');
455
+ interfaceItem.style.marginBottom = '12px';
456
+ interfaceItem.style.backgroundColor = hasMultipleScenarios ? '#f8f9fa' : '#fff';
457
+ interfaceItem.style.border = '1px solid #e9ecef';
458
+ interfaceItem.style.borderRadius = '6px';
459
+ interfaceItem.style.padding = '12px';
460
+ interfaceItem.style.opacity = isDisabled ? '0.6' : '1';
461
+ interfaceItem.style.transition = 'all 0.2s';
462
+ interfaceItem.style.width = '100%';
463
+ interfaceItem.style.boxSizing = 'border-box';
464
+ interfaceItem.style.overflow = 'hidden';
465
+
466
+ // 接口头部:checkbox + 接口名
467
+ const header = document.createElement('div');
468
+ header.style.display = 'flex';
469
+ header.style.alignItems = 'center';
470
+ header.style.marginBottom = hasMultipleScenarios ? '10px' : '0';
471
+ header.style.width = '100%';
472
+ header.style.boxSizing = 'border-box';
473
+ header.style.overflow = 'hidden';
474
+
475
+ const checkbox = document.createElement('input');
476
+ checkbox.type = 'checkbox';
477
+ checkbox.checked = !isDisabled;
478
+ checkbox.style.marginRight = '12px';
479
+ checkbox.style.width = '18px';
480
+ checkbox.style.height = '18px';
481
+ checkbox.style.cursor = 'pointer';
482
+ checkbox.onchange = () => toggleInterface(interfaceName, checkbox.checked);
483
+
484
+ const name = document.createElement('span');
485
+ name.style.fontSize = '15px';
486
+ name.style.fontWeight = '500';
487
+ name.style.color = isDisabled ? '#999' : '#212529';
488
+ name.style.flex = '1';
489
+ name.style.minWidth = '0';
490
+ name.style.overflow = 'hidden';
491
+ name.style.textOverflow = 'ellipsis';
492
+ name.style.whiteSpace = 'nowrap';
493
+ name.textContent = interfaceName;
494
+
495
+ header.appendChild(checkbox);
496
+ header.appendChild(name);
497
+ interfaceItem.appendChild(header);
498
+
499
+ // 如果有多个场景,全部展示出来
500
+ if (hasMultipleScenarios) {
501
+ const scenariosList = document.createElement('div');
502
+ scenariosList.style.marginLeft = '30px';
503
+ scenariosList.style.marginTop = '8px';
504
+ scenariosList.style.opacity = isDisabled ? '0.5' : '1';
505
+ scenariosList.style.width = 'calc(100% - 30px)';
506
+ scenariosList.style.boxSizing = 'border-box';
507
+ scenariosList.style.overflow = 'hidden';
508
+
509
+ scenarios.forEach((scenario, index) => {
510
+ const isActive = scenario.isActive;
511
+
512
+ const scenarioItem = document.createElement('div');
513
+ scenarioItem.style.display = 'flex';
514
+ scenarioItem.style.alignItems = 'center';
515
+ scenarioItem.style.padding = '8px 12px';
516
+ scenarioItem.style.marginBottom = index < scenarios.length - 1 ? '6px' : '0';
517
+ scenarioItem.style.backgroundColor = isActive ? '#e7f3ff' : '#fff';
518
+ scenarioItem.style.border = isActive ? '1px solid #1890ff' : '1px solid #e0e0e0';
519
+ scenarioItem.style.borderRadius = '4px';
520
+ scenarioItem.style.transition = 'all 0.2s';
521
+ scenarioItem.style.width = '100%';
522
+ scenarioItem.style.boxSizing = 'border-box';
523
+ scenarioItem.style.overflow = 'hidden';
524
+ scenarioItem.style.gap = '10px';
525
+
526
+ if (!isDisabled) {
527
+ scenarioItem.onmouseover = () => {
528
+ if (!isActive) {
529
+ scenarioItem.style.backgroundColor = '#f5f5f5';
530
+ scenarioItem.style.borderColor = '#1890ff';
531
+ }
532
+ };
533
+ scenarioItem.onmouseout = () => {
534
+ if (!isActive) {
535
+ scenarioItem.style.backgroundColor = '#fff';
536
+ scenarioItem.style.borderColor = '#e0e0e0';
537
+ }
538
+ };
539
+ }
540
+
541
+ const scenarioName = document.createElement('span');
542
+ scenarioName.style.fontSize = '13px';
543
+ scenarioName.style.fontFamily = 'monospace';
544
+ scenarioName.style.color = isActive ? '#1890ff' : '#495057';
545
+ scenarioName.style.flex = '1';
546
+ scenarioName.style.minWidth = '0';
547
+ scenarioName.style.overflow = 'hidden';
548
+ scenarioName.style.textOverflow = 'ellipsis';
549
+ scenarioName.style.whiteSpace = 'nowrap';
550
+ scenarioName.textContent = scenario.name;
551
+
552
+ if (isActive) {
553
+ const activeBadge = document.createElement('span');
554
+ activeBadge.textContent = '✓';
555
+ activeBadge.style.marginLeft = '8px';
556
+ activeBadge.style.color = '#1890ff';
557
+ activeBadge.style.fontWeight = 'bold';
558
+ scenarioName.appendChild(activeBadge);
559
+ }
560
+
561
+ const switchBtn = document.createElement('button');
562
+ switchBtn.textContent = isActive ? '当前' : '切换';
563
+ switchBtn.style.padding = '6px 16px';
564
+ switchBtn.style.fontSize = '12px';
565
+ switchBtn.style.border = '1px solid #1890ff';
566
+ switchBtn.style.borderRadius = '4px';
567
+ switchBtn.style.backgroundColor = isActive ? '#1890ff' : '#fff';
568
+ switchBtn.style.color = isActive ? '#fff' : '#1890ff';
569
+ switchBtn.style.cursor = isActive || isDisabled ? 'not-allowed' : 'pointer';
570
+ switchBtn.style.fontWeight = '500';
571
+ switchBtn.style.transition = 'all 0.2s';
572
+ switchBtn.disabled = isActive || isDisabled;
573
+
574
+ if (!isActive && !isDisabled) {
575
+ switchBtn.onmouseover = () => {
576
+ switchBtn.style.backgroundColor = '#1890ff';
577
+ switchBtn.style.color = '#fff';
578
+ };
579
+ switchBtn.onmouseout = () => {
580
+ switchBtn.style.backgroundColor = '#fff';
581
+ switchBtn.style.color = '#1890ff';
582
+ };
583
+ }
584
+
585
+ if (isActive || isDisabled) {
586
+ switchBtn.style.opacity = '0.6';
587
+ }
588
+
589
+ switchBtn.onclick = () => {
590
+ if (!isActive && !isDisabled) {
591
+ switchScenario(interfaceName, scenario.id);
592
+ }
593
+ };
594
+
595
+ // 拷贝本地文件路径按钮
596
+ const copyBtn = document.createElement('button');
597
+ copyBtn.textContent = '拷贝路径';
598
+ copyBtn.title = scenario.filePath ? scenario.filePath : '无可用本地路径';
599
+ copyBtn.style.padding = '6px 12px';
600
+ copyBtn.style.fontSize = '12px';
601
+ copyBtn.style.border = '1px solid #6c757d';
602
+ copyBtn.style.borderRadius = '4px';
603
+ copyBtn.style.backgroundColor = '#fff';
604
+ copyBtn.style.color = '#6c757d';
605
+ copyBtn.style.cursor = (!scenario.filePath || isDisabled) ? 'not-allowed' : 'pointer';
606
+ copyBtn.style.fontWeight = '500';
607
+ copyBtn.style.transition = 'all 0.2s';
608
+ copyBtn.disabled = (!scenario.filePath) || isDisabled;
609
+
610
+ if (!copyBtn.disabled) {
611
+ copyBtn.onmouseover = () => {
612
+ copyBtn.style.backgroundColor = '#6c757d';
613
+ copyBtn.style.color = '#fff';
614
+ };
615
+ copyBtn.onmouseout = () => {
616
+ copyBtn.style.backgroundColor = '#fff';
617
+ copyBtn.style.color = '#6c757d';
618
+ };
619
+ } else {
620
+ copyBtn.style.opacity = '0.6';
621
+ }
622
+
623
+ copyBtn.onclick = async () => {
624
+ if (copyBtn.disabled) return;
625
+ const ok = await copyToClipboard(scenario.filePath);
626
+ if (ok) {
627
+ // 复制成功:不展示顶部提示栏,改为按钮自身短暂反馈
628
+ const oldText = copyBtn.textContent;
629
+ const oldBg = copyBtn.style.backgroundColor;
630
+ const oldColor = copyBtn.style.color;
631
+ copyBtn.textContent = '已复制';
632
+ copyBtn.style.backgroundColor = '#28a745';
633
+ copyBtn.style.color = '#fff';
634
+ copyBtn.disabled = true;
635
+ setTimeout(() => {
636
+ copyBtn.textContent = oldText;
637
+ copyBtn.style.backgroundColor = oldBg || '#fff';
638
+ copyBtn.style.color = oldColor || '#6c757d';
639
+ copyBtn.disabled = false;
640
+ }, 1200);
641
+ } else {
642
+ showStatus('拷贝失败:请检查浏览器权限或使用 HTTPS/localhost', 'error');
643
+ }
644
+ };
645
+
646
+ scenarioItem.appendChild(scenarioName);
647
+ scenarioItem.appendChild(copyBtn);
648
+ scenarioItem.appendChild(switchBtn);
649
+ scenariosList.appendChild(scenarioItem);
650
+ });
651
+
652
+ interfaceItem.appendChild(scenariosList);
653
+ }
654
+
655
+ container.appendChild(interfaceItem);
656
+ });
657
+ } catch (error) {
658
+ showStatus('加载场景列表失败: ' + error.message, 'error');
659
+ }
660
+ }
661
+
662
+ // 切换接口启用/禁用状态(静默操作,不显示提示)
663
+ async function toggleInterface(interfaceName, enabled) {
664
+ try {
665
+ const response = await fetch(`/mock-admin/interfaces/${interfaceName}/toggle`, {
666
+ method: 'POST',
667
+ headers: { 'Content-Type': 'application/json' },
668
+ body: JSON.stringify({ enabled })
669
+ });
670
+
671
+ const data = await response.json();
672
+
673
+ if (data.success) {
674
+ // 静默更新,不显示提示
675
+ setTimeout(() => {
676
+ loadScenarios();
677
+ }, 100);
678
+ }
679
+ } catch (error) {
680
+ console.error('切换接口状态失败:', error);
681
+ }
682
+ }
683
+
684
+ // 切换场景(静默操作,不显示提示)
685
+ async function switchScenario(interfaceName, scenarioId) {
686
+ const scenario = scenarioId === 'default' ? null : scenarioId;
687
+
688
+ try {
689
+ const response = await fetch(`/mock-admin/scenarios/${interfaceName}`, {
690
+ method: 'POST',
691
+ headers: { 'Content-Type': 'application/json' },
692
+ body: JSON.stringify({ scenario })
693
+ });
694
+
695
+ const data = await response.json();
696
+
697
+ if (data.success) {
698
+ // 静默更新,不显示提示
699
+ setTimeout(() => {
700
+ loadScenarios();
701
+ }, 200);
702
+ }
703
+ } catch (error) {
704
+ console.error('切换场景失败:', error);
705
+ }
706
+ }
707
+
708
+ // 显示状态消息
709
+ function showStatus(message, type = 'info') {
710
+ const statusDiv = document.getElementById('status');
711
+ statusDiv.className = 'status ' + type;
712
+ statusDiv.textContent = message;
713
+ statusDiv.style.display = 'block';
714
+
715
+ if (type === 'success' || type === 'error') {
716
+ setTimeout(() => {
717
+ statusDiv.style.display = 'none';
718
+ }, 3000);
719
+ }
720
+ }
721
+
722
+ // 页面加载时获取数据
723
+ loadMockIds();
724
+ loadScenarios();
725
+
726
+ // 每5秒自动刷新一次(保留搜索状态)
727
+ setInterval(() => {
728
+ const mockIdSearchInput = document.getElementById('mockid-search');
729
+ const mockIdSearch = mockIdSearchInput ? mockIdSearchInput.value.trim() : '';
730
+ loadMockIds(mockIdSearch);
731
+ loadScenarios();
732
+ }, 5000);
733
+ </script>
734
+ </body>
735
+ </html>
736
+