lexlepty commited on
Commit
b0f1799
·
verified ·
1 Parent(s): 841d4e1

Upload 2 files

Browse files
Files changed (2) hide show
  1. templates/index.html +563 -31
  2. templates/login.html +137 -126
templates/index.html CHANGED
@@ -352,6 +352,9 @@
352
  display: flex;
353
  align-items: center;
354
  justify-content: space-around;
 
 
 
355
  }
356
 
357
  .logo {
@@ -361,23 +364,37 @@
361
  .nav-item {
362
  flex: 1;
363
  margin: 0 5px;
364
- padding: 8px;
365
- flex-direction: column;
 
366
  font-size: 12px;
 
 
367
  }
368
 
369
  .nav-item i {
370
- margin: 0 0 5px 0;
 
 
 
 
 
 
 
 
371
  }
372
 
373
  .main-content {
374
  margin-left: 0;
375
- margin-bottom: 60px;
376
  padding-top: 90px;
 
 
377
  }
378
 
379
  .header {
380
  left: 0;
 
381
  }
382
 
383
  .search-container {
@@ -387,13 +404,73 @@
387
  .upload-btn {
388
  right: 20px;
389
  bottom: 80px;
 
390
  }
391
 
392
  .file-grid {
393
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
394
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  }
396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  /* 拖拽上传区域样式 */
398
  .drag-overlay {
399
  position: fixed;
@@ -413,25 +490,63 @@
413
 
414
  /* 面包屑导航 */
415
  .breadcrumb {
416
- margin-bottom: 20px;
417
- display: flex;
 
418
  align-items: center;
 
419
  gap: 8px;
420
  font-size: 14px;
 
 
 
 
 
421
  }
422
 
423
  .breadcrumb-item {
424
  cursor: pointer;
425
  color: var(--text);
426
- transition: color 0.3s ease;
 
 
 
 
 
 
 
427
  }
428
 
429
  .breadcrumb-item:hover {
430
  color: var(--primary-glow);
 
431
  }
432
 
433
  .breadcrumb-separator {
434
  color: var(--border-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
  /* 加载指示器样式 */
437
  .loading-indicator {
@@ -716,6 +831,106 @@
716
  justify-content: center;
717
  transition: all 0.3s ease;
718
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  </style>
720
  </head>
721
  <body>
@@ -760,6 +975,14 @@
760
 
761
  <!-- 主内容区 -->
762
  <main class="main-content">
 
 
 
 
 
 
 
 
763
  <!-- 视图切换按钮 -->
764
  <div class="view-toggle">
765
  <button class="view-btn active" data-view="grid">
@@ -770,10 +993,7 @@
770
  </button>
771
  </div>
772
 
773
- <!-- 面包屑导航 -->
774
- <div class="breadcrumb">
775
- <span class="breadcrumb-item" data-path="/">根目录</span>
776
- </div>
777
 
778
  <!-- 文件容器 -->
779
  <div class="file-container">
@@ -789,9 +1009,6 @@
789
 
790
  <!-- 上传进度条 -->
791
  <div class="upload-progress">
792
- <div class="progress-bar">
793
- <div class="progress-fill"></div>
794
- </div>
795
  </div>
796
  </div>
797
 
@@ -827,6 +1044,8 @@
827
  // 文件管理类
828
  class FileManager {
829
  constructor() {
 
 
830
  this.currentPath = '/';
831
  this.currentView = 'grid';
832
  this.currentFileType = 'all';
@@ -860,6 +1079,16 @@
860
 
861
  // 拖拽上传
862
  this.initDragAndDrop();
 
 
 
 
 
 
 
 
 
 
863
  }
864
 
865
  // 加载文件列表
@@ -902,6 +1131,14 @@
902
  const element = document.createElement('div');
903
  element.className = `file-item ${this.currentView}`;
904
 
 
 
 
 
 
 
 
 
905
  const icon = this.getFileIcon(file.type, file.file_type);
906
  const size = this.formatFileSize(file.size);
907
 
@@ -924,13 +1161,41 @@
924
  `;
925
  }
926
 
927
- // 添加事件监听
928
- element.addEventListener('click', () => this.handleFileClick(file));
929
- element.addEventListener('contextmenu', (e) => this.showFileMenu(e, file));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
930
 
931
  return element;
932
  }
933
-
934
  // 处理文件点击
935
  handleFileClick(file) {
936
  if (file.type === 'directory') {
@@ -1174,24 +1439,130 @@
1174
  // 文件下载
1175
  async downloadFile(file) {
1176
  try {
1177
- const response = await fetch(`/api/files/download/${file.path}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
  if (!response.ok) throw new Error('Download failed');
 
 
 
 
1179
 
1180
- const blob = await response.blob();
1181
- const url = URL.createObjectURL(blob);
1182
- const a = document.createElement('a');
1183
- a.href = url;
1184
- a.download = file.path.split('/').pop();
1185
- document.body.appendChild(a);
1186
- a.click();
1187
- document.body.removeChild(a);
1188
- URL.revokeObjectURL(url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1189
  } catch (error) {
1190
- console.error('Error downloading file:', error);
1191
- this.showError('下载文件失败');
 
 
 
 
 
 
 
 
1192
  }
1193
  }
1194
-
1195
  // 文件上传处理
1196
  async handleFileUpload(files) {
1197
  const uploadProgress = document.querySelector('.upload-progress');
@@ -1527,6 +1898,167 @@
1527
  }, 2000);
1528
  }, 100);
1529
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1530
  }
1531
  // 初始化文件管理器
1532
  new FileManager();
 
352
  display: flex;
353
  align-items: center;
354
  justify-content: space-around;
355
+ z-index: 1000;
356
+ background: var(--sidebar-bg);
357
+ box-shadow: 0 -2px 10px var(--shadow-color);
358
  }
359
 
360
  .logo {
 
364
  .nav-item {
365
  flex: 1;
366
  margin: 0 5px;
367
+ padding: 8px 15px;
368
+ flex-direction: row;
369
+ align-items: center;
370
  font-size: 12px;
371
+ height: 40px;
372
+ border-radius: 8px;
373
  }
374
 
375
  .nav-item i {
376
+ margin: 0 8px 0 0;
377
+ font-size: 16px;
378
+ }
379
+
380
+ .nav-item-text {
381
+ display: block;
382
+ white-space: nowrap;
383
+ overflow: hidden;
384
+ text-overflow: ellipsis;
385
  }
386
 
387
  .main-content {
388
  margin-left: 0;
389
+ margin-bottom: 70px;
390
  padding-top: 90px;
391
+ padding-bottom: 70px;
392
+ min-height: calc(100vh - 70px);
393
  }
394
 
395
  .header {
396
  left: 0;
397
+ z-index: 999;
398
  }
399
 
400
  .search-container {
 
404
  .upload-btn {
405
  right: 20px;
406
  bottom: 80px;
407
+ z-index: 1001;
408
  }
409
 
410
  .file-grid {
411
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
412
  }
413
+
414
+ .view-toggle {
415
+ right: 20px;
416
+ gap: 8px;
417
+ }
418
+
419
+ .upload-progress {
420
+ bottom: 70px;
421
+ right: 20px;
422
+ max-width: calc(100vw - 40px);
423
+ z-index: 1001;
424
+ }
425
+ }
426
+ .action-buttons {
427
+ position: absolute;
428
+ right: 30px;
429
+ top: calc(var(--header-height) + 20px);
430
+ display: flex;
431
+ gap: 16px;
432
+ align-items: center;
433
+ z-index: 10;
434
+ }
435
+
436
+ .action-btn {
437
+ padding: 8px 15px;
438
+ border: 1px solid var(--border-color);
439
+ border-radius: 8px;
440
+ background: var(--card-bg);
441
+ cursor: pointer;
442
+ transition: all 0.3s ease;
443
+ display: flex;
444
+ align-items: center;
445
+ gap: 8px;
446
+ color: var(--text);
447
+ }
448
+
449
+ .action-btn:hover {
450
+ border-color: var(--primary-glow);
451
+ box-shadow: 0 2px 8px var(--shadow-color);
452
  }
453
 
454
+ .action-btn i {
455
+ font-size: 16px;
456
+ color: var(--primary-glow);
457
+ }
458
+
459
+ @media (max-width: 768px) {
460
+ .action-buttons {
461
+ right: 20px;
462
+ gap: 8px;
463
+ }
464
+
465
+ .action-btn {
466
+ padding: 6px 12px;
467
+ font-size: 12px;
468
+ }
469
+
470
+ .action-btn i {
471
+ font-size: 14px;
472
+ }
473
+ }
474
  /* 拖拽上传区域样式 */
475
  .drag-overlay {
476
  position: fixed;
 
490
 
491
  /* 面包屑导航 */
492
  .breadcrumb {
493
+ margin: 20px 0;
494
+ padding: 12px 16px;
495
+ display: inline-flex;
496
  align-items: center;
497
+ flex-wrap: wrap;
498
  gap: 8px;
499
  font-size: 14px;
500
+ background: var(--card-bg);
501
+ border-radius: 8px;
502
+ box-shadow: 0 2px 8px var(--shadow-color);
503
+ width: auto;
504
+ min-width: min-content;
505
  }
506
 
507
  .breadcrumb-item {
508
  cursor: pointer;
509
  color: var(--text);
510
+ transition: all 0.3s ease;
511
+ padding: 4px 8px;
512
+ border-radius: 4px;
513
+ display: inline-flex; /* Changed from flex to inline-flex */
514
+ align-items: center;
515
+ white-space: nowrap;
516
+ overflow: hidden;
517
+ text-overflow: ellipsis;
518
  }
519
 
520
  .breadcrumb-item:hover {
521
  color: var(--primary-glow);
522
+ background: rgba(255, 149, 128, 0.1);
523
  }
524
 
525
  .breadcrumb-separator {
526
  color: var(--border-color);
527
+ margin: 0 4px;
528
+ user-select: none;
529
+ }
530
+
531
+ @media (max-width: 768px) {
532
+ .breadcrumb {
533
+ padding: 8px 12px;
534
+ margin: 12px 0;
535
+ font-size: 12px;
536
+ overflow-x: auto;
537
+ -webkit-overflow-scrolling: touch;
538
+ scrollbar-width: none;
539
+ -ms-overflow-style: none;
540
+ }
541
+
542
+ .breadcrumb::-webkit-scrollbar {
543
+ display: none;
544
+ }
545
+
546
+ .breadcrumb-item {
547
+ padding: 4px 6px;
548
+ max-width: 150px;
549
+ }
550
  }
551
  /* 加载指示器样式 */
552
  .loading-indicator {
 
831
  justify-content: center;
832
  transition: all 0.3s ease;
833
  }
834
+ .cancel-download {
835
+ padding: 4px 8px;
836
+ border: none;
837
+ background: #ff4444;
838
+ color: white;
839
+ border-radius: 4px;
840
+ cursor: pointer;
841
+ font-size: 12px;
842
+ display: flex;
843
+ align-items: center;
844
+ gap: 4px;
845
+ transition: all 0.3s ease;
846
+ }
847
+
848
+ .cancel-download:hover {
849
+ background: #ff6666;
850
+ transform: translateY(-1px);
851
+ }
852
+
853
+ .stats-row {
854
+ display: flex;
855
+ justify-content: space-between;
856
+ margin-top: 4px;
857
+ }
858
+
859
+ .download-stats {
860
+ font-size: 12px;
861
+ color: #666;
862
+ margin-top: 8px;
863
+ }
864
+ .file-item.selectable {
865
+ position: relative;
866
+ cursor: pointer;
867
+ }
868
+
869
+ .file-item.selectable::before {
870
+ content: '';
871
+ position: absolute;
872
+ top: 10px;
873
+ left: 10px;
874
+ width: 20px;
875
+ height: 20px;
876
+ border: 2px solid var(--border-color);
877
+ border-radius: 4px;
878
+ background: white;
879
+ z-index: 1;
880
+ }
881
+
882
+ .file-item.selected::before {
883
+ background: var(--primary-glow);
884
+ border-color: var(--primary-glow);
885
+ }
886
+
887
+ .file-item.selected::after {
888
+ content: '\f00c';
889
+ font-family: 'Font Awesome 6 Free';
890
+ font-weight: 900;
891
+ position: absolute;
892
+ top: 10px;
893
+ left: 10px;
894
+ width: 20px;
895
+ height: 20px;
896
+ color: white;
897
+ display: flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ z-index: 2;
901
+ }
902
+
903
+ .multi-select-btn.active {
904
+ background: linear-gradient(45deg, var(--primary-glow), var(--secondary-glow));
905
+ color: white;
906
+ border-color: transparent;
907
+ }
908
+ .batch-operations {
909
+ display: flex;
910
+ gap: 8px;
911
+ margin-left: 16px;
912
+ }
913
+
914
+ .folder-name-input {
915
+ width: 100%;
916
+ padding: 8px 12px;
917
+ border: 1px solid var(--border-color);
918
+ border-radius: 4px;
919
+ margin: 16px 0;
920
+ font-size: 14px;
921
+ }
922
+
923
+ .folder-name-input:focus {
924
+ outline: none;
925
+ border-color: var(--primary-glow);
926
+ box-shadow: 0 0 0 2px rgba(255, 149, 128, 0.2);
927
+ }
928
+
929
+ .multi-select-btn.active {
930
+ background: linear-gradient(45deg, var(--primary-glow), var(--secondary-glow));
931
+ color: white;
932
+ border-color: transparent;
933
+ }
934
  </style>
935
  </head>
936
  <body>
 
975
 
976
  <!-- 主内容区 -->
977
  <main class="main-content">
978
+ <!-- 面包屑导航 -->
979
+ <div class="breadcrumb">
980
+ <span class="breadcrumb-item" data-path="/">根目录</span>
981
+ </div>
982
+ <button class="action-btn create-folder-btn">
983
+ <i class="fas fa-folder-plus"></i>
984
+ <span>新建文件夹</span>
985
+ </button>
986
  <!-- 视图切换按钮 -->
987
  <div class="view-toggle">
988
  <button class="view-btn active" data-view="grid">
 
993
  </button>
994
  </div>
995
 
996
+
 
 
 
997
 
998
  <!-- 文件容器 -->
999
  <div class="file-container">
 
1009
 
1010
  <!-- 上传进度条 -->
1011
  <div class="upload-progress">
 
 
 
1012
  </div>
1013
  </div>
1014
 
 
1044
  // 文件管理类
1045
  class FileManager {
1046
  constructor() {
1047
+ this.selectedFiles = new Set();
1048
+ this.isMultiSelectMode = false;
1049
  this.currentPath = '/';
1050
  this.currentView = 'grid';
1051
  this.currentFileType = 'all';
 
1079
 
1080
  // 拖拽上传
1081
  this.initDragAndDrop();
1082
+ // 新建文件夹按钮监听
1083
+ const createFolderBtn = document.querySelector('.create-folder-btn');
1084
+ createFolderBtn.addEventListener('click', () => this.showCreateFolderDialog());
1085
+
1086
+ // 添加多选按钮
1087
+ const multiSelectBtn = document.createElement('button');
1088
+ multiSelectBtn.className = 'action-btn multi-select-btn';
1089
+ multiSelectBtn.innerHTML = '<i class="fas fa-check-square"></i><span>多选</span>';
1090
+ multiSelectBtn.addEventListener('click', () => this.toggleMultiSelectMode());
1091
+ document.querySelector('.view-toggle').prepend(multiSelectBtn);
1092
  }
1093
 
1094
  // 加载文件列表
 
1131
  const element = document.createElement('div');
1132
  element.className = `file-item ${this.currentView}`;
1133
 
1134
+ // 添加多选模式相关的类
1135
+ if (this.isMultiSelectMode) {
1136
+ element.classList.add('selectable');
1137
+ if (this.selectedFiles.has(file)) {
1138
+ element.classList.add('selected');
1139
+ }
1140
+ }
1141
+
1142
  const icon = this.getFileIcon(file.type, file.file_type);
1143
  const size = this.formatFileSize(file.size);
1144
 
 
1161
  `;
1162
  }
1163
 
1164
+ // 事件处理逻辑
1165
+ if (this.isMultiSelectMode) {
1166
+ // 多选模式下的点击处理
1167
+ element.addEventListener('click', (e) => {
1168
+ e.preventDefault();
1169
+ e.stopPropagation();
1170
+
1171
+ if (file.type === 'directory') {
1172
+ // 文件夹仍然保持导航功能
1173
+ this.currentPath = file.path;
1174
+ this.loadFiles();
1175
+ } else {
1176
+ // 文件切换选中状态
1177
+ if (this.selectedFiles.has(file)) {
1178
+ this.selectedFiles.delete(file);
1179
+ element.classList.remove('selected');
1180
+ } else {
1181
+ this.selectedFiles.add(file);
1182
+ element.classList.add('selected');
1183
+ }
1184
+ }
1185
+ });
1186
+ } else {
1187
+ // 普通模式下的点击处理
1188
+ element.addEventListener('click', () => this.handleFileClick(file));
1189
+ }
1190
+
1191
+ // 右键菜单处理
1192
+ element.addEventListener('contextmenu', (e) => {
1193
+ e.preventDefault();
1194
+ this.showFileMenu(e, file);
1195
+ });
1196
 
1197
  return element;
1198
  }
 
1199
  // 处理文件点击
1200
  handleFileClick(file) {
1201
  if (file.type === 'directory') {
 
1439
  // 文件下载
1440
  async downloadFile(file) {
1441
  try {
1442
+ const uploadProgress = document.querySelector('.upload-progress');
1443
+ const progressItem = document.createElement('div');
1444
+ progressItem.className = 'progress-item';
1445
+ progressItem.innerHTML = `
1446
+ <div class="file-info">
1447
+ <span class="filename">${file.path.split('/').pop()}</span>
1448
+ <button class="cancel-download">
1449
+ <i class="fas fa-times"></i> 取消
1450
+ </button>
1451
+ </div>
1452
+ <div class="progress-bar">
1453
+ <div class="progress-fill"></div>
1454
+ </div>
1455
+ <div class="download-stats">
1456
+ <div class="stats-row">
1457
+ <span class="progress-text">0%</span>
1458
+ <span class="downloaded-size">0 MB / 0 MB</span>
1459
+ </div>
1460
+ <div class="stats-row">
1461
+ <span class="speed">等待开始...</span>
1462
+ </div>
1463
+ </div>
1464
+ `;
1465
+
1466
+ uploadProgress.style.display = 'block';
1467
+ uploadProgress.appendChild(progressItem);
1468
+
1469
+ const progressFill = progressItem.querySelector('.progress-fill');
1470
+ const progressText = progressItem.querySelector('.progress-text');
1471
+ const speedElement = progressItem.querySelector('.speed');
1472
+ const sizeElement = progressItem.querySelector('.downloaded-size');
1473
+ const cancelButton = progressItem.querySelector('.cancel-download');
1474
+
1475
+ const controller = new AbortController();
1476
+ let isDownloadCancelled = false;
1477
+
1478
+ cancelButton.onclick = () => {
1479
+ controller.abort();
1480
+ isDownloadCancelled = true;
1481
+ progressItem.remove();
1482
+ if (!uploadProgress.hasChildNodes()) {
1483
+ uploadProgress.style.display = 'none';
1484
+ }
1485
+ };
1486
+
1487
+ const response = await fetch(`/api/files/download/${file.path}`, {
1488
+ signal: controller.signal
1489
+ });
1490
+
1491
  if (!response.ok) throw new Error('Download failed');
1492
+
1493
+ const contentLength = response.headers.get('content-length');
1494
+ const total = parseInt(contentLength, 10);
1495
+ const reader = response.body.getReader();
1496
 
1497
+ let receivedLength = 0;
1498
+ let lastTime = Date.now();
1499
+ let lastReceived = 0;
1500
+ let currentSpeed = 0;
1501
+ let lastSpeedUpdate = Date.now();
1502
+ const chunks = [];
1503
+
1504
+ while (true) {
1505
+ const {done, value} = await reader.read();
1506
+
1507
+ if (done || isDownloadCancelled) break;
1508
+
1509
+ chunks.push(value);
1510
+ receivedLength += value.length;
1511
+
1512
+ const percent = (receivedLength / total) * 100;
1513
+ progressFill.style.width = `${percent.toFixed(1)}%`;
1514
+ progressText.textContent = `${percent.toFixed(1)}%`;
1515
+
1516
+ const now = Date.now();
1517
+ if (now - lastSpeedUpdate >= 1000) {
1518
+ const timeElapsed = (now - lastTime) / 1000;
1519
+ const receivedSinceLastTime = receivedLength - lastReceived;
1520
+
1521
+ if (timeElapsed > 0) {
1522
+ currentSpeed = receivedSinceLastTime / timeElapsed;
1523
+ if (currentSpeed > 0) {
1524
+ speedElement.textContent = `${this.formatFileSize(currentSpeed)}/s`;
1525
+ }
1526
+ }
1527
+
1528
+ lastTime = now;
1529
+ lastReceived = receivedLength;
1530
+ lastSpeedUpdate = now;
1531
+ }
1532
+
1533
+ sizeElement.textContent = `${this.formatFileSize(receivedLength)} / ${this.formatFileSize(total)}`;
1534
+ }
1535
+
1536
+ if (!isDownloadCancelled) {
1537
+ const blob = new Blob(chunks);
1538
+ const url = URL.createObjectURL(blob);
1539
+ const a = document.createElement('a');
1540
+ a.href = url;
1541
+ a.download = file.path.split('/').pop();
1542
+ document.body.appendChild(a);
1543
+ a.click();
1544
+ document.body.removeChild(a);
1545
+ URL.revokeObjectURL(url);
1546
+
1547
+ progressItem.remove();
1548
+ if (!uploadProgress.hasChildNodes()) {
1549
+ uploadProgress.style.display = 'none';
1550
+ }
1551
+ }
1552
+
1553
  } catch (error) {
1554
+ const uploadProgress = document.querySelector('.upload-progress');
1555
+ if (error.name === 'AbortError') {
1556
+ this.showMessage('下载已取消');
1557
+ } else {
1558
+ console.error('Error downloading file:', error);
1559
+ this.showError('下载文件失败');
1560
+ }
1561
+ if (!uploadProgress.hasChildNodes()) {
1562
+ uploadProgress.style.display = 'none';
1563
+ }
1564
  }
1565
  }
 
1566
  // 文件上传处理
1567
  async handleFileUpload(files) {
1568
  const uploadProgress = document.querySelector('.upload-progress');
 
1898
  }, 2000);
1899
  }, 100);
1900
  }
1901
+
1902
+ // 添加多选模式切换按钮
1903
+ addMultiSelectButton() {
1904
+ const multiSelectBtn = document.createElement('button');
1905
+ multiSelectBtn.className = 'multi-select-btn';
1906
+ multiSelectBtn.innerHTML = '<i class="fas fa-check-square"></i> 多选';
1907
+ multiSelectBtn.onclick = () => this.toggleMultiSelectMode();
1908
+
1909
+ document.querySelector('.view-toggle').appendChild(multiSelectBtn);
1910
+ }
1911
+
1912
+ // 切换多选模式
1913
+ toggleMultiSelectMode() {
1914
+ this.isMultiSelectMode = !this.isMultiSelectMode;
1915
+ this.selectedFiles.clear();
1916
+
1917
+ // 更新按钮状态
1918
+ const multiSelectBtn = document.querySelector('.multi-select-btn');
1919
+ multiSelectBtn.classList.toggle('active');
1920
+
1921
+ // 更新按钮文本
1922
+ if (this.isMultiSelectMode) {
1923
+ // 显示批量操作按钮
1924
+ this.showBatchOperations();
1925
+ multiSelectBtn.innerHTML = '<i class="fas fa-check-square"></i><span>退出多选</span>';
1926
+ } else {
1927
+ // 隐藏批量操作按钮
1928
+ this.hideBatchOperations();
1929
+ multiSelectBtn.innerHTML = '<i class="fas fa-check-square"></i><span>多选</span>';
1930
+ }
1931
+
1932
+ this.renderFiles();
1933
+ }
1934
+ showBatchOperations() {
1935
+ const batchOpsContainer = document.createElement('div');
1936
+ batchOpsContainer.className = 'batch-operations';
1937
+ batchOpsContainer.innerHTML = `
1938
+ <button class="action-btn batch-download-btn">
1939
+ <i class="fas fa-download"></i><span>批量下载</span>
1940
+ </button>
1941
+ <button class="action-btn batch-delete-btn">
1942
+ <i class="fas fa-trash"></i><span>批量删除</span>
1943
+ </button>
1944
+ `;
1945
+
1946
+ document.querySelector('.view-toggle').appendChild(batchOpsContainer);
1947
+
1948
+ // 绑定事件
1949
+ batchOpsContainer.querySelector('.batch-download-btn').onclick = () => this.batchDownload();
1950
+ batchOpsContainer.querySelector('.batch-delete-btn').onclick = () => this.batchDelete();
1951
+ }
1952
+
1953
+ hideBatchOperations() {
1954
+ const batchOps = document.querySelector('.batch-operations');
1955
+ if (batchOps) {
1956
+ batchOps.remove();
1957
+ }
1958
+ }
1959
+ // 批量下载
1960
+ async batchDownload() {
1961
+ for (const file of this.selectedFiles) {
1962
+ await this.downloadFile(file);
1963
+ }
1964
+ }
1965
+
1966
+ // 批量删除
1967
+ async batchDelete() {
1968
+ const confirmed = await this.showConfirmDialog(
1969
+ '批量删除',
1970
+ `确定要删除选中的 ${this.selectedFiles.size} 个文件吗?此操作不可恢复。`
1971
+ );
1972
+
1973
+ if (confirmed) {
1974
+ for (const file of this.selectedFiles) {
1975
+ await this.deleteFile(file);
1976
+ }
1977
+ }
1978
+ }
1979
+ async createFolder(folderName) {
1980
+ try {
1981
+ const response = await fetch('/api/files/create_folder', {
1982
+ method: 'POST',
1983
+ headers: {
1984
+ 'Content-Type': 'application/json'
1985
+ },
1986
+ body: JSON.stringify({
1987
+ path: this.currentPath,
1988
+ name: folderName
1989
+ })
1990
+ });
1991
+
1992
+ if (!response.ok) {
1993
+ throw new Error('Failed to create folder');
1994
+ }
1995
+
1996
+ await this.loadFiles();
1997
+ this.showMessage('文件夹创建成功');
1998
+
1999
+ } catch (error) {
2000
+ console.error('Error creating folder:', error);
2001
+ this.showError('创建文件夹失败');
2002
+ }
2003
+ }
2004
+
2005
+ // 显示创建文件夹对话框
2006
+ async showCreateFolderDialog() {
2007
+ const modal = document.createElement('div');
2008
+ modal.className = 'confirm-modal';
2009
+ modal.innerHTML = `
2010
+ <div class="confirm-content">
2011
+ <h3>新建文件夹</h3>
2012
+ <div class="input-container">
2013
+ <input type="text"
2014
+ class="folder-name-input"
2015
+ placeholder="请输入文件夹名称"
2016
+ maxlength="255">
2017
+ </div>
2018
+ <div class="confirm-buttons">
2019
+ <button class="confirm-cancel">取消</button>
2020
+ <button class="confirm-ok">创建</button>
2021
+ </div>
2022
+ </div>
2023
+ `;
2024
+
2025
+ document.body.appendChild(modal);
2026
+ const input = modal.querySelector('.folder-name-input');
2027
+ input.focus();
2028
+
2029
+ try {
2030
+ const folderName = await new Promise((resolve) => {
2031
+ const handleCreateFolder = () => {
2032
+ const name = input.value.trim();
2033
+ if (name) {
2034
+ resolve(name);
2035
+ }
2036
+ modal.remove();
2037
+ };
2038
+
2039
+ const handleCancel = () => {
2040
+ resolve(null);
2041
+ modal.remove();
2042
+ };
2043
+
2044
+ modal.querySelector('.confirm-ok').onclick = handleCreateFolder;
2045
+ modal.querySelector('.confirm-cancel').onclick = handleCancel;
2046
+
2047
+ input.onkeyup = (e) => {
2048
+ if (e.key === 'Enter') handleCreateFolder();
2049
+ if (e.key === 'Escape') handleCancel();
2050
+ };
2051
+ });
2052
+
2053
+ if (folderName) {
2054
+ await this.createFolder(folderName);
2055
+ }
2056
+ } catch (error) {
2057
+ console.error('Error creating folder:', error);
2058
+ this.showError('创建文件夹失败');
2059
+ }
2060
+ }
2061
+
2062
  }
2063
  // 初始化文件管理器
2064
  new FileManager();
templates/login.html CHANGED
@@ -1,127 +1,138 @@
1
- <!DOCTYPE html>
2
- <html lang="zh">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>登录 - 云存储</title>
7
- <style>
8
- :root {
9
- --primary-glow: #ff9580;
10
- --secondary-glow: #ffd700;
11
- --background: #ffffff;
12
- --text: #333333;
13
- --card-bg: #ffffff;
14
- --border-color: #e0e0e0;
15
- }
16
- * {
17
- margin: 0;
18
- padding: 0;
19
- box-sizing: border-box;
20
- }
21
- body {
22
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
23
- background: var(--background);
24
- color: var(--text);
25
- min-height: 100vh;
26
- display: flex;
27
- align-items: center;
28
- justify-content: center;
29
- }
30
- .login-container {
31
- background: var(--card-bg);
32
- padding: 40px;
33
- border-radius: 15px;
34
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
35
- width: 100%;
36
- max-width: 400px;
37
- transition: all 0.3s ease;
38
- }
39
- .login-container:hover {
40
- transform: translateY(-5px);
41
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
42
- }
43
- .login-title {
44
- text-align: center;
45
- margin-bottom: 30px;
46
- font-size: 24px;
47
- color: var(--text);
48
- }
49
- .login-form {
50
- display: flex;
51
- flex-direction: column;
52
- gap: 20px;
53
- }
54
- .password-input {
55
- width: 100%;
56
- padding: 12px 20px;
57
- border: 2px solid var(--border-color);
58
- border-radius: 25px;
59
- font-size: 16px;
60
- transition: all 0.3s ease;
61
- }
62
- .password-input:focus {
63
- outline: none;
64
- border-color: var(--primary-glow);
65
- box-shadow: 0 0 10px rgba(255, 149, 128, 0.3);
66
- }
67
- .login-button {
68
- width: 100%;
69
- padding: 12px 20px;
70
- border: none;
71
- border-radius: 25px;
72
- background: linear-gradient(45deg, var(--primary-glow), var(--secondary-glow));
73
- color: white;
74
- font-size: 16px;
75
- cursor: pointer;
76
- transition: all 0.3s ease;
77
- }
78
- .login-button:hover {
79
- transform: translateY(-2px);
80
- box-shadow: 0 5px 15px rgba(255, 149, 128, 0.4);
81
- }
82
- .error-message {
83
- color: #ff4444;
84
- text-align: center;
85
- margin-top: 10px;
86
- display: none;
87
- }
88
- </style>
89
- </head>
90
- <body>
91
- <div class="login-container">
92
- <h1 class="login-title"> Cloud Vault的登录界面(云盘)</h1>
93
- <p class="login-title">没错在这输一下密码,密码1234</p>
94
- <form class="login-form" id="loginForm">
95
- <input type="password" class="password-input" placeholder="请输入访问密码" required>
96
- <button type="submit" class="login-button">登录</button>
97
- </form>
98
- <div class="error-message" id="errorMessage">密码错误,请重试</div>
99
- </div>
100
-
101
- <script>
102
- document.getElementById('loginForm').addEventListener('submit', async (e) => {
103
- e.preventDefault();
104
- const password = document.querySelector('.password-input').value;
105
- const errorMessage = document.getElementById('errorMessage');
106
-
107
- try {
108
- const response = await fetch('/login', {
109
- method: 'POST',
110
- headers: {
111
- 'Content-Type': 'application/x-www-form-urlencoded',
112
- },
113
- body: `password=${encodeURIComponent(password)}`
114
- });
115
- if (response.ok) {
116
- window.location.href = '/';
117
- } else {
118
- errorMessage.style.display = 'block';
119
- }
120
- } catch (error) {
121
- console.error('Login failed:', error);
122
- errorMessage.style.display = 'block';
123
- }
124
- });
125
- </script>
126
- </body>
 
 
 
 
 
 
 
 
 
 
 
127
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>登录 - 云存储</title>
7
+ <style>
8
+ :root {
9
+ --primary-glow: #ff9580;
10
+ --secondary-glow: #ffd700;
11
+ --background: #ffffff;
12
+ --text: #333333;
13
+ --card-bg: #ffffff;
14
+ --border-color: #e0e0e0;
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ body {
24
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
25
+ background: var(--background);
26
+ color: var(--text);
27
+ min-height: 100vh;
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ }
32
+
33
+ .login-container {
34
+ background: var(--card-bg);
35
+ padding: 40px;
36
+ border-radius: 15px;
37
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
38
+ width: 100%;
39
+ max-width: 400px;
40
+ transition: all 0.3s ease;
41
+ }
42
+
43
+ .login-container:hover {
44
+ transform: translateY(-5px);
45
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
46
+ }
47
+
48
+ .login-title {
49
+ text-align: center;
50
+ margin-bottom: 30px;
51
+ font-size: 24px;
52
+ color: var(--text);
53
+ }
54
+
55
+ .login-form {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 20px;
59
+ }
60
+
61
+ .password-input {
62
+ width: 100%;
63
+ padding: 12px 20px;
64
+ border: 2px solid var(--border-color);
65
+ border-radius: 25px;
66
+ font-size: 16px;
67
+ transition: all 0.3s ease;
68
+ }
69
+
70
+ .password-input:focus {
71
+ outline: none;
72
+ border-color: var(--primary-glow);
73
+ box-shadow: 0 0 10px rgba(255, 149, 128, 0.3);
74
+ }
75
+
76
+ .login-button {
77
+ width: 100%;
78
+ padding: 12px 20px;
79
+ border: none;
80
+ border-radius: 25px;
81
+ background: linear-gradient(45deg, var(--primary-glow), var(--secondary-glow));
82
+ color: white;
83
+ font-size: 16px;
84
+ cursor: pointer;
85
+ transition: all 0.3s ease;
86
+ }
87
+
88
+ .login-button:hover {
89
+ transform: translateY(-2px);
90
+ box-shadow: 0 5px 15px rgba(255, 149, 128, 0.4);
91
+ }
92
+
93
+ .error-message {
94
+ color: #ff4444;
95
+ text-align: center;
96
+ margin-top: 10px;
97
+ display: none;
98
+ }
99
+ </style>
100
+ </head>
101
+ <body>
102
+ <div class="login-container">
103
+ <h1 class="login-title"> Cloud Vault的登录界面,没错在这输一下密码,密码1234</h1>
104
+ <form class="login-form" id="loginForm">
105
+ <input type="password" class="password-input" placeholder="请输入访问密码" required>
106
+ <button type="submit" class="login-button">登录</button>
107
+ </form>
108
+ <div class="error-message" id="errorMessage">密码错误,请重试</div>
109
+ </div>
110
+
111
+ <script>
112
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
113
+ e.preventDefault();
114
+ const password = document.querySelector('.password-input').value;
115
+ const errorMessage = document.getElementById('errorMessage');
116
+
117
+ try {
118
+ const response = await fetch('/login', {
119
+ method: 'POST',
120
+ headers: {
121
+ 'Content-Type': 'application/x-www-form-urlencoded',
122
+ },
123
+ body: `password=${encodeURIComponent(password)}`
124
+ });
125
+
126
+ if (response.ok) {
127
+ window.location.href = '/';
128
+ } else {
129
+ errorMessage.style.display = 'block';
130
+ }
131
+ } catch (error) {
132
+ console.error('Login failed:', error);
133
+ errorMessage.style.display = 'block';
134
+ }
135
+ });
136
+ </script>
137
+ </body>
138
  </html>