mock-service-cli 3.3.6 → 3.5.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.
- package/README.md +175 -211
- package/bin/mock-service-cli +26 -2
- package/lib/api-overview.html +684 -0
- package/lib/file-explorer.html +945 -0
- package/lib/fileExplorerServer.js +278 -0
- package/lib/mockServer.js +81 -1
- package/package.json +8 -3
|
@@ -0,0 +1,684 @@
|
|
|
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>API 概览 - Mock Service</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.container {
|
|
22
|
+
max-width: 1200px;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.header {
|
|
27
|
+
background: white;
|
|
28
|
+
padding: 24px;
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
31
|
+
margin-bottom: 20px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.header h1 {
|
|
35
|
+
color: #333;
|
|
36
|
+
font-size: 28px;
|
|
37
|
+
margin-bottom: 8px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.header p {
|
|
41
|
+
color: #666;
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.stat-cards {
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
48
|
+
gap: 16px;
|
|
49
|
+
margin-bottom: 20px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.stat-card {
|
|
53
|
+
background: white;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
border-radius: 12px;
|
|
56
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.stat-card .number {
|
|
60
|
+
font-size: 36px;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
63
|
+
-webkit-background-clip: text;
|
|
64
|
+
-webkit-text-fill-color: transparent;
|
|
65
|
+
background-clip: text;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.stat-card .label {
|
|
69
|
+
color: #666;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
margin-top: 4px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.api-container {
|
|
75
|
+
background: white;
|
|
76
|
+
border-radius: 12px;
|
|
77
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.search-box {
|
|
82
|
+
padding: 20px;
|
|
83
|
+
border-bottom: 1px solid #eee;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.search-box input {
|
|
87
|
+
width: 100%;
|
|
88
|
+
padding: 12px 16px;
|
|
89
|
+
border: 2px solid #e0e0e0;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
transition: border-color 0.3s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.search-box input:focus {
|
|
96
|
+
outline: none;
|
|
97
|
+
border-color: #667eea;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.directory {
|
|
101
|
+
border-bottom: 1px solid #eee;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.directory-header {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
padding: 16px 20px;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
background: #f8f9fa;
|
|
110
|
+
transition: background 0.3s;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.directory-header:hover {
|
|
114
|
+
background: #e9ecef;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.toggle-icon {
|
|
118
|
+
width: 20px;
|
|
119
|
+
height: 20px;
|
|
120
|
+
margin-right: 12px;
|
|
121
|
+
transition: transform 0.3s;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.toggle-icon.collapsed {
|
|
125
|
+
transform: rotate(-90deg);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.directory-name {
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
color: #333;
|
|
131
|
+
flex: 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.api-count {
|
|
135
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
136
|
+
color: white;
|
|
137
|
+
padding: 4px 12px;
|
|
138
|
+
border-radius: 12px;
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.directory-content {
|
|
144
|
+
max-height: 5000px;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
transition: max-height 0.3s ease-out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.directory-content.collapsed {
|
|
150
|
+
max-height: 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.api-item {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
padding: 14px 20px 14px 52px;
|
|
157
|
+
border-bottom: 1px solid #f0f0f0;
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
transition: background 0.2s;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.api-item:hover {
|
|
163
|
+
background: #f8f9fa;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.api-item:last-child {
|
|
167
|
+
border-bottom: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.method-badge {
|
|
171
|
+
padding: 4px 10px;
|
|
172
|
+
border-radius: 6px;
|
|
173
|
+
font-size: 12px;
|
|
174
|
+
font-weight: 700;
|
|
175
|
+
text-transform: uppercase;
|
|
176
|
+
margin-right: 12px;
|
|
177
|
+
min-width: 70px;
|
|
178
|
+
text-align: center;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.method-get { background: #61affe; color: white; }
|
|
182
|
+
.method-post { background: #49cc90; color: white; }
|
|
183
|
+
.method-put { background: #fca130; color: white; }
|
|
184
|
+
.method-delete { background: #f93e3e; color: white; }
|
|
185
|
+
.method-patch { background: #50e3c2; color: white; }
|
|
186
|
+
.method-head { background: #9012fe; color: white; }
|
|
187
|
+
.method-options { background: #0d5aa7; color: white; }
|
|
188
|
+
|
|
189
|
+
.api-url {
|
|
190
|
+
flex: 1;
|
|
191
|
+
color: #333;
|
|
192
|
+
font-family: 'Courier New', monospace;
|
|
193
|
+
font-size: 14px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.test-btn {
|
|
197
|
+
padding: 6px 16px;
|
|
198
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
199
|
+
color: white;
|
|
200
|
+
border: none;
|
|
201
|
+
border-radius: 6px;
|
|
202
|
+
font-size: 13px;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.test-btn:hover {
|
|
209
|
+
transform: translateY(-1px);
|
|
210
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.modal {
|
|
214
|
+
display: none;
|
|
215
|
+
position: fixed;
|
|
216
|
+
top: 0;
|
|
217
|
+
left: 0;
|
|
218
|
+
right: 0;
|
|
219
|
+
bottom: 0;
|
|
220
|
+
background: rgba(0, 0, 0, 0.5);
|
|
221
|
+
z-index: 1000;
|
|
222
|
+
padding: 20px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.modal.active {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.modal-content {
|
|
232
|
+
background: white;
|
|
233
|
+
border-radius: 12px;
|
|
234
|
+
max-width: 800px;
|
|
235
|
+
width: 100%;
|
|
236
|
+
max-height: 90vh;
|
|
237
|
+
overflow-y: auto;
|
|
238
|
+
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.modal-header {
|
|
242
|
+
display: flex;
|
|
243
|
+
justify-content: space-between;
|
|
244
|
+
align-items: center;
|
|
245
|
+
padding: 20px;
|
|
246
|
+
border-bottom: 1px solid #eee;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.modal-title {
|
|
250
|
+
font-size: 20px;
|
|
251
|
+
font-weight: 600;
|
|
252
|
+
color: #333;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.close-btn {
|
|
256
|
+
width: 32px;
|
|
257
|
+
height: 32px;
|
|
258
|
+
border: none;
|
|
259
|
+
background: #f0f0f0;
|
|
260
|
+
border-radius: 6px;
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
font-size: 18px;
|
|
263
|
+
color: #666;
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: center;
|
|
267
|
+
transition: background 0.2s;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.close-btn:hover {
|
|
271
|
+
background: #e0e0e0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.modal-body {
|
|
275
|
+
padding: 20px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.api-info-row {
|
|
279
|
+
display: flex;
|
|
280
|
+
margin-bottom: 16px;
|
|
281
|
+
align-items: center;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.api-info-label {
|
|
285
|
+
width: 100px;
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
color: #666;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.api-info-value {
|
|
291
|
+
flex: 1;
|
|
292
|
+
color: #333;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.request-section {
|
|
296
|
+
margin-top: 24px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.section-title {
|
|
300
|
+
font-size: 16px;
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
color: #333;
|
|
303
|
+
margin-bottom: 12px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.request-body-input {
|
|
307
|
+
width: 100%;
|
|
308
|
+
min-height: 120px;
|
|
309
|
+
padding: 12px;
|
|
310
|
+
border: 2px solid #e0e0e0;
|
|
311
|
+
border-radius: 8px;
|
|
312
|
+
font-family: 'Courier New', monospace;
|
|
313
|
+
font-size: 13px;
|
|
314
|
+
resize: vertical;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.request-body-input:focus {
|
|
318
|
+
outline: none;
|
|
319
|
+
border-color: #667eea;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.send-btn {
|
|
323
|
+
margin-top: 16px;
|
|
324
|
+
padding: 12px 24px;
|
|
325
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
326
|
+
color: white;
|
|
327
|
+
border: none;
|
|
328
|
+
border-radius: 8px;
|
|
329
|
+
font-size: 14px;
|
|
330
|
+
font-weight: 600;
|
|
331
|
+
cursor: pointer;
|
|
332
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.send-btn:hover {
|
|
336
|
+
transform: translateY(-2px);
|
|
337
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.send-btn:disabled {
|
|
341
|
+
opacity: 0.6;
|
|
342
|
+
cursor: not-allowed;
|
|
343
|
+
transform: none;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.response-section {
|
|
347
|
+
margin-top: 24px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.response-status {
|
|
351
|
+
display: inline-flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
padding: 6px 12px;
|
|
354
|
+
border-radius: 6px;
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
margin-bottom: 12px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.response-status.success {
|
|
360
|
+
background: #d4edda;
|
|
361
|
+
color: #155724;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.response-status.error {
|
|
365
|
+
background: #f8d7da;
|
|
366
|
+
color: #721c24;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.response-output {
|
|
370
|
+
width: 100%;
|
|
371
|
+
min-height: 150px;
|
|
372
|
+
padding: 12px;
|
|
373
|
+
background: #f8f9fa;
|
|
374
|
+
border: 1px solid #e0e0e0;
|
|
375
|
+
border-radius: 8px;
|
|
376
|
+
font-family: 'Courier New', monospace;
|
|
377
|
+
font-size: 13px;
|
|
378
|
+
white-space: pre-wrap;
|
|
379
|
+
word-break: break-all;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.loading {
|
|
383
|
+
display: inline-block;
|
|
384
|
+
width: 16px;
|
|
385
|
+
height: 16px;
|
|
386
|
+
border: 2px solid #ffffff;
|
|
387
|
+
border-radius: 50%;
|
|
388
|
+
border-top-color: transparent;
|
|
389
|
+
animation: spin 0.8s linear infinite;
|
|
390
|
+
margin-right: 8px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@keyframes spin {
|
|
394
|
+
to { transform: rotate(360deg); }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.empty-state {
|
|
398
|
+
text-align: center;
|
|
399
|
+
padding: 60px 20px;
|
|
400
|
+
color: #999;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.empty-state svg {
|
|
404
|
+
width: 100px;
|
|
405
|
+
height: 100px;
|
|
406
|
+
margin-bottom: 20px;
|
|
407
|
+
opacity: 0.5;
|
|
408
|
+
}
|
|
409
|
+
</style>
|
|
410
|
+
</head>
|
|
411
|
+
<body>
|
|
412
|
+
<div class="container">
|
|
413
|
+
<div class="header">
|
|
414
|
+
<h1>🎯 API 概览</h1>
|
|
415
|
+
<p>探索和测试您的 Mock API 接口</p>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div class="stat-cards" id="statCards">
|
|
419
|
+
</div>
|
|
420
|
+
|
|
421
|
+
<div class="api-container">
|
|
422
|
+
<div class="search-box">
|
|
423
|
+
<input type="text" id="searchInput" placeholder="搜索 API 接口 (输入 URL 或 HTTP 方法)...">
|
|
424
|
+
</div>
|
|
425
|
+
<div id="apiList">
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div class="modal" id="apiModal">
|
|
431
|
+
<div class="modal-content">
|
|
432
|
+
<div class="modal-header">
|
|
433
|
+
<div class="modal-title" id="modalTitle">API 测试</div>
|
|
434
|
+
<button class="close-btn" onclick="closeModal()">×</button>
|
|
435
|
+
</div>
|
|
436
|
+
<div class="modal-body" id="modalBody">
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<script>
|
|
442
|
+
let apiData = null;
|
|
443
|
+
let currentApi = null;
|
|
444
|
+
|
|
445
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
446
|
+
fetchApiData();
|
|
447
|
+
document.getElementById('searchInput').addEventListener('input', filterApis);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
async function fetchApiData() {
|
|
451
|
+
try {
|
|
452
|
+
const response = await fetch('/__api-data');
|
|
453
|
+
apiData = await response.json();
|
|
454
|
+
renderStatCards();
|
|
455
|
+
renderApiList();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error('Failed to fetch API data:', error);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function renderStatCards() {
|
|
462
|
+
const methodCounts = {};
|
|
463
|
+
apiData.apis.forEach(api => {
|
|
464
|
+
methodCounts[api.method] = (methodCounts[api.method] || 0) + 1;
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const container = document.getElementById('statCards');
|
|
468
|
+
let html = `
|
|
469
|
+
<div class="stat-card">
|
|
470
|
+
<div class="number">${apiData.apis.length}</div>
|
|
471
|
+
<div class="label">总接口数</div>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="stat-card">
|
|
474
|
+
<div class="number">${Object.keys(apiData.groupedApis).length}</div>
|
|
475
|
+
<div class="label">目录数</div>
|
|
476
|
+
</div>
|
|
477
|
+
`;
|
|
478
|
+
|
|
479
|
+
Object.entries(methodCounts).forEach(([method, count]) => {
|
|
480
|
+
html += `
|
|
481
|
+
<div class="stat-card">
|
|
482
|
+
<div class="number">${count}</div>
|
|
483
|
+
<div class="label">${method.toUpperCase()}</div>
|
|
484
|
+
</div>
|
|
485
|
+
`;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
container.innerHTML = html;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function renderApiList(filterText = '') {
|
|
492
|
+
const container = document.getElementById('apiList');
|
|
493
|
+
const searchLower = filterText.toLowerCase();
|
|
494
|
+
|
|
495
|
+
let html = '';
|
|
496
|
+
let hasResults = false;
|
|
497
|
+
|
|
498
|
+
Object.entries(apiData.groupedApis).forEach(([dir, apis]) => {
|
|
499
|
+
const filteredApis = apis.filter(api =>
|
|
500
|
+
api.url.toLowerCase().includes(searchLower) ||
|
|
501
|
+
api.method.toLowerCase().includes(searchLower)
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (filteredApis.length === 0) return;
|
|
505
|
+
hasResults = true;
|
|
506
|
+
|
|
507
|
+
const dirName = dir.replace(/\\/g, '/');
|
|
508
|
+
html += `
|
|
509
|
+
<div class="directory">
|
|
510
|
+
<div class="directory-header" onclick="toggleDirectory(this)">
|
|
511
|
+
<svg class="toggle-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
512
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
513
|
+
</svg>
|
|
514
|
+
<span class="directory-name">📁 ${dirName}</span>
|
|
515
|
+
<span class="api-count">${filteredApis.length}</span>
|
|
516
|
+
</div>
|
|
517
|
+
<div class="directory-content">
|
|
518
|
+
${filteredApis.map(api => renderApiItem(api)).join('')}
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
`;
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (!hasResults) {
|
|
525
|
+
html = `
|
|
526
|
+
<div class="empty-state">
|
|
527
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
528
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
529
|
+
<path d="m21 21-4.35-4.35"></path>
|
|
530
|
+
</svg>
|
|
531
|
+
<h3>没有找到匹配的接口</h3>
|
|
532
|
+
<p>尝试使用其他关键词搜索</p>
|
|
533
|
+
</div>
|
|
534
|
+
`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
container.innerHTML = html;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function renderApiItem(api) {
|
|
541
|
+
return `
|
|
542
|
+
<div class="api-item" onclick="openApiModal('${api.method}', '${api.url.replace(/'/g, "\\'")}', '${api.file.replace(/'/g, "\\'")}')">
|
|
543
|
+
<span class="method-badge method-${api.method.toLowerCase()}">${api.method}</span>
|
|
544
|
+
<span class="api-url">${api.url}</span>
|
|
545
|
+
<button class="test-btn" onclick="event.stopPropagation(); testApi('${api.method}', '${api.url.replace(/'/g, "\\'")}')">测试</button>
|
|
546
|
+
</div>
|
|
547
|
+
`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function toggleDirectory(header) {
|
|
551
|
+
const icon = header.querySelector('.toggle-icon');
|
|
552
|
+
const content = header.nextElementSibling;
|
|
553
|
+
icon.classList.toggle('collapsed');
|
|
554
|
+
content.classList.toggle('collapsed');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function filterApis(e) {
|
|
558
|
+
renderApiList(e.target.value);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function openApiModal(method, url, file) {
|
|
562
|
+
currentApi = { method, url, file };
|
|
563
|
+
const modal = document.getElementById('apiModal');
|
|
564
|
+
const title = document.getElementById('modalTitle');
|
|
565
|
+
const body = document.getElementById('modalBody');
|
|
566
|
+
|
|
567
|
+
title.innerHTML = `<span class="method-badge method-${method.toLowerCase()}">${method}</span> ${url}`;
|
|
568
|
+
|
|
569
|
+
body.innerHTML = `
|
|
570
|
+
<div class="api-info-row">
|
|
571
|
+
<span class="api-info-label">文件路径:</span>
|
|
572
|
+
<span class="api-info-value">${file}</span>
|
|
573
|
+
</div>
|
|
574
|
+
${(method === 'POST' || method === 'PUT' || method === 'PATCH') ? `
|
|
575
|
+
<div class="request-section">
|
|
576
|
+
<div class="section-title">请求体 (JSON)</div>
|
|
577
|
+
<textarea class="request-body-input" id="requestBody" placeholder='{"key": "value"}'></textarea>
|
|
578
|
+
</div>
|
|
579
|
+
` : ''}
|
|
580
|
+
<button class="send-btn" id="sendBtn" onclick="sendRequest()">发送请求</button>
|
|
581
|
+
<div class="response-section" id="responseSection" style="display: none;">
|
|
582
|
+
<div class="section-title">响应</div>
|
|
583
|
+
<div class="response-status" id="responseStatus"></div>
|
|
584
|
+
<pre class="response-output" id="responseOutput"></pre>
|
|
585
|
+
</div>
|
|
586
|
+
`;
|
|
587
|
+
|
|
588
|
+
modal.classList.add('active');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function closeModal() {
|
|
592
|
+
document.getElementById('apiModal').classList.remove('active');
|
|
593
|
+
currentApi = null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function testApi(method, url) {
|
|
597
|
+
currentApi = { method, url };
|
|
598
|
+
await sendRequestDirect(method, url);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function sendRequest() {
|
|
602
|
+
if (!currentApi) return;
|
|
603
|
+
await sendRequestDirect(currentApi.method, currentApi.url);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function sendRequestDirect(method, url) {
|
|
607
|
+
const sendBtn = document.getElementById('sendBtn');
|
|
608
|
+
const responseSection = document.getElementById('responseSection');
|
|
609
|
+
const responseStatus = document.getElementById('responseStatus');
|
|
610
|
+
const responseOutput = document.getElementById('responseOutput');
|
|
611
|
+
|
|
612
|
+
let requestBody = null;
|
|
613
|
+
const requestBodyEl = document.getElementById('requestBody');
|
|
614
|
+
if (requestBodyEl) {
|
|
615
|
+
const bodyText = requestBodyEl.value.trim();
|
|
616
|
+
if (bodyText) {
|
|
617
|
+
try {
|
|
618
|
+
requestBody = JSON.parse(bodyText);
|
|
619
|
+
} catch (e) {
|
|
620
|
+
alert('请求体 JSON 格式错误');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (sendBtn) {
|
|
627
|
+
sendBtn.disabled = true;
|
|
628
|
+
sendBtn.innerHTML = '<span class="loading"></span>发送中...';
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
const options = {
|
|
633
|
+
method: method,
|
|
634
|
+
headers: {
|
|
635
|
+
'Content-Type': 'application/json'
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
if (requestBody) {
|
|
640
|
+
options.body = JSON.stringify(requestBody);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const response = await fetch(url, options);
|
|
644
|
+
const data = await response.json();
|
|
645
|
+
|
|
646
|
+
if (responseSection) responseSection.style.display = 'block';
|
|
647
|
+
if (responseStatus) {
|
|
648
|
+
responseStatus.className = 'response-status success';
|
|
649
|
+
responseStatus.textContent = `✓ ${response.status} ${response.statusText}`;
|
|
650
|
+
}
|
|
651
|
+
if (responseOutput) {
|
|
652
|
+
responseOutput.textContent = JSON.stringify(data, null, 2);
|
|
653
|
+
}
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (responseSection) responseSection.style.display = 'block';
|
|
656
|
+
if (responseStatus) {
|
|
657
|
+
responseStatus.className = 'response-status error';
|
|
658
|
+
responseStatus.textContent = '✗ 请求失败';
|
|
659
|
+
}
|
|
660
|
+
if (responseOutput) {
|
|
661
|
+
responseOutput.textContent = error.message;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (sendBtn) {
|
|
666
|
+
sendBtn.disabled = false;
|
|
667
|
+
sendBtn.innerHTML = '发送请求';
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
document.getElementById('apiModal').addEventListener('click', (e) => {
|
|
672
|
+
if (e.target === e.currentTarget) {
|
|
673
|
+
closeModal();
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
document.addEventListener('keydown', (e) => {
|
|
678
|
+
if (e.key === 'Escape') {
|
|
679
|
+
closeModal();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
</script>
|
|
683
|
+
</body>
|
|
684
|
+
</html>
|