WACATW commited on
Commit
1b14d1f
·
verified ·
1 Parent(s): 7d9040a

Upload 3 files

Browse files
Files changed (3) hide show
  1. public/cloud.html +152 -0
  2. public/css/cloud.css +647 -0
  3. public/js/cloud.js +483 -0
public/cloud.html ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>云盘 - 一只猫的小家</title>
7
+ <link rel="stylesheet" href="css/cloud.css">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ </head>
10
+ <body>
11
+ <!-- 顶部导航 -->
12
+ <header class="header">
13
+ <div class="header-left">
14
+ <a href="index.html" class="back-btn">
15
+ <i class="fas fa-arrow-left"></i>
16
+ <span>返回首页</span>
17
+ </a>
18
+ <h1 class="logo">☁️ 我的云盘</h1>
19
+ </div>
20
+ <div class="header-right">
21
+ <div class="storage-info">
22
+ <div class="storage-text">
23
+ <span id="usedStorage">0 B</span> / <span id="maxStorage">1 GB</span>
24
+ </div>
25
+ <div class="storage-bar">
26
+ <div class="storage-fill" id="storageFill"></div>
27
+ </div>
28
+ </div>
29
+ <div class="user-info">
30
+ <span class="username" id="username">加载中...</span>
31
+ <a href="index.html" class="logout-btn" onclick="logout()">
32
+ <i class="fas fa-sign-out-alt"></i>
33
+ </a>
34
+ </div>
35
+ </div>
36
+ </header>
37
+
38
+ <!-- 主内容区 -->
39
+ <main class="main-content">
40
+ <!-- 操作栏 -->
41
+ <div class="toolbar">
42
+ <div class="toolbar-left">
43
+ <button class="btn btn-primary" id="uploadBtn">
44
+ <i class="fas fa-upload"></i>
45
+ <span>上传文件</span>
46
+ </button>
47
+ <input type="file" id="fileInput" multiple style="display: none;">
48
+
49
+ <button class="btn btn-secondary" id="mkdirBtn">
50
+ <i class="fas fa-folder-plus"></i>
51
+ <span>新建文件夹</span>
52
+ </button>
53
+
54
+ <button class="btn btn-secondary" id="refreshBtn">
55
+ <i class="fas fa-sync-alt"></i>
56
+ <span>刷新</span>
57
+ </button>
58
+ </div>
59
+
60
+ <!-- 面包屑导航 -->
61
+ <div class="breadcrumb" id="breadcrumb">
62
+ <a href="#" data-path="">
63
+ <i class="fas fa-home"></i> 根目录
64
+ </a>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- 上传区域 -->
69
+ <div class="upload-zone" id="uploadZone">
70
+ <div class="upload-icon">
71
+ <i class="fas fa-cloud-upload-alt"></i>
72
+ </div>
73
+ <div class="upload-text">
74
+ <p>拖拽文件到这里上传</p>
75
+ <p class="upload-hint">或者点击上方"上传文件"按钮</p>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- 文件列表 -->
80
+ <div class="file-list" id="fileList">
81
+ <div class="loading">
82
+ <i class="fas fa-spinner fa-spin"></i>
83
+ <span>加载中...</span>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- 空状态 -->
88
+ <div class="empty-state" id="emptyState" style="display: none;">
89
+ <div class="empty-icon">
90
+ <i class="fas fa-folder-open"></i>
91
+ </div>
92
+ <p>这里还没有文件</p>
93
+ <p class="empty-hint">上传文件或新建文件夹开始使用吧</p>
94
+ </div>
95
+ </main>
96
+
97
+ <!-- 新建文件夹模态框 -->
98
+ <div class="modal" id="mkdirModal">
99
+ <div class="modal-content">
100
+ <div class="modal-header">
101
+ <h3>新建文件夹</h3>
102
+ <button class="modal-close" id="closeMkdirModal">
103
+ <i class="fas fa-times"></i>
104
+ </button>
105
+ </div>
106
+ <div class="modal-body">
107
+ <input type="text" id="folderNameInput" placeholder="请输入文件夹名称" class="modal-input">
108
+ </div>
109
+ <div class="modal-footer">
110
+ <button class="btn btn-secondary" id="cancelMkdir">取消</button>
111
+ <button class="btn btn-primary" id="confirmMkdir">创建</button>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- 删除确认模态框 -->
117
+ <div class="modal" id="deleteModal">
118
+ <div class="modal-content">
119
+ <div class="modal-header">
120
+ <h3>确认删除</h3>
121
+ <button class="modal-close" id="closeDeleteModal">
122
+ <i class="fas fa-times"></i>
123
+ </button>
124
+ </div>
125
+ <div class="modal-body">
126
+ <p>确定要删除 <strong id="deleteFileName"></strong> 吗?</p>
127
+ <p class="delete-warning">此操作不可恢复!</p>
128
+ </div>
129
+ <div class="modal-footer">
130
+ <button class="btn btn-secondary" id="cancelDelete">取消</button>
131
+ <button class="btn btn-danger" id="confirmDelete">删除</button>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- 上传进度条 -->
137
+ <div class="upload-progress" id="uploadProgress">
138
+ <div class="progress-bar">
139
+ <div class="progress-fill" id="progressFill"></div>
140
+ </div>
141
+ <div class="progress-text" id="progressText">正在上传...</div>
142
+ </div>
143
+
144
+ <!-- Toast 提示 -->
145
+ <div class="toast" id="toast">
146
+ <i class="toast-icon fas fa-info-circle" id="toastIcon"></i>
147
+ <span class="toast-message" id="toastMessage"></span>
148
+ </div>
149
+
150
+ <script src="js/cloud.js"></script>
151
+ </body>
152
+ </html>
public/css/cloud.css ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
9
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10
+ min-height: 100vh;
11
+ color: #333;
12
+ }
13
+
14
+ /* 顶部导航 */
15
+ .header {
16
+ background: rgba(255, 255, 255, 0.95);
17
+ backdrop-filter: blur(10px);
18
+ padding: 15px 30px;
19
+ display: flex;
20
+ justify-content: space-between;
21
+ align-items: center;
22
+ box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
23
+ position: sticky;
24
+ top: 0;
25
+ z-index: 100;
26
+ }
27
+
28
+ .header-left {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 20px;
32
+ }
33
+
34
+ .back-btn {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ color: #667eea;
39
+ text-decoration: none;
40
+ font-size: 14px;
41
+ padding: 8px 16px;
42
+ border-radius: 20px;
43
+ background: rgba(102, 126, 234, 0.1);
44
+ transition: all 0.3s;
45
+ }
46
+
47
+ .back-btn:hover {
48
+ background: rgba(102, 126, 234, 0.2);
49
+ }
50
+
51
+ .logo {
52
+ font-size: 20px;
53
+ font-weight: 600;
54
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
55
+ -webkit-background-clip: text;
56
+ -webkit-text-fill-color: transparent;
57
+ background-clip: text;
58
+ }
59
+
60
+ .header-right {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 25px;
64
+ }
65
+
66
+ .storage-info {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 5px;
70
+ min-width: 180px;
71
+ }
72
+
73
+ .storage-text {
74
+ font-size: 12px;
75
+ color: #666;
76
+ text-align: right;
77
+ }
78
+
79
+ .storage-bar {
80
+ width: 180px;
81
+ height: 6px;
82
+ background: #e0e0e0;
83
+ border-radius: 3px;
84
+ overflow: hidden;
85
+ }
86
+
87
+ .storage-fill {
88
+ height: 100%;
89
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
90
+ border-radius: 3px;
91
+ transition: width 0.3s;
92
+ width: 0%;
93
+ }
94
+
95
+ .user-info {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 12px;
99
+ }
100
+
101
+ .username {
102
+ font-size: 14px;
103
+ color: #333;
104
+ font-weight: 500;
105
+ }
106
+
107
+ .logout-btn {
108
+ color: #999;
109
+ text-decoration: none;
110
+ font-size: 16px;
111
+ padding: 8px;
112
+ border-radius: 50%;
113
+ transition: all 0.3s;
114
+ }
115
+
116
+ .logout-btn:hover {
117
+ color: #f5576c;
118
+ background: rgba(245, 87, 108, 0.1);
119
+ }
120
+
121
+ /* 主内容区 */
122
+ .main-content {
123
+ max-width: 1200px;
124
+ margin: 30px auto;
125
+ padding: 0 20px;
126
+ }
127
+
128
+ /* 工具栏 */
129
+ .toolbar {
130
+ background: rgba(255, 255, 255, 0.95);
131
+ backdrop-filter: blur(10px);
132
+ border-radius: 15px;
133
+ padding: 20px;
134
+ margin-bottom: 20px;
135
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
136
+ }
137
+
138
+ .toolbar-left {
139
+ display: flex;
140
+ gap: 12px;
141
+ margin-bottom: 15px;
142
+ flex-wrap: wrap;
143
+ }
144
+
145
+ .btn {
146
+ display: inline-flex;
147
+ align-items: center;
148
+ gap: 8px;
149
+ padding: 10px 20px;
150
+ border: none;
151
+ border-radius: 10px;
152
+ font-size: 14px;
153
+ font-weight: 500;
154
+ cursor: pointer;
155
+ transition: all 0.3s;
156
+ text-decoration: none;
157
+ }
158
+
159
+ .btn-primary {
160
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
161
+ color: white;
162
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
163
+ }
164
+
165
+ .btn-primary:hover {
166
+ transform: translateY(-2px);
167
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
168
+ }
169
+
170
+ .btn-secondary {
171
+ background: #f5f5f5;
172
+ color: #333;
173
+ }
174
+
175
+ .btn-secondary:hover {
176
+ background: #e8e8e8;
177
+ }
178
+
179
+ .btn-danger {
180
+ background: linear-gradient(135deg, #f5576c 0%, #f093fb 100%);
181
+ color: white;
182
+ }
183
+
184
+ .btn-danger:hover {
185
+ transform: translateY(-2px);
186
+ box-shadow: 0 6px 20px rgba(245, 87, 108, 0.4);
187
+ }
188
+
189
+ /* 面包屑 */
190
+ .breadcrumb {
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 10px;
194
+ flex-wrap: wrap;
195
+ padding-top: 15px;
196
+ border-top: 1px solid #eee;
197
+ }
198
+
199
+ .breadcrumb a {
200
+ color: #667eea;
201
+ text-decoration: none;
202
+ font-size: 14px;
203
+ padding: 5px 10px;
204
+ border-radius: 5px;
205
+ transition: all 0.3s;
206
+ }
207
+
208
+ .breadcrumb a:hover {
209
+ background: rgba(102, 126, 234, 0.1);
210
+ }
211
+
212
+ .breadcrumb .separator {
213
+ color: #ccc;
214
+ font-size: 12px;
215
+ }
216
+
217
+ /* 上传区域 */
218
+ .upload-zone {
219
+ background: rgba(255, 255, 255, 0.9);
220
+ backdrop-filter: blur(10px);
221
+ border: 2px dashed #667eea;
222
+ border-radius: 15px;
223
+ padding: 40px;
224
+ text-align: center;
225
+ margin-bottom: 20px;
226
+ transition: all 0.3s;
227
+ }
228
+
229
+ .upload-zone.dragover {
230
+ background: rgba(102, 126, 234, 0.1);
231
+ border-color: #764ba2;
232
+ transform: scale(1.01);
233
+ }
234
+
235
+ .upload-icon {
236
+ font-size: 48px;
237
+ color: #667eea;
238
+ margin-bottom: 15px;
239
+ }
240
+
241
+ .upload-text p {
242
+ font-size: 16px;
243
+ color: #333;
244
+ margin-bottom: 5px;
245
+ }
246
+
247
+ .upload-hint {
248
+ font-size: 13px;
249
+ color: #999;
250
+ }
251
+
252
+ /* 文件列表 */
253
+ .file-list {
254
+ background: rgba(255, 255, 255, 0.95);
255
+ backdrop-filter: blur(10px);
256
+ border-radius: 15px;
257
+ overflow: hidden;
258
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
259
+ }
260
+
261
+ .file-item {
262
+ display: flex;
263
+ align-items: center;
264
+ padding: 15px 20px;
265
+ border-bottom: 1px solid #f0f0f0;
266
+ transition: all 0.3s;
267
+ cursor: pointer;
268
+ }
269
+
270
+ .file-item:last-child {
271
+ border-bottom: none;
272
+ }
273
+
274
+ .file-item:hover {
275
+ background: rgba(102, 126, 234, 0.05);
276
+ }
277
+
278
+ .file-icon {
279
+ width: 40px;
280
+ height: 40px;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ border-radius: 10px;
285
+ margin-right: 15px;
286
+ font-size: 20px;
287
+ }
288
+
289
+ .file-icon.folder {
290
+ background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
291
+ color: #ff9a56;
292
+ }
293
+
294
+ .file-icon.file {
295
+ background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
296
+ color: #667eea;
297
+ }
298
+
299
+ .file-info {
300
+ flex: 1;
301
+ min-width: 0;
302
+ }
303
+
304
+ .file-name {
305
+ font-size: 14px;
306
+ color: #333;
307
+ font-weight: 500;
308
+ margin-bottom: 3px;
309
+ white-space: nowrap;
310
+ overflow: hidden;
311
+ text-overflow: ellipsis;
312
+ }
313
+
314
+ .file-meta {
315
+ font-size: 12px;
316
+ color: #999;
317
+ display: flex;
318
+ gap: 15px;
319
+ }
320
+
321
+ .file-actions {
322
+ display: flex;
323
+ gap: 8px;
324
+ opacity: 0;
325
+ transition: opacity 0.3s;
326
+ }
327
+
328
+ .file-item:hover .file-actions {
329
+ opacity: 1;
330
+ }
331
+
332
+ .action-btn {
333
+ width: 32px;
334
+ height: 32px;
335
+ border: none;
336
+ border-radius: 8px;
337
+ background: #f5f5f5;
338
+ color: #666;
339
+ cursor: pointer;
340
+ transition: all 0.3s;
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ font-size: 14px;
345
+ }
346
+
347
+ .action-btn:hover {
348
+ background: #e8e8e8;
349
+ }
350
+
351
+ .action-btn.download-btn:hover {
352
+ background: rgba(102, 126, 234, 0.1);
353
+ color: #667eea;
354
+ }
355
+
356
+ .action-btn.delete-btn:hover {
357
+ background: rgba(245, 87, 108, 0.1);
358
+ color: #f5576c;
359
+ }
360
+
361
+ /* 加载状态 */
362
+ .loading {
363
+ padding: 60px;
364
+ text-align: center;
365
+ color: #999;
366
+ }
367
+
368
+ .loading i {
369
+ font-size: 32px;
370
+ margin-bottom: 15px;
371
+ color: #667eea;
372
+ }
373
+
374
+ .loading span {
375
+ display: block;
376
+ font-size: 14px;
377
+ }
378
+
379
+ /* 空状态 */
380
+ .empty-state {
381
+ background: rgba(255, 255, 255, 0.95);
382
+ backdrop-filter: blur(10px);
383
+ border-radius: 15px;
384
+ padding: 60px;
385
+ text-align: center;
386
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
387
+ }
388
+
389
+ .empty-icon {
390
+ font-size: 64px;
391
+ color: #ddd;
392
+ margin-bottom: 20px;
393
+ }
394
+
395
+ .empty-state p {
396
+ font-size: 16px;
397
+ color: #666;
398
+ margin-bottom: 5px;
399
+ }
400
+
401
+ .empty-hint {
402
+ font-size: 13px;
403
+ color: #999;
404
+ }
405
+
406
+ /* 模态框 */
407
+ .modal {
408
+ display: none;
409
+ position: fixed;
410
+ top: 0;
411
+ left: 0;
412
+ width: 100%;
413
+ height: 100%;
414
+ background: rgba(0, 0, 0, 0.5);
415
+ backdrop-filter: blur(5px);
416
+ z-index: 1000;
417
+ align-items: center;
418
+ justify-content: center;
419
+ }
420
+
421
+ .modal.show {
422
+ display: flex;
423
+ }
424
+
425
+ .modal-content {
426
+ background: white;
427
+ border-radius: 15px;
428
+ width: 90%;
429
+ max-width: 400px;
430
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
431
+ animation: modalIn 0.3s ease;
432
+ }
433
+
434
+ @keyframes modalIn {
435
+ from {
436
+ opacity: 0;
437
+ transform: scale(0.9);
438
+ }
439
+ to {
440
+ opacity: 1;
441
+ transform: scale(1);
442
+ }
443
+ }
444
+
445
+ .modal-header {
446
+ display: flex;
447
+ justify-content: space-between;
448
+ align-items: center;
449
+ padding: 20px;
450
+ border-bottom: 1px solid #f0f0f0;
451
+ }
452
+
453
+ .modal-header h3 {
454
+ font-size: 16px;
455
+ color: #333;
456
+ }
457
+
458
+ .modal-close {
459
+ background: none;
460
+ border: none;
461
+ font-size: 18px;
462
+ color: #999;
463
+ cursor: pointer;
464
+ transition: color 0.3s;
465
+ }
466
+
467
+ .modal-close:hover {
468
+ color: #666;
469
+ }
470
+
471
+ .modal-body {
472
+ padding: 20px;
473
+ }
474
+
475
+ .modal-input {
476
+ width: 100%;
477
+ padding: 12px 15px;
478
+ border: 2px solid #e0e0e0;
479
+ border-radius: 10px;
480
+ font-size: 14px;
481
+ outline: none;
482
+ transition: border-color 0.3s;
483
+ }
484
+
485
+ .modal-input:focus {
486
+ border-color: #667eea;
487
+ }
488
+
489
+ .modal-body p {
490
+ font-size: 14px;
491
+ color: #333;
492
+ margin-bottom: 10px;
493
+ }
494
+
495
+ .delete-warning {
496
+ color: #f5576c;
497
+ font-size: 13px;
498
+ }
499
+
500
+ .modal-footer {
501
+ display: flex;
502
+ justify-content: flex-end;
503
+ gap: 10px;
504
+ padding: 15px 20px;
505
+ border-top: 1px solid #f0f0f0;
506
+ }
507
+
508
+ /* 上传进度条 */
509
+ .upload-progress {
510
+ display: none;
511
+ position: fixed;
512
+ bottom: 30px;
513
+ left: 50%;
514
+ transform: translateX(-50%);
515
+ background: white;
516
+ padding: 15px 25px;
517
+ border-radius: 10px;
518
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
519
+ z-index: 999;
520
+ min-width: 300px;
521
+ }
522
+
523
+ .upload-progress.show {
524
+ display: block;
525
+ }
526
+
527
+ .progress-bar {
528
+ width: 100%;
529
+ height: 8px;
530
+ background: #e0e0e0;
531
+ border-radius: 4px;
532
+ overflow: hidden;
533
+ margin-bottom: 8px;
534
+ }
535
+
536
+ .progress-fill {
537
+ height: 100%;
538
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
539
+ border-radius: 4px;
540
+ transition: width 0.3s;
541
+ width: 0%;
542
+ }
543
+
544
+ .progress-text {
545
+ font-size: 13px;
546
+ color: #666;
547
+ text-align: center;
548
+ }
549
+
550
+ /* Toast 提示 */
551
+ .toast {
552
+ position: fixed;
553
+ top: 80px;
554
+ left: 50%;
555
+ transform: translateX(-50%) translateY(-20px);
556
+ background: white;
557
+ padding: 12px 24px;
558
+ border-radius: 10px;
559
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
560
+ z-index: 1001;
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 10px;
564
+ opacity: 0;
565
+ transition: all 0.3s;
566
+ pointer-events: none;
567
+ }
568
+
569
+ .toast.show {
570
+ opacity: 1;
571
+ transform: translateX(-50%) translateY(0);
572
+ }
573
+
574
+ .toast-icon {
575
+ font-size: 18px;
576
+ }
577
+
578
+ .toast-success .toast-icon {
579
+ color: #4caf50;
580
+ }
581
+
582
+ .toast-error .toast-icon {
583
+ color: #f5576c;
584
+ }
585
+
586
+ .toast-info .toast-icon {
587
+ color: #667eea;
588
+ }
589
+
590
+ .toast-message {
591
+ font-size: 14px;
592
+ color: #333;
593
+ }
594
+
595
+ /* 响应式 */
596
+ @media (max-width: 768px) {
597
+ .header {
598
+ padding: 12px 15px;
599
+ flex-direction: column;
600
+ gap: 15px;
601
+ }
602
+
603
+ .header-left {
604
+ width: 100%;
605
+ justify-content: space-between;
606
+ }
607
+
608
+ .header-right {
609
+ width: 100%;
610
+ justify-content: space-between;
611
+ }
612
+
613
+ .storage-bar {
614
+ width: 120px;
615
+ }
616
+
617
+ .main-content {
618
+ margin: 15px auto;
619
+ padding: 0 10px;
620
+ }
621
+
622
+ .toolbar {
623
+ padding: 15px;
624
+ }
625
+
626
+ .btn {
627
+ padding: 8px 15px;
628
+ font-size: 13px;
629
+ }
630
+
631
+ .btn span {
632
+ display: none;
633
+ }
634
+
635
+ .upload-zone {
636
+ padding: 30px 20px;
637
+ }
638
+
639
+ .file-actions {
640
+ opacity: 1;
641
+ }
642
+
643
+ .file-meta {
644
+ flex-direction: column;
645
+ gap: 2px;
646
+ }
647
+ }
public/js/cloud.js ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 全局变量
2
+ let currentPath = '';
3
+ let token = localStorage.getItem('token') || '';
4
+
5
+ // DOM 元素
6
+ const uploadBtn = document.getElementById('uploadBtn');
7
+ const fileInput = document.getElementById('fileInput');
8
+ const mkdirBtn = document.getElementById('mkdirBtn');
9
+ const refreshBtn = document.getElementById('refreshBtn');
10
+ const uploadZone = document.getElementById('uploadZone');
11
+ const fileList = document.getElementById('fileList');
12
+ const emptyState = document.getElementById('emptyState');
13
+ const breadcrumb = document.getElementById('breadcrumb');
14
+ const mkdirModal = document.getElementById('mkdirModal');
15
+ const deleteModal = document.getElementById('deleteModal');
16
+ const uploadProgress = document.getElementById('uploadProgress');
17
+ const progressFill = document.getElementById('progressFill');
18
+ const progressText = document.getElementById('progressText');
19
+ const toast = document.getElementById('toast');
20
+ const toastIcon = document.getElementById('toastIcon');
21
+ const toastMessage = document.getElementById('toastMessage');
22
+
23
+ // 初始化
24
+ document.addEventListener('DOMContentLoaded', () => {
25
+ checkLogin();
26
+ loadUserInfo();
27
+ loadFileList();
28
+ initEventListeners();
29
+ });
30
+
31
+ // 检查登录状态
32
+ function checkLogin() {
33
+ if (!token) {
34
+ showToast('请先登录', 'error');
35
+ setTimeout(() => {
36
+ window.location.href = 'login.html';
37
+ }, 1500);
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+
43
+ // 加载用户信息
44
+ function loadUserInfo() {
45
+ fetch('/api/cloud/user-info', {
46
+ headers: {
47
+ 'Authorization': 'Bearer ' + token
48
+ }
49
+ })
50
+ .then(res => res.json())
51
+ .then(data => {
52
+ if (data.code === 200) {
53
+ document.getElementById('username').textContent = data.data.username;
54
+ document.getElementById('usedStorage').textContent = data.data.usedStorageFormatted;
55
+ document.getElementById('maxStorage').textContent = data.data.maxStorageFormatted;
56
+ document.getElementById('storageFill').style.width = data.data.storagePercent + '%';
57
+ } else if (data.code === 401) {
58
+ localStorage.removeItem('token');
59
+ showToast('登录已过期,请重新登录', 'error');
60
+ setTimeout(() => {
61
+ window.location.href = 'login.html';
62
+ }, 1500);
63
+ }
64
+ })
65
+ .catch(() => {
66
+ showToast('加载用户信息失败', 'error');
67
+ });
68
+ }
69
+
70
+ // 加载文件列表
71
+ function loadFileList() {
72
+ fileList.style.display = 'block';
73
+ emptyState.style.display = 'none';
74
+ fileList.innerHTML = '<div class="loading"><i class="fas fa-spinner fa-spin"></i><span>加载中...</span></div>';
75
+
76
+ fetch('/api/cloud/list?path=' + encodeURIComponent(currentPath), {
77
+ headers: {
78
+ 'Authorization': 'Bearer ' + token
79
+ }
80
+ })
81
+ .then(res => res.json())
82
+ .then(data => {
83
+ if (data.code === 200) {
84
+ renderFileList(data.data.files);
85
+ renderBreadcrumb(data.data.breadcrumb);
86
+ // 更新存储信息
87
+ loadUserInfo();
88
+ } else {
89
+ fileList.innerHTML = '<div class="loading"><span>加载失败</span></div>';
90
+ showToast(data.msg || '加载失败', 'error');
91
+ }
92
+ })
93
+ .catch(() => {
94
+ fileList.innerHTML = '<div class="loading"><span>加载失败</span></div>';
95
+ showToast('网络错误', 'error');
96
+ });
97
+ }
98
+
99
+ // 渲染文件列表
100
+ function renderFileList(files) {
101
+ if (files.length === 0) {
102
+ fileList.style.display = 'none';
103
+ emptyState.style.display = 'block';
104
+ return;
105
+ }
106
+
107
+ let html = '';
108
+ files.forEach(file => {
109
+ const iconClass = file.type === 'directory' ? 'folder' : 'file';
110
+ const icon = file.type === 'directory' ? 'fa-folder' : 'fa-file';
111
+
112
+ html += `
113
+ <div class="file-item" data-name="${file.name}" data-type="${file.type}">
114
+ <div class="file-icon ${iconClass}">
115
+ <i class="fas ${icon}"></i>
116
+ </div>
117
+ <div class="file-info">
118
+ <div class="file-name">${file.name}</div>
119
+ <div class="file-meta">
120
+ <span>${file.sizeFormatted}</span>
121
+ <span>${file.modified}</span>
122
+ </div>
123
+ </div>
124
+ <div class="file-actions">
125
+ ${file.type === 'file' ? `
126
+ <button class="action-btn download-btn" data-filename="${file.name}" title="下载">
127
+ <i class="fas fa-download"></i>
128
+ </button>
129
+ ` : ''}
130
+ <button class="action-btn delete-btn" data-filename="${file.name}" data-type="${file.type}" title="删除">
131
+ <i class="fas fa-trash"></i>
132
+ </button>
133
+ </div>
134
+ </div>
135
+ `;
136
+ });
137
+
138
+ fileList.innerHTML = html;
139
+ }
140
+
141
+ // 渲染面包屑
142
+ function renderBreadcrumb(items) {
143
+ let html = '<a href="#" data-path=""><i class="fas fa-home"></i> 根目录</a>';
144
+
145
+ if (items && items.length > 0) {
146
+ items.forEach((item, index) => {
147
+ html += '<span class="separator"><i class="fas fa-chevron-right"></i></span>';
148
+ if (index === items.length - 1) {
149
+ html += `<span class="current">${item.name}</span>`;
150
+ } else {
151
+ html += `<a href="#" data-path="${item.path}">${item.name}</a>`;
152
+ }
153
+ });
154
+ }
155
+
156
+ breadcrumb.innerHTML = html;
157
+
158
+ // 绑定点击事件
159
+ breadcrumb.querySelectorAll('a').forEach(a => {
160
+ a.addEventListener('click', (e) => {
161
+ e.preventDefault();
162
+ const path = a.dataset.path;
163
+ currentPath = path;
164
+ loadFileList();
165
+ });
166
+ });
167
+ }
168
+
169
+ // 初始化事件监听
170
+ function initEventListeners() {
171
+ // 上传按钮
172
+ uploadBtn.addEventListener('click', () => {
173
+ fileInput.click();
174
+ });
175
+
176
+ // 文件选择
177
+ fileInput.addEventListener('change', (e) => {
178
+ if (e.target.files.length > 0) {
179
+ uploadFiles(e.target.files);
180
+ }
181
+ });
182
+
183
+ // 拖拽上传
184
+ uploadZone.addEventListener('dragover', (e) => {
185
+ e.preventDefault();
186
+ uploadZone.classList.add('dragover');
187
+ });
188
+
189
+ uploadZone.addEventListener('dragleave', () => {
190
+ uploadZone.classList.remove('dragover');
191
+ });
192
+
193
+ uploadZone.addEventListener('drop', (e) => {
194
+ e.preventDefault();
195
+ uploadZone.classList.remove('dragover');
196
+ if (e.dataTransfer.files.length > 0) {
197
+ uploadFiles(e.dataTransfer.files);
198
+ }
199
+ });
200
+
201
+ // 新建文件夹
202
+ mkdirBtn.addEventListener('click', () => {
203
+ mkdirModal.classList.add('show');
204
+ document.getElementById('folderNameInput').value = '';
205
+ document.getElementById('folderNameInput').focus();
206
+ });
207
+
208
+ document.getElementById('closeMkdirModal').addEventListener('click', () => {
209
+ mkdirModal.classList.remove('show');
210
+ });
211
+
212
+ document.getElementById('cancelMkdir').addEventListener('click', () => {
213
+ mkdirModal.classList.remove('show');
214
+ });
215
+
216
+ document.getElementById('confirmMkdir').addEventListener('click', createFolder);
217
+
218
+ document.getElementById('folderNameInput').addEventListener('keypress', (e) => {
219
+ if (e.key === 'Enter') {
220
+ createFolder();
221
+ }
222
+ });
223
+
224
+ // 刷新
225
+ refreshBtn.addEventListener('click', () => {
226
+ loadFileList();
227
+ showToast('已刷新', 'info');
228
+ });
229
+
230
+ // 删除确认
231
+ document.getElementById('closeDeleteModal').addEventListener('click', () => {
232
+ deleteModal.classList.remove('show');
233
+ });
234
+
235
+ document.getElementById('cancelDelete').addEventListener('click', () => {
236
+ deleteModal.classList.remove('show');
237
+ });
238
+
239
+ document.getElementById('confirmDelete').addEventListener('click', confirmDelete);
240
+
241
+ // 点击文件/文件夹
242
+ fileList.addEventListener('click', (e) => {
243
+ const fileItem = e.target.closest('.file-item');
244
+ if (!fileItem) return;
245
+
246
+ // 如果点击的是操作按钮,不处理
247
+ if (e.target.closest('.action-btn')) return;
248
+
249
+ const name = fileItem.dataset.name;
250
+ const type = fileItem.dataset.type;
251
+
252
+ if (type === 'directory') {
253
+ // 进入文件夹
254
+ currentPath = currentPath ? currentPath + '/' + name : name;
255
+ loadFileList();
256
+ }
257
+ });
258
+
259
+ // 下载按钮
260
+ fileList.addEventListener('click', (e) => {
261
+ const downloadBtn = e.target.closest('.download-btn');
262
+ if (downloadBtn) {
263
+ const filename = downloadBtn.dataset.filename;
264
+ downloadFile(filename);
265
+ }
266
+ });
267
+
268
+ // 删除按钮
269
+ fileList.addEventListener('click', (e) => {
270
+ const deleteBtn = e.target.closest('.delete-btn');
271
+ if (deleteBtn) {
272
+ const filename = deleteBtn.dataset.filename;
273
+ const type = deleteBtn.dataset.type;
274
+ showDeleteModal(filename, type);
275
+ }
276
+ });
277
+
278
+ // 点击模态框背景关闭
279
+ mkdirModal.addEventListener('click', (e) => {
280
+ if (e.target === mkdirModal) {
281
+ mkdirModal.classList.remove('show');
282
+ }
283
+ });
284
+
285
+ deleteModal.addEventListener('click', (e) => {
286
+ if (e.target === deleteModal) {
287
+ deleteModal.classList.remove('show');
288
+ }
289
+ });
290
+
291
+ // ESC 关闭模态框
292
+ document.addEventListener('keydown', (e) => {
293
+ if (e.key === 'Escape') {
294
+ mkdirModal.classList.remove('show');
295
+ deleteModal.classList.remove('show');
296
+ }
297
+ });
298
+ }
299
+
300
+ // 上传文件
301
+ function uploadFiles(files) {
302
+ if (files.length === 0) return;
303
+
304
+ showProgress();
305
+ let uploaded = 0;
306
+ let total = files.length;
307
+ let hasError = false;
308
+
309
+ Array.from(files).forEach((file) => {
310
+ const formData = new FormData();
311
+ formData.append('file', file);
312
+ formData.append('path', currentPath);
313
+ formData.append('token', token);
314
+
315
+ const xhr = new XMLHttpRequest();
316
+
317
+ xhr.upload.addEventListener('progress', (e) => {
318
+ if (e.lengthComputable) {
319
+ const percent = (e.loaded / e.total) * 100;
320
+ const overallPercent = ((uploaded + percent / 100) / total) * 100;
321
+ progressFill.style.width = overallPercent + '%';
322
+ progressText.textContent = '正在上传... (' + (uploaded + 1) + '/' + total + ')';
323
+ }
324
+ });
325
+
326
+ xhr.addEventListener('load', () => {
327
+ try {
328
+ const result = JSON.parse(xhr.responseText);
329
+ if (result.code !== 200) {
330
+ hasError = true;
331
+ showToast(result.msg || '上传失败', 'error');
332
+ }
333
+ } catch (e) {
334
+ hasError = true;
335
+ showToast('上传失败', 'error');
336
+ }
337
+
338
+ uploaded++;
339
+ if (uploaded >= total) {
340
+ setTimeout(() => {
341
+ hideProgress();
342
+ if (!hasError) {
343
+ showToast('上传成功', 'success');
344
+ }
345
+ loadFileList();
346
+ }, 300);
347
+ }
348
+ });
349
+
350
+ xhr.addEventListener('error', () => {
351
+ hasError = true;
352
+ uploaded++;
353
+ if (uploaded >= total) {
354
+ hideProgress();
355
+ showToast('上传失败', 'error');
356
+ }
357
+ });
358
+
359
+ xhr.open('POST', '/api/cloud/upload');
360
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token);
361
+ xhr.send(formData);
362
+ });
363
+
364
+ fileInput.value = '';
365
+ }
366
+
367
+ // 新建文件夹
368
+ function createFolder() {
369
+ const folderName = document.getElementById('folderNameInput').value.trim();
370
+ if (!folderName) {
371
+ showToast('请输入文件夹名称', 'error');
372
+ return;
373
+ }
374
+
375
+ fetch('/api/cloud/mkdir', {
376
+ method: 'POST',
377
+ headers: {
378
+ 'Content-Type': 'application/json',
379
+ 'Authorization': 'Bearer ' + token
380
+ },
381
+ body: JSON.stringify({ dirname: folderName, path: currentPath })
382
+ })
383
+ .then(res => res.json())
384
+ .then(data => {
385
+ if (data.code === 200) {
386
+ showToast('文件夹创建成功', 'success');
387
+ mkdirModal.classList.remove('show');
388
+ loadFileList();
389
+ } else {
390
+ showToast(data.msg || '创建失败', 'error');
391
+ }
392
+ })
393
+ .catch(() => {
394
+ showToast('创建失败', 'error');
395
+ });
396
+ }
397
+
398
+ // 显示删除确认框
399
+ function showDeleteModal(filename, type) {
400
+ document.getElementById('deleteFileName').textContent = filename;
401
+ deleteModal.dataset.filename = filename;
402
+ deleteModal.dataset.type = type;
403
+ deleteModal.classList.add('show');
404
+ }
405
+
406
+ // 确认删除
407
+ function confirmDelete() {
408
+ const filename = deleteModal.dataset.filename;
409
+ if (!filename) return;
410
+
411
+ fetch('/api/cloud/delete', {
412
+ method: 'POST',
413
+ headers: {
414
+ 'Content-Type': 'application/json',
415
+ 'Authorization': 'Bearer ' + token
416
+ },
417
+ body: JSON.stringify({ filename: filename, path: currentPath })
418
+ })
419
+ .then(res => res.json())
420
+ .then(data => {
421
+ if (data.code === 200) {
422
+ showToast('删除成功', 'success');
423
+ deleteModal.classList.remove('show');
424
+ loadFileList();
425
+ } else {
426
+ showToast(data.msg || '删除失败', 'error');
427
+ }
428
+ })
429
+ .catch(() => {
430
+ showToast('删除失败', 'error');
431
+ });
432
+ }
433
+
434
+ // 下载文件
435
+ function downloadFile(filename) {
436
+ const url = '/api/cloud/download?filename=' + encodeURIComponent(filename) + '&path=' + encodeURIComponent(currentPath) + '&token=' + encodeURIComponent(token);
437
+ window.location.href = url;
438
+ }
439
+
440
+ // 退出登录
441
+ function logout() {
442
+ localStorage.removeItem('token');
443
+ showToast('已退出登录', 'info');
444
+ }
445
+
446
+ // 显示进度条
447
+ function showProgress() {
448
+ uploadProgress.classList.add('show');
449
+ progressFill.style.width = '0%';
450
+ }
451
+
452
+ // 隐藏进度条
453
+ function hideProgress() {
454
+ uploadProgress.classList.remove('show');
455
+ }
456
+
457
+ // Toast 提示
458
+ let toastTimer = null;
459
+
460
+ function showToast(message, type) {
461
+ if (!type) type = 'info';
462
+
463
+ toastMessage.textContent = message;
464
+ toast.className = 'toast toast-' + type;
465
+
466
+ if (type === 'success') {
467
+ toastIcon.className = 'toast-icon fas fa-check-circle';
468
+ } else if (type === 'error') {
469
+ toastIcon.className = 'toast-icon fas fa-exclamation-circle';
470
+ } else {
471
+ toastIcon.className = 'toast-icon fas fa-info-circle';
472
+ }
473
+
474
+ toast.classList.add('show');
475
+
476
+ if (toastTimer) {
477
+ clearTimeout(toastTimer);
478
+ }
479
+
480
+ toastTimer = setTimeout(() => {
481
+ toast.classList.remove('show');
482
+ }, 3000);
483
+ }