chatyou commited on
Commit
ddc575f
·
verified ·
1 Parent(s): 29a42c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +476 -0
app.py CHANGED
@@ -287,6 +287,482 @@ HTML_CONTENT = """<!DOCTYPE html>
287
  </style>
288
  </head>
289
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  </body>
291
  </html>"""
292
 
 
287
  </style>
288
  </head>
289
  <body>
290
+ <div class="container">
291
+ <h1 style="font-size: 2.5rem; margin-bottom: 2rem; color: #0f172a;">📦 HF Bucket 文件管理器 (目录导航)</h1>
292
+ <div class="grid">
293
+ <!-- 左侧:文件上传与浏览 -->
294
+
295
+
296
+ <!-- 右侧:API 测试面板 + 使用说明 -->
297
+ <div class="card">
298
+ <h2>🧪 API 测试</h2>
299
+ <div class="api-test">
300
+ <div class="api-section">
301
+ <h3>📤 上传文件 (POST /upload) 支持目录</h3>
302
+ <div class="api-row">
303
+ <input type="file" id="apiFileInput" style="flex:1; padding:0.5rem;">
304
+ <span style="color:#64748b;">(留空则使用左侧文件)</span>
305
+ </div>
306
+ <div class="api-row">
307
+ <input type="text" id="apiUploadDir" class="api-input" placeholder="目标目录 (可选,如 images/)" value="">
308
+ <button class="button secondary small" id="apiUploadBtn">上传测试</button>
309
+ </div>
310
+ </div>
311
+
312
+ <div class="api-section">
313
+ <h3>📋 列出文件 (GET /list?dir=...)</h3>
314
+ <div class="api-row">
315
+ <input type="text" id="apiListDir" class="api-input" placeholder="目录 (留空为根目录)" value="">
316
+ <button class="button secondary small" id="apiListBtn">获取列表</button>
317
+ </div>
318
+ </div>
319
+
320
+ <div class="api-section">
321
+ <h3>📥 获取文件信息 (HEAD /file/&lt;filename&gt;)</h3>
322
+ <div class="api-row">
323
+ <input type="text" class="api-input" id="apiFilename" placeholder="输入文件名(可包含目录)">
324
+ <button class="button secondary small" id="apiGetBtn">检查</button>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="api-section">
329
+ <h3>🗑️ 删除文件 (DELETE /delete/&lt;filename&gt;)</h3>
330
+ <div class="api-row">
331
+ <input type="text" class="api-input" id="apiDeleteFilename" placeholder="要删除的文件名(可包含目录)">
332
+ <button class="button danger small" id="apiDeleteBtn">删除</button>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="api-result" id="apiResult">点击按钮查看结果</div>
337
+
338
+ <div class="api-docs">
339
+ <h3>📚 API 使用说明 (JavaScript 示例)</h3>
340
+ <p style="color: #334155; margin-bottom: 1rem;">所有请求均需在 Hugging Face Space 内调用(同源),无需额外认证。</p>
341
+
342
+ <!-- 上传文件 -->
343
+ <div style="background: white; border-radius: 16px; padding: 1rem; margin-bottom: 1.5rem; border-left: 4px solid #3b82f6;">
344
+ <h4 style="margin: 0 0 0.5rem 0; color: #0f172a;">📤 上传文件</h4>
345
+ <p><code>POST /upload</code></p>
346
+ <p><strong>请求格式:</strong> <code>multipart/form-data</code></p>
347
+ <p><strong>参数:</strong></p>
348
+ <ul style="margin-left: 1.5rem; margin-bottom: 0.5rem;">
349
+ <li><code>file</code> (必填) - 要上传的文件</li>
350
+ <li><code>dir</code> (可选) - 目标目录路径,例如 <code>"images/"</code> 或 <code>"logs/2026/"</code>。留空则上传到根目录。</li>
351
+ </ul>
352
+ <p><strong>成功响应:</strong> <code>{"success": true, "filename": "远程完整路径"}</code></p>
353
+ <p><strong>失败响应:</strong> <code>{"error": "错误信息"}</code> (HTTP 4xx/5xx)</p>
354
+ <!-- 修改后的代码框样式 -->
355
+ <pre style="background: #000000; color: white; padding: 1rem; border-radius: 12px; overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.9rem; line-height: 1.5;"><code>// 使用 FormData 上传
356
+ const fileInput = document.getElementById('fileInput'); // 文件选择 input
357
+ const file = fileInput.files[0];
358
+ const dir = "images/"; // 可选目录
359
+
360
+ const formData = new FormData();
361
+ formData.append('file', file);
362
+ formData.append('dir', dir);
363
+
364
+ fetch('/upload', {
365
+ method: 'POST',
366
+ body: formData
367
+ })
368
+ .then(res => res.json())
369
+ .then(data => {
370
+ if (data.error) throw new Error(data.error);
371
+ console.log('上传成功:', data.filename);
372
+ })
373
+ .catch(err => console.error('上传失败:', err.message));</code></pre>
374
+ </div>
375
+
376
+ <!-- 列出文件 -->
377
+ <div style="background: white; border-radius: 16px; padding: 1rem; margin-bottom: 1.5rem; border-left: 4px solid #3b82f6;">
378
+ <h4 style="margin: 0 0 0.5rem 0; color: #0f172a;">📋 列出文件</h4>
379
+ <p><code>GET /list?dir=...</code></p>
380
+ <p><strong>查询参数:</strong></p>
381
+ <ul style="margin-left: 1.5rem; margin-bottom: 0.5rem;">
382
+ <li><code>dir</code> (可选) - 目录路径,��如 <code>"images/"</code>。留空则列出根目录下的第一层内容。</li>
383
+ </ul>
384
+ <p><strong>成功响应:</strong> 字符串数组,每个元素是文件或目录的完整路径(目录路径以 <code>/</code> 结尾)。例如:<code>["file.txt", "images/", "logs/"]</code></p>
385
+ <p><strong>失败响应:</strong> <code>{"error": "错误信息"}</code> (HTTP 5xx)</p>
386
+ <pre style="background: #000000; color: white; padding: 1rem; border-radius: 12px; overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.9rem; line-height: 1.5;"><code>// 列出根目录内容
387
+ fetch('/list')
388
+ .then(res => res.json())
389
+ .then(files => {
390
+ files.forEach(path => {
391
+ if (path.endsWith('/')) {
392
+ console.log('目录:', path);
393
+ } else {
394
+ console.log('文件:', path);
395
+ }
396
+ });
397
+ });
398
+
399
+ // 列出指定目录(如 images/)内容
400
+ const dir = "images/";
401
+ fetch(`/list?dir=${encodeURIComponent(dir)}`)
402
+ .then(res => res.json())
403
+ .then(files => console.log(files));</code></pre>
404
+ </div>
405
+
406
+ <!-- 下载文件 -->
407
+ <div style="background: white; border-radius: 16px; padding: 1rem; margin-bottom: 1.5rem; border-left: 4px solid #3b82f6;">
408
+ <h4 style="margin: 0 0 0.5rem 0; color: #0f172a;">📥 下载文件</h4>
409
+ <p><code>GET /file/&lt;filename&gt;</code></p>
410
+ <p><strong>路径参数:</strong></p>
411
+ <ul style="margin-left: 1.5rem; margin-bottom: 0.5rem;">
412
+ <li><code>filename</code> - 文件的完整路径(可包含目录),例如 <code>"images/avatar.png"</code>。</li>
413
+ </ul>
414
+ <p><strong>成功响应:</strong> 文件内容(作为附件下载)。</p>
415
+ <p><strong>失败响应:</strong> <code>{"error": "错误信息"}</code> (HTTP 4xx/5xx)</p>
416
+ <pre style="background: #000000; color: white; padding: 1rem; border-radius: 12px; overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.9rem; line-height: 1.5;"><code>// 触发浏览器下载
417
+ const filename = "images/avatar.png";
418
+ window.location.href = `/file/${encodeURIComponent(filename)}`;
419
+
420
+ // 或者用 fetch 获取文件 Blob
421
+ fetch(`/file/${encodeURIComponent(filename)}`)
422
+ .then(res => {
423
+ if (!res.ok) throw new Error('文件不存在');
424
+ return res.blob();
425
+ })
426
+ .then(blob => {
427
+ const url = URL.createObjectURL(blob);
428
+ const a = document.createElement('a');
429
+ a.href = url;
430
+ a.download = filename.split('/').pop(); // 提取文件名
431
+ a.click();
432
+ })
433
+ .catch(err => console.error('下载失败:', err.message));</code></pre>
434
+ </div>
435
+
436
+ <!-- 检查文件存在性 -->
437
+ <div style="background: white; border-radius: 16px; padding: 1rem; margin-bottom: 1.5rem; border-left: 4px solid #3b82f6;">
438
+ <h4 style="margin: 0 0 0.5rem 0; color: #0f172a;">🔍 检查文件是否存在</h4>
439
+ <p><code>HEAD /file/&lt;filename&gt;</code></p>
440
+ <p><strong>路径参数:</strong> 同下载接口的 <code>filename</code>。</p>
441
+ <p><strong>成功响应:</strong> HTTP 200,无响应体。</p>
442
+ <p><strong>失败响应:</strong> HTTP 404 并返回 JSON 错误信息。</p>
443
+ <pre style="background: #000000; color: white; padding: 1rem; border-radius: 12px; overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.9rem; line-height: 1.5;"><code>const filename = "config.json";
444
+
445
+ fetch(`/file/${encodeURIComponent(filename)}`, { method: 'HEAD' })
446
+ .then(res => {
447
+ if (res.ok) {
448
+ console.log('文件存在');
449
+ } else {
450
+ return res.json().then(err => { throw new Error(err.error); });
451
+ }
452
+ })
453
+ .catch(err => console.error('检查失败:', err.message));</code></pre>
454
+ </div>
455
+
456
+ <!-- 删除文件 -->
457
+ <div style="background: white; border-radius: 16px; padding: 1rem; margin-bottom: 1.5rem; border-left: 4px solid #ef4444;">
458
+ <h4 style="margin: 0 0 0.5rem 0; color: #0f172a;">🗑️ 删除文件</h4>
459
+ <p><code>DELETE /delete/&lt;filename&gt;</code></p>
460
+ <p><strong>路径参数:</strong> 同下载接口的 <code>filename</code>。</p>
461
+ <p><strong>成功响应:</strong> <code>{"success": true}</code></p>
462
+ <p><strong>失败响应:</strong> <code>{"error": "错误信息"}</code> (HTTP 4xx/5xx)</p>
463
+ <pre style="background: #1e1e1e; color: white; padding: 1rem; border-radius: 12px; overflow-x: auto; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.9rem; line-height: 1.5;"><code>const filename = "temp.log";
464
+
465
+ fetch(`/delete/${encodeURIComponent(filename)}`, { method: 'DELETE' })
466
+ .then(res => res.json())
467
+ .then(data => {
468
+ if (data.success) {
469
+ console.log('删除成功');
470
+ } else {
471
+ throw new Error(data.error);
472
+ }
473
+ })
474
+ .catch(err => console.error('删除失败:', err.message));</code></pre>
475
+ </div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ <footer>Powered by Hugging Face Buckets · 支持多级目录导航</footer>
481
+ </div>
482
+
483
+ <script>
484
+ (function() {
485
+ // DOM 元素
486
+ const fileInput = document.getElementById('fileInput');
487
+ const uploadBtn = document.getElementById('uploadBtn');
488
+ const fileLabel = document.getElementById('fileLabel');
489
+ const uploadDir = document.getElementById('uploadDir');
490
+ const currentDir = document.getElementById('currentDir');
491
+ const listBtn = document.getElementById('listBtn');
492
+ const goUpBtn = document.getElementById('goUpBtn');
493
+ const logDiv = document.getElementById('log');
494
+ const fileListDiv = document.getElementById('fileList');
495
+ const apiResult = document.getElementById('apiResult');
496
+ const apiListBtn = document.getElementById('apiListBtn');
497
+ const apiListDir = document.getElementById('apiListDir');
498
+ const apiGetBtn = document.getElementById('apiGetBtn');
499
+ const apiDeleteBtn = document.getElementById('apiDeleteBtn');
500
+ const apiFilename = document.getElementById('apiFilename');
501
+ const apiDeleteFilename = document.getElementById('apiDeleteFilename');
502
+ const apiFileInput = document.getElementById('apiFileInput');
503
+ const apiUploadDir = document.getElementById('apiUploadDir');
504
+ const apiUploadBtn = document.getElementById('apiUploadBtn');
505
+
506
+ // 辅助函数:添加日志
507
+ function addLog(msg, isErr = false) {
508
+ const p = document.createElement('p');
509
+ const ts = new Date().toLocaleTimeString();
510
+ p.innerHTML = '<span class="timestamp">[' + ts + ']</span> ' + (isErr ? '❌ ' : '') + msg;
511
+ if (isErr) p.style.color = '#f87171';
512
+ logDiv.appendChild(p);
513
+ logDiv.scrollTop = logDiv.scrollHeight;
514
+ }
515
+ function clearLog() { logDiv.innerHTML = ''; }
516
+
517
+ // 显示 API 结果
518
+ function showApiResult(data, isError = false) {
519
+ if (typeof data === 'object') {
520
+ apiResult.textContent = JSON.stringify(data, null, 2);
521
+ } else {
522
+ apiResult.textContent = data;
523
+ }
524
+ if (isError) apiResult.style.color = '#f87171';
525
+ else apiResult.style.color = '#b9e6f0';
526
+ }
527
+
528
+ // 构建远程路径:目录 + 文件名
529
+ function buildRemotePath(dir, filename) {
530
+ dir = dir.trim();
531
+ if (!dir) return filename;
532
+ if (!dir.endsWith('/')) dir += '/';
533
+ if (dir.startsWith('/')) dir = dir.substring(1);
534
+ return dir + filename;
535
+ }
536
+
537
+ // 获取用于上传的文件
538
+ function getUploadFile() {
539
+ if (apiFileInput.files.length > 0) {
540
+ return apiFileInput.files[0];
541
+ } else if (fileInput.files.length > 0) {
542
+ return fileInput.files[0];
543
+ }
544
+ return null;
545
+ }
546
+
547
+ // 加载文件列表
548
+ async function loadList(dir) {
549
+ dir = dir || '';
550
+ currentDir.value = dir; // 同步输入框
551
+ try {
552
+ fileListDiv.innerHTML = '<div class="empty-message">加载中...</div>';
553
+ let url = '/list';
554
+ if (dir) url += '?dir=' + encodeURIComponent(dir);
555
+ const res = await fetch(url);
556
+ if (!res.ok) {
557
+ const err = await res.text();
558
+ throw new Error(`HTTP ${res.status}: ${err}`);
559
+ }
560
+ const items = await res.json(); // 数组,每个元素是路径字符串
561
+ if (items.length === 0) {
562
+ fileListDiv.innerHTML = '<div class="empty-message">📭 空目录</div>';
563
+ } else {
564
+ // 根据路径是否以 / 结尾判断是目录还是文件
565
+ const html = items.map(path => {
566
+ const isDir = path.endsWith('/');
567
+ if (isDir) {
568
+ const dirName = path.slice(0, -1); // 去掉末尾斜杠,用于显示
569
+ return `
570
+ <div class="file-item">
571
+ <span class="directory-link" onclick="navigateTo('${path}')">
572
+ 📁 ${dirName}
573
+ </span>
574
+ <!-- 目录暂不提供删除按钮,可后续扩展 -->
575
+ </div>
576
+ `;
577
+ } else {
578
+ // 文件:显示下载链接和删除按钮
579
+ return `
580
+ <div class="file-item">
581
+ <a href="/file/${encodeURIComponent(path)}" target="_blank" class="file-link">
582
+ 📄 ${path}
583
+ </a>
584
+ <button class="button danger small" onclick="deleteFile('${path.replace(/'/g, "\\\\'")}')">删除</button>
585
+ </div>
586
+ `;
587
+ }
588
+ }).join('');
589
+ fileListDiv.innerHTML = html;
590
+ }
591
+ } catch (err) {
592
+ fileListDiv.innerHTML = '<div class="empty-message">❌ 加载失败</div>';
593
+ addLog('列表加载错误: ' + err.message, true);
594
+ }
595
+ }
596
+
597
+ // 导航到目录
598
+ window.navigateTo = function(dirPath) {
599
+ // dirPath 以 / 结尾,例如 "subdir/"
600
+ const dir = dirPath; // 直接使用,loadList 会处理
601
+ loadList(dir);
602
+ };
603
+
604
+ // 删除文件
605
+ window.deleteFile = async function(filename) {
606
+ if (!confirm(`确定删除 ${filename} 吗?`)) return;
607
+ try {
608
+ addLog('正在删除: ' + filename);
609
+ const res = await fetch('/delete/' + encodeURIComponent(filename), { method: 'DELETE' });
610
+ const text = await res.text();
611
+ if (!res.ok) throw new Error(`删除失败 (${res.status}): ${text}`);
612
+ addLog('✅ 删除成功: ' + filename);
613
+ // 刷新当前目录
614
+ loadList(currentDir.value);
615
+ } catch (err) {
616
+ addLog(err.message, true);
617
+ }
618
+ };
619
+
620
+ // 返回上级目录
621
+ function goUp() {
622
+ let dir = currentDir.value.trim();
623
+ if (!dir) return; // 已在根目录
624
+ // 移除末尾的 /
625
+ dir = dir.endsWith('/') ? dir.slice(0, -1) : dir;
626
+ const parts = dir.split('/');
627
+ parts.pop(); // 去掉最后一级
628
+ const parent = parts.length ? parts.join('/') + '/' : '';
629
+ loadList(parent);
630
+ }
631
+
632
+ // 左侧文件选择显示
633
+ fileInput.addEventListener('change', function() {
634
+ if (fileInput.files.length > 0) {
635
+ fileLabel.textContent = '📄 ' + fileInput.files[0].name;
636
+ } else {
637
+ fileLabel.textContent = '📎 选择文件';
638
+ }
639
+ });
640
+
641
+ // 左侧上传按钮
642
+ uploadBtn.addEventListener('click', async () => {
643
+ const file = fileInput.files[0];
644
+ if (!file) { alert('请选择文件'); return; }
645
+
646
+ const dir = uploadDir.value;
647
+ const remotePath = buildRemotePath(dir, file.name);
648
+
649
+ clearLog();
650
+ addLog('开始上传到: ' + remotePath);
651
+ uploadBtn.disabled = true;
652
+
653
+ const formData = new FormData();
654
+ formData.append('file', file);
655
+ formData.append('dir', dir);
656
+
657
+ try {
658
+ const res = await fetch('/upload', { method: 'POST', body: formData });
659
+ const data = await res.json();
660
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
661
+ addLog('✅ 上传成功!路径: ' + data.filename);
662
+ fileInput.value = '';
663
+ fileLabel.textContent = '📎 选择文件';
664
+ uploadDir.value = '';
665
+ // 上传后刷新当前目录
666
+ loadList(currentDir.value);
667
+ } catch (err) {
668
+ addLog('❌ ' + err.message, true);
669
+ } finally {
670
+ uploadBtn.disabled = false;
671
+ }
672
+ });
673
+
674
+ // 列出文件按钮
675
+ listBtn.addEventListener('click', () => {
676
+ loadList(currentDir.value.trim());
677
+ });
678
+
679
+ // 返回上级按钮
680
+ goUpBtn.addEventListener('click', goUp);
681
+
682
+ // API 测试:上传文件
683
+ apiUploadBtn.addEventListener('click', async () => {
684
+ const file = getUploadFile();
685
+ if (!file) {
686
+ alert('请选择文件(在左侧或右侧上传区域选择)');
687
+ return;
688
+ }
689
+
690
+ const dir = apiUploadDir.value;
691
+ const remotePath = buildRemotePath(dir, file.name);
692
+
693
+ const formData = new FormData();
694
+ formData.append('file', file);
695
+ formData.append('dir', dir);
696
+
697
+ try {
698
+ showApiResult('正在上传到 ' + remotePath + ' ...');
699
+ const res = await fetch('/upload', { method: 'POST', body: formData });
700
+ const data = await res.json();
701
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
702
+ showApiResult(data);
703
+ loadList(currentDir.value);
704
+ } catch (err) {
705
+ showApiResult(err.message, true);
706
+ }
707
+ });
708
+
709
+ // API 测试:列出文件
710
+ apiListBtn.addEventListener('click', async () => {
711
+ const dir = apiListDir.value.trim();
712
+ try {
713
+ showApiResult('正在请求 /list?dir=' + dir + ' ...');
714
+ let url = '/list';
715
+ if (dir) url += '?dir=' + encodeURIComponent(dir);
716
+ const res = await fetch(url);
717
+ const data = await res.json();
718
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
719
+ showApiResult(data);
720
+ // 同时更新左侧浏览目录为相同目录并刷新
721
+ loadList(dir);
722
+ } catch (err) {
723
+ showApiResult(err.message, true);
724
+ }
725
+ });
726
+
727
+ // API 测试:获取文件信息
728
+ apiGetBtn.addEventListener('click', async () => {
729
+ const filename = apiFilename.value.trim();
730
+ if (!filename) { alert('请输入文件名'); return; }
731
+ try {
732
+ showApiResult('正在检查 /file/' + filename + ' ...');
733
+ const res = await fetch('/file/' + encodeURIComponent(filename), { method: 'HEAD' });
734
+ if (res.ok) {
735
+ showApiResult({ status: 'OK', message: '文件存在,可下载' });
736
+ } else {
737
+ const text = await res.text();
738
+ throw new Error(`HTTP ${res.status}: ${text}`);
739
+ }
740
+ } catch (err) {
741
+ showApiResult(err.message, true);
742
+ }
743
+ });
744
+
745
+ // API 测试:删除文件
746
+ apiDeleteBtn.addEventListener('click', async () => {
747
+ const filename = apiDeleteFilename.value.trim();
748
+ if (!filename) { alert('请输入文件名'); return; }
749
+ if (!confirm(`确定通过 API 删除 ${filename} 吗?`)) return;
750
+ try {
751
+ showApiResult('正在删除 ' + filename + ' ...');
752
+ const res = await fetch('/delete/' + encodeURIComponent(filename), { method: 'DELETE' });
753
+ const text = await res.text();
754
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${text}`);
755
+ showApiResult({ success: true, message: '文件已删除' });
756
+ loadList(currentDir.value);
757
+ } catch (err) {
758
+ showApiResult(err.message, true);
759
+ }
760
+ });
761
+
762
+ // 初始化:加载根目录
763
+ loadList('');
764
+ })();
765
+ </script>
766
  </body>
767
  </html>"""
768