@huyooo/file-explorer-frontend-vue 0.4.2

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.
Files changed (76) hide show
  1. package/dist/components/Breadcrumb.vue.d.ts +11 -0
  2. package/dist/components/Breadcrumb.vue.d.ts.map +1 -0
  3. package/dist/components/CompressDialog.vue.d.ts +16 -0
  4. package/dist/components/CompressDialog.vue.d.ts.map +1 -0
  5. package/dist/components/ContextMenu.vue.d.ts +18 -0
  6. package/dist/components/ContextMenu.vue.d.ts.map +1 -0
  7. package/dist/components/FileGrid.vue.d.ts +40 -0
  8. package/dist/components/FileGrid.vue.d.ts.map +1 -0
  9. package/dist/components/FileIcon.vue.d.ts +13 -0
  10. package/dist/components/FileIcon.vue.d.ts.map +1 -0
  11. package/dist/components/FileInfoDialog.vue.d.ts +14 -0
  12. package/dist/components/FileInfoDialog.vue.d.ts.map +1 -0
  13. package/dist/components/FileList.vue.d.ts +37 -0
  14. package/dist/components/FileList.vue.d.ts.map +1 -0
  15. package/dist/components/FileListView.vue.d.ts +43 -0
  16. package/dist/components/FileListView.vue.d.ts.map +1 -0
  17. package/dist/components/FileSidebar.vue.d.ts +17 -0
  18. package/dist/components/FileSidebar.vue.d.ts.map +1 -0
  19. package/dist/components/ProgressDialog.vue.d.ts +28 -0
  20. package/dist/components/ProgressDialog.vue.d.ts.map +1 -0
  21. package/dist/components/SortIndicator.vue.d.ts +6 -0
  22. package/dist/components/SortIndicator.vue.d.ts.map +1 -0
  23. package/dist/components/StatusBar.vue.d.ts +27 -0
  24. package/dist/components/StatusBar.vue.d.ts.map +1 -0
  25. package/dist/components/Toolbar.vue.d.ts +60 -0
  26. package/dist/components/Toolbar.vue.d.ts.map +1 -0
  27. package/dist/components/Window.vue.d.ts +65 -0
  28. package/dist/components/Window.vue.d.ts.map +1 -0
  29. package/dist/composables/useApplicationIcon.d.ts +16 -0
  30. package/dist/composables/useApplicationIcon.d.ts.map +1 -0
  31. package/dist/composables/useDragAndDrop.d.ts +14 -0
  32. package/dist/composables/useDragAndDrop.d.ts.map +1 -0
  33. package/dist/composables/useMediaPlayer.d.ts +24 -0
  34. package/dist/composables/useMediaPlayer.d.ts.map +1 -0
  35. package/dist/composables/useSelection.d.ts +15 -0
  36. package/dist/composables/useSelection.d.ts.map +1 -0
  37. package/dist/composables/useWindowDrag.d.ts +18 -0
  38. package/dist/composables/useWindowDrag.d.ts.map +1 -0
  39. package/dist/composables/useWindowResize.d.ts +12 -0
  40. package/dist/composables/useWindowResize.d.ts.map +1 -0
  41. package/dist/index.css +1 -0
  42. package/dist/index.d.ts +22 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +4051 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types/index.d.ts +268 -0
  47. package/dist/types/index.d.ts.map +1 -0
  48. package/dist/utils/fileTypeIcon.d.ts +6 -0
  49. package/dist/utils/fileTypeIcon.d.ts.map +1 -0
  50. package/dist/utils/folderTypeIcon.d.ts +14 -0
  51. package/dist/utils/folderTypeIcon.d.ts.map +1 -0
  52. package/package.json +55 -0
  53. package/src/components/Breadcrumb.vue +111 -0
  54. package/src/components/CompressDialog.vue +478 -0
  55. package/src/components/ContextMenu.vue +550 -0
  56. package/src/components/FileGrid.vue +504 -0
  57. package/src/components/FileIcon.vue +132 -0
  58. package/src/components/FileInfoDialog.vue +465 -0
  59. package/src/components/FileList.vue +421 -0
  60. package/src/components/FileListView.vue +321 -0
  61. package/src/components/FileSidebar.vue +158 -0
  62. package/src/components/ProgressDialog.vue +368 -0
  63. package/src/components/SortIndicator.vue +22 -0
  64. package/src/components/StatusBar.vue +43 -0
  65. package/src/components/Toolbar.vue +271 -0
  66. package/src/components/Window.vue +561 -0
  67. package/src/composables/useApplicationIcon.ts +79 -0
  68. package/src/composables/useDragAndDrop.ts +103 -0
  69. package/src/composables/useMediaPlayer.ts +174 -0
  70. package/src/composables/useSelection.ts +107 -0
  71. package/src/composables/useWindowDrag.ts +66 -0
  72. package/src/composables/useWindowResize.ts +134 -0
  73. package/src/index.ts +32 -0
  74. package/src/types/index.ts +273 -0
  75. package/src/utils/fileTypeIcon.ts +309 -0
  76. package/src/utils/folderTypeIcon.ts +132 -0
@@ -0,0 +1,504 @@
1
+ <template>
2
+ <div class="file-grid" @contextmenu.prevent="handleEmptyContextMenu">
3
+ <div
4
+ v-for="item in items"
5
+ :key="item.id"
6
+ :draggable="editingId !== item.id"
7
+ @dragstart="$emit('dragStart', $event, item)"
8
+ @dragover.prevent="$emit('dragOver', $event, item)"
9
+ @dragleave="$emit('dragLeave', $event)"
10
+ @drop.prevent="$emit('drop', $event, item)"
11
+ @click.stop="$emit('select', item, $event)"
12
+ @dblclick.stop="$emit('open', item)"
13
+ @contextmenu.prevent.stop="$emit('contextMenu', item, $event)"
14
+ :class="[
15
+ 'file-grid-item',
16
+ selectedIds.has(item.id) && editingId !== item.id
17
+ ? 'file-grid-item--selected'
18
+ : dragOverId === item.id
19
+ ? 'file-grid-item--drag-over'
20
+ : 'file-grid-item--normal'
21
+ ]"
22
+ >
23
+ <div class="file-grid-item-icon">
24
+ <!-- 应用程序图标 -->
25
+ <img
26
+ v-if="item.type === FileType.APPLICATION && getAppIconUrl?.(item)"
27
+ :src="getAppIconUrl(item)"
28
+ :alt="item.name"
29
+ :class="[
30
+ 'file-grid-item-thumbnail file-grid-item-thumbnail--application',
31
+ selectedIds.has(item.id) && editingId !== item.id ? 'file-grid-item-thumbnail--selected' : ''
32
+ ]"
33
+ />
34
+ <!-- 图片/视频缩略图 -->
35
+ <template v-else-if="hasThumbnail(item)">
36
+ <!-- 视频类型必须有缩略图,如果没有缩略图说明不应该被识别为视频,按普通文件处理 -->
37
+ <div v-if="item.type === FileType.VIDEO && item.thumbnailUrl" class="file-grid-item-thumbnail file-grid-item-thumbnail--video">
38
+ <img
39
+ :src="item.thumbnailUrl"
40
+ class="file-grid-item-thumbnail"
41
+ :alt="item.name"
42
+ @error="$emit('thumbnailError', item, $event)"
43
+ />
44
+ <div class="file-grid-item-video-play">
45
+ <div class="file-grid-item-video-play-icon" />
46
+ </div>
47
+ </div>
48
+ <!-- 图片:永远只显示缩略图,不显示原始 URL -->
49
+ <img
50
+ v-else-if="item.thumbnailUrl"
51
+ :src="item.thumbnailUrl"
52
+ :alt="item.name"
53
+ class="file-grid-item-thumbnail"
54
+ @error="$emit('thumbnailError', item, $event)"
55
+ />
56
+ </template>
57
+ <FileIcon
58
+ v-else
59
+ :type="item.type"
60
+ :name="item.name"
61
+ :size="48"
62
+ />
63
+ </div>
64
+
65
+ <div class="file-grid-item-name-wrapper">
66
+ <input
67
+ v-if="editingId === item.id"
68
+ type="text"
69
+ class="file-grid-item-rename-input"
70
+ :value="item.name"
71
+ @blur="handleRename(item, $event)"
72
+ @keydown.enter="handleEnterKey"
73
+ @keydown.escape="handleEscapeKey"
74
+ ref="renameInput"
75
+ autofocus
76
+ />
77
+ <span
78
+ v-else
79
+ @click.stop="$emit('nameClick', item, $event)"
80
+ :class="[
81
+ 'file-grid-item-name',
82
+ selectedIds.has(item.id) ? 'file-grid-item-name--selected' : ''
83
+ ]"
84
+ :title="item.name"
85
+ >
86
+ <template v-if="getFileNameParts(item).ext">
87
+ <span class="file-grid-item-name-base">{{ getFileNameParts(item).baseName }}</span>
88
+ <span class="file-grid-item-name-ext">{{ getFileNameParts(item).ext }}</span>
89
+ </template>
90
+ <template v-else>{{ item.name }}</template>
91
+ </span>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </template>
96
+
97
+ <script setup lang="ts">
98
+ import { computed } from 'vue';
99
+ import type { FileItem } from '../types';
100
+ import { FileType } from '../types';
101
+ import FileIcon from './FileIcon.vue';
102
+
103
+ /**
104
+ * 分离文件名和扩展名
105
+ */
106
+ function splitFileName(name: string, isFolder: boolean): { baseName: string; ext: string } {
107
+ if (isFolder) {
108
+ return { baseName: name, ext: '' };
109
+ }
110
+ const lastDot = name.lastIndexOf('.');
111
+ if (lastDot <= 0) {
112
+ return { baseName: name, ext: '' };
113
+ }
114
+ return {
115
+ baseName: name.substring(0, lastDot),
116
+ ext: name.substring(lastDot),
117
+ };
118
+ }
119
+
120
+ /**
121
+ * 获取文件名各部分(带缓存)
122
+ */
123
+ const fileNameCache = new Map<string, { baseName: string; ext: string }>();
124
+ function getFileNameParts(item: FileItem) {
125
+ const key = `${item.id}-${item.name}`;
126
+ if (!fileNameCache.has(key)) {
127
+ fileNameCache.set(key, splitFileName(item.name, item.type === FileType.FOLDER));
128
+ }
129
+ return fileNameCache.get(key)!;
130
+ }
131
+
132
+ interface Props {
133
+ items: FileItem[];
134
+ selectedIds: Set<string>;
135
+ editingId?: string | null;
136
+ dragOverId?: string | null;
137
+ /** 获取应用程序图标 URL 的函数 */
138
+ getAppIconUrl?: (item: FileItem) => string | undefined;
139
+ }
140
+
141
+ const props = defineProps<Props>();
142
+
143
+ const emit = defineEmits<{
144
+ select: [item: FileItem, e: MouseEvent];
145
+ open: [item: FileItem];
146
+ contextMenu: [item: FileItem, e: MouseEvent];
147
+ contextMenuEmpty: [e: MouseEvent];
148
+ nameClick: [item: FileItem, e: MouseEvent];
149
+ rename: [item: FileItem, newName: string];
150
+ renameCancel: [item: FileItem];
151
+ dragStart: [e: DragEvent, item: FileItem];
152
+ dragOver: [e: DragEvent, item: FileItem];
153
+ dragLeave: [e: DragEvent];
154
+ drop: [e: DragEvent, item: FileItem];
155
+ thumbnailError: [item: FileItem, e: Event];
156
+ }>();
157
+
158
+ /**
159
+ * 空白处右键菜单
160
+ */
161
+ const handleEmptyContextMenu = (e: MouseEvent) => {
162
+ // 检查是否点击了文件项(文件项的 contextmenu 有 .stop,不会到这里)
163
+ const target = e.target as HTMLElement;
164
+ // 如果点击的不是文件项内部元素,则触发空白处菜单
165
+ if (!target.closest('.file-grid-item')) {
166
+ emit('contextMenuEmpty', e);
167
+ }
168
+ };
169
+
170
+ /**
171
+ * 判断是否有缩略图或应用程序图标
172
+ */
173
+ const hasThumbnail = (item: FileItem): boolean => {
174
+ // 应用程序图标
175
+ if (item.type === FileType.APPLICATION && props.getAppIconUrl?.(item)) {
176
+ return true;
177
+ }
178
+ // 图片:必须有缩略图才显示,永远不显示原始 URL
179
+ if (item.type === FileType.IMAGE) {
180
+ return !!item.thumbnailUrl;
181
+ }
182
+ // 视频:必须有缩略图才显示,如果没有缩略图说明不应该被识别为视频
183
+ if (item.type === FileType.VIDEO) {
184
+ return !!item.thumbnailUrl;
185
+ }
186
+ return false;
187
+ };
188
+
189
+ /**
190
+ * 视频悬停播放
191
+ */
192
+ const handleVideoHover = (e: Event, isHover: boolean) => {
193
+ const video = e.target as HTMLVideoElement;
194
+ if (isHover) {
195
+ video.play().catch(() => {});
196
+ } else {
197
+ video.pause();
198
+ video.currentTime = 0;
199
+ }
200
+ };
201
+
202
+ /**
203
+ * 处理重命名(blur 事件)
204
+ */
205
+ const handleRename = (item: FileItem, e: Event) => {
206
+ const input = e.target as HTMLInputElement;
207
+ const newName = input.value.trim();
208
+ if (newName && newName !== item.name) {
209
+ emit('rename', item, newName);
210
+ } else {
211
+ emit('renameCancel', item);
212
+ }
213
+ };
214
+
215
+ /**
216
+ * Enter 键保存
217
+ */
218
+ const handleEnterKey = (e: KeyboardEvent) => {
219
+ (e.target as HTMLInputElement).blur();
220
+ };
221
+
222
+ /**
223
+ * Escape 键取消
224
+ */
225
+ const handleEscapeKey = (e: KeyboardEvent) => {
226
+ const input = e.target as HTMLInputElement;
227
+ // 恢复原始值,这样 blur 时会触发 renameCancel
228
+ const item = props.items.find(i => i.id === props.editingId);
229
+ if (item) {
230
+ input.value = item.name;
231
+ }
232
+ input.blur();
233
+ };
234
+ </script>
235
+
236
+ <style scoped>
237
+ /**
238
+ * FileGrid - 网格视图样式
239
+ * 参考 macOS Finder 和 Windows Explorer 的设计
240
+ */
241
+
242
+ .file-grid {
243
+ display: grid;
244
+ /* macOS Finder 默认约 90-100px */
245
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
246
+ gap: 6px;
247
+ padding: 12px;
248
+ align-content: start;
249
+ grid-auto-rows: min-content;
250
+ overflow-y: auto;
251
+ overflow-x: hidden;
252
+ }
253
+
254
+ /* 网格项 - 更紧凑的布局 */
255
+ .file-grid-item {
256
+ display: flex;
257
+ flex-direction: column;
258
+ align-items: center;
259
+ padding: 4px;
260
+ border-radius: 6px;
261
+ cursor: pointer;
262
+ border: 1px solid transparent;
263
+ transition: background-color 120ms ease, border-color 120ms ease;
264
+ position: relative;
265
+ overflow: hidden;
266
+ min-width: 0;
267
+ width: 100%;
268
+ }
269
+
270
+ /* 拖拽时的鼠标样式 */
271
+ .file-grid-item[draggable="true"] {
272
+ cursor: grab;
273
+ }
274
+
275
+ .file-grid-item:active {
276
+ cursor: grabbing;
277
+ }
278
+
279
+ /* 悬停效果 - 类似 macOS */
280
+ .file-grid-item--normal:hover {
281
+ background: rgba(0, 0, 0, 0.04);
282
+ }
283
+
284
+ /* 选中状态 - macOS 风格的蓝色高亮 */
285
+ .file-grid-item--selected {
286
+ background: rgba(0, 122, 255, 0.12);
287
+ border-color: rgba(0, 122, 255, 0.2);
288
+ }
289
+
290
+ .file-grid-item--selected:hover {
291
+ background: rgba(0, 122, 255, 0.16);
292
+ }
293
+
294
+ /* 拖拽悬停 */
295
+ .file-grid-item--drag-over {
296
+ background: rgba(0, 122, 255, 0.2);
297
+ border-color: rgba(0, 122, 255, 0.4);
298
+ }
299
+
300
+ /* 图标/缩略图容器 - 统一尺寸 */
301
+ .file-grid-item-icon {
302
+ position: relative;
303
+ pointer-events: none;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: center;
307
+ /* 统一 48x48 尺寸,更紧凑 */
308
+ width: 48px;
309
+ height: 48px;
310
+ flex-shrink: 0;
311
+ }
312
+
313
+ /* 缩略图样式 - 统一尺寸和圆角 */
314
+ /* 缩略图保持原图比例,使用 object-fit: cover 填充固定尺寸容器 */
315
+ .file-grid-item-thumbnail {
316
+ width: 48px;
317
+ height: 48px;
318
+ object-fit: cover;
319
+ object-position: center;
320
+ border-radius: 4px;
321
+ /* 轻微阴影增加层次感 */
322
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
323
+ background: #f5f5f5;
324
+ /* 确保图片能作为加载占位符,平滑过渡到原图 */
325
+ display: block;
326
+ }
327
+
328
+ /* 应用程序图标 - 无背景 */
329
+ .file-grid-item-thumbnail--application {
330
+ object-fit: contain;
331
+ background: transparent;
332
+ box-shadow: none;
333
+ border-radius: 10px;
334
+ }
335
+
336
+ .file-grid-item-thumbnail--selected {
337
+ box-shadow: 0 2px 6px rgba(0, 122, 255, 0.2), 0 0 0 2px rgba(0, 122, 255, 0.3);
338
+ }
339
+
340
+ /* 视频缩略图容器 */
341
+ .file-grid-item-thumbnail--video {
342
+ position: relative;
343
+ border-radius: 4px;
344
+ overflow: hidden;
345
+ background: #000;
346
+ width: 48px;
347
+ height: 48px;
348
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
349
+ display: flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+ }
353
+
354
+ /* 视频缩略图内的图片 - 保持比例,填充容器 */
355
+ .file-grid-item-thumbnail--video > img {
356
+ width: 100%;
357
+ height: 100%;
358
+ object-fit: cover;
359
+ object-position: center;
360
+ display: block;
361
+ }
362
+
363
+ /* 视频播放图标 */
364
+ .file-grid-item-video-play {
365
+ position: absolute;
366
+ inset: 0;
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ pointer-events: none;
371
+ opacity: 0.9;
372
+ transition: opacity 150ms;
373
+ }
374
+
375
+ .file-grid-item-thumbnail--video:hover .file-grid-item-video-play {
376
+ opacity: 0;
377
+ }
378
+
379
+ .file-grid-item-video-play-icon {
380
+ width: 20px;
381
+ height: 20px;
382
+ border-radius: 50%;
383
+ background: rgba(0, 0, 0, 0.5);
384
+ backdrop-filter: blur(4px);
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ }
389
+
390
+ .file-grid-item-video-play-icon::before {
391
+ content: '';
392
+ width: 0;
393
+ height: 0;
394
+ border-top: 4px solid transparent;
395
+ border-left: 7px solid white;
396
+ border-bottom: 4px solid transparent;
397
+ margin-left: 2px;
398
+ }
399
+
400
+ /* 文件名容器 */
401
+ .file-grid-item-name-wrapper {
402
+ display: flex;
403
+ align-items: flex-start;
404
+ justify-content: center;
405
+ width: 100%;
406
+ min-height: 0;
407
+ }
408
+
409
+ /* 文件名 - macOS 风格,最多2行,保留扩展名 */
410
+ .file-grid-item-name {
411
+ font-size: 12px;
412
+ line-height: 1.3;
413
+ text-align: center;
414
+ padding: 2px;
415
+ border-radius: 3px;
416
+ user-select: none;
417
+ cursor: default;
418
+ transition: color 100ms;
419
+ color: #1d1d1f;
420
+ width: 100%;
421
+ /* 使用 flexbox 布局,让扩展名始终显示 */
422
+ display: flex;
423
+ flex-direction: column;
424
+ align-items: center;
425
+ justify-content: flex-start;
426
+ min-height: 0;
427
+ }
428
+
429
+ /* 文件名主体部分 - 可换行,最多1行 */
430
+ .file-grid-item-name-base {
431
+ display: -webkit-box;
432
+ -webkit-line-clamp: 1;
433
+ -webkit-box-orient: vertical;
434
+ overflow: hidden;
435
+ word-break: break-word;
436
+ text-overflow: ellipsis;
437
+ width: 100%;
438
+ text-align: center;
439
+ }
440
+
441
+ /* 扩展名部分 - 始终显示,不换行 */
442
+ .file-grid-item-name-ext {
443
+ display: block;
444
+ white-space: nowrap;
445
+ flex-shrink: 0;
446
+ text-align: center;
447
+ }
448
+
449
+ /* 选中后点击文件名可编辑 */
450
+ .file-grid-item-name--selected {
451
+ cursor: text;
452
+ }
453
+
454
+ /* 重命名输入框 */
455
+ .file-grid-item-rename-input {
456
+ width: 100%;
457
+ font-size: 11px;
458
+ line-height: 1.3;
459
+ text-align: center;
460
+ padding: 2px 4px;
461
+ border: 1px solid #007aff;
462
+ border-radius: 3px;
463
+ outline: none;
464
+ box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2);
465
+ background: white;
466
+ cursor: text;
467
+ }
468
+
469
+ /* 深色模式支持 */
470
+ @media (prefers-color-scheme: dark) {
471
+ .file-grid-item--normal:hover {
472
+ background: rgba(255, 255, 255, 0.06);
473
+ }
474
+
475
+ .file-grid-item--selected {
476
+ background: rgba(10, 132, 255, 0.25);
477
+ border-color: rgba(10, 132, 255, 0.3);
478
+ }
479
+
480
+ .file-grid-item--selected:hover {
481
+ background: rgba(10, 132, 255, 0.3);
482
+ }
483
+
484
+ .file-grid-item--drag-over {
485
+ background: rgba(10, 132, 255, 0.35);
486
+ border-color: rgba(10, 132, 255, 0.5);
487
+ }
488
+
489
+ .file-grid-item-thumbnail {
490
+ background: #2c2c2e;
491
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.05);
492
+ }
493
+
494
+ .file-grid-item-name {
495
+ color: #f5f5f7;
496
+ }
497
+
498
+ .file-grid-item-rename-input {
499
+ background: #1c1c1e;
500
+ border-color: #0a84ff;
501
+ color: #f5f5f7;
502
+ }
503
+ }
504
+ </style>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div :class="className">
3
+ <Icon :icon="iconName" :width="size" :height="size" :class="iconClass" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue';
9
+ import { Icon } from '@iconify/vue';
10
+ import { FileType } from '../types';
11
+ import { getFileTypeIcon } from '../utils/fileTypeIcon';
12
+ import { getFolderTypeIcon } from '../utils/folderTypeIcon';
13
+
14
+ interface Props {
15
+ type: FileType;
16
+ name?: string;
17
+ className?: string;
18
+ size?: number;
19
+ }
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ className: '',
23
+ size: 24
24
+ });
25
+
26
+ const iconName = computed(() => {
27
+ if (props.type === FileType.FOLDER) {
28
+ // 文件夹:先查特定类型图标,否则用默认 folder
29
+ const folderIcon = props.name ? getFolderTypeIcon(props.name) : undefined;
30
+ return folderIcon ?? 'flat-color-icons:folder';
31
+ }
32
+
33
+ // 如果有文件名,使用 material-icon-theme 图标映射(传递 type 作为兜底)
34
+ if (props.name) {
35
+ return getFileTypeIcon(props.name, props.type);
36
+ }
37
+
38
+ // 回退逻辑(没有文件名时)
39
+ switch (props.type) {
40
+ case FileType.IMAGE:
41
+ return 'material-icon-theme:image';
42
+ case FileType.TEXT:
43
+ return 'material-icon-theme:document';
44
+ case FileType.CODE:
45
+ return 'material-icon-theme:javascript';
46
+ case FileType.MUSIC:
47
+ return 'material-icon-theme:audio';
48
+ case FileType.VIDEO:
49
+ return 'material-icon-theme:video';
50
+ case FileType.PDF:
51
+ return 'material-icon-theme:pdf';
52
+ case FileType.DOCUMENT:
53
+ return 'material-icon-theme:word';
54
+ case FileType.APPLICATION:
55
+ return 'material-icon-theme:exe';
56
+ case FileType.ARCHIVE:
57
+ return 'material-icon-theme:zip';
58
+ default:
59
+ return 'material-icon-theme:document';
60
+ }
61
+ });
62
+
63
+ const iconClass = computed(() => {
64
+ const base = 'file-icon';
65
+ switch (props.type) {
66
+ case FileType.FOLDER:
67
+ return `${base} file-icon--folder`;
68
+ case FileType.IMAGE:
69
+ return `${base} file-icon--image`;
70
+ case FileType.TEXT:
71
+ return `${base} file-icon--text`;
72
+ case FileType.CODE:
73
+ return `${base} file-icon--code`;
74
+ case FileType.MUSIC:
75
+ return `${base} file-icon--music`;
76
+ case FileType.VIDEO:
77
+ return `${base} file-icon--video`;
78
+ case FileType.PDF:
79
+ case FileType.DOCUMENT:
80
+ return `${base} file-icon--pdf`;
81
+ case FileType.APPLICATION:
82
+ return `${base} file-icon--application`;
83
+ case FileType.ARCHIVE:
84
+ return `${base} file-icon--archive`;
85
+ default:
86
+ return `${base} file-icon--default`;
87
+ }
88
+ });
89
+ </script>
90
+
91
+ <style scoped>
92
+ .file-icon--folder {
93
+ color: rgb(96, 165, 250);
94
+ fill: rgb(96, 165, 250);
95
+ }
96
+
97
+ .file-icon--image {
98
+ color: rgb(168, 85, 247);
99
+ }
100
+
101
+ .file-icon--text {
102
+ color: rgb(107, 114, 128);
103
+ }
104
+
105
+ .file-icon--code {
106
+ color: rgb(34, 197, 94);
107
+ }
108
+
109
+ .file-icon--music {
110
+ color: rgb(248, 113, 113);
111
+ }
112
+
113
+ .file-icon--video {
114
+ color: rgb(37, 99, 235);
115
+ }
116
+
117
+ .file-icon--pdf {
118
+ color: rgb(220, 38, 38);
119
+ }
120
+
121
+ .file-icon--application {
122
+ color: rgb(139, 92, 246);
123
+ }
124
+
125
+ .file-icon--archive {
126
+ color: rgb(234, 179, 8);
127
+ }
128
+
129
+ .file-icon--default {
130
+ color: rgb(156, 163, 175);
131
+ }
132
+ </style>