@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.
- package/dist/components/Breadcrumb.vue.d.ts +11 -0
- package/dist/components/Breadcrumb.vue.d.ts.map +1 -0
- package/dist/components/CompressDialog.vue.d.ts +16 -0
- package/dist/components/CompressDialog.vue.d.ts.map +1 -0
- package/dist/components/ContextMenu.vue.d.ts +18 -0
- package/dist/components/ContextMenu.vue.d.ts.map +1 -0
- package/dist/components/FileGrid.vue.d.ts +40 -0
- package/dist/components/FileGrid.vue.d.ts.map +1 -0
- package/dist/components/FileIcon.vue.d.ts +13 -0
- package/dist/components/FileIcon.vue.d.ts.map +1 -0
- package/dist/components/FileInfoDialog.vue.d.ts +14 -0
- package/dist/components/FileInfoDialog.vue.d.ts.map +1 -0
- package/dist/components/FileList.vue.d.ts +37 -0
- package/dist/components/FileList.vue.d.ts.map +1 -0
- package/dist/components/FileListView.vue.d.ts +43 -0
- package/dist/components/FileListView.vue.d.ts.map +1 -0
- package/dist/components/FileSidebar.vue.d.ts +17 -0
- package/dist/components/FileSidebar.vue.d.ts.map +1 -0
- package/dist/components/ProgressDialog.vue.d.ts +28 -0
- package/dist/components/ProgressDialog.vue.d.ts.map +1 -0
- package/dist/components/SortIndicator.vue.d.ts +6 -0
- package/dist/components/SortIndicator.vue.d.ts.map +1 -0
- package/dist/components/StatusBar.vue.d.ts +27 -0
- package/dist/components/StatusBar.vue.d.ts.map +1 -0
- package/dist/components/Toolbar.vue.d.ts +60 -0
- package/dist/components/Toolbar.vue.d.ts.map +1 -0
- package/dist/components/Window.vue.d.ts +65 -0
- package/dist/components/Window.vue.d.ts.map +1 -0
- package/dist/composables/useApplicationIcon.d.ts +16 -0
- package/dist/composables/useApplicationIcon.d.ts.map +1 -0
- package/dist/composables/useDragAndDrop.d.ts +14 -0
- package/dist/composables/useDragAndDrop.d.ts.map +1 -0
- package/dist/composables/useMediaPlayer.d.ts +24 -0
- package/dist/composables/useMediaPlayer.d.ts.map +1 -0
- package/dist/composables/useSelection.d.ts +15 -0
- package/dist/composables/useSelection.d.ts.map +1 -0
- package/dist/composables/useWindowDrag.d.ts +18 -0
- package/dist/composables/useWindowDrag.d.ts.map +1 -0
- package/dist/composables/useWindowResize.d.ts +12 -0
- package/dist/composables/useWindowResize.d.ts.map +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4051 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +268 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/fileTypeIcon.d.ts +6 -0
- package/dist/utils/fileTypeIcon.d.ts.map +1 -0
- package/dist/utils/folderTypeIcon.d.ts +14 -0
- package/dist/utils/folderTypeIcon.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/components/Breadcrumb.vue +111 -0
- package/src/components/CompressDialog.vue +478 -0
- package/src/components/ContextMenu.vue +550 -0
- package/src/components/FileGrid.vue +504 -0
- package/src/components/FileIcon.vue +132 -0
- package/src/components/FileInfoDialog.vue +465 -0
- package/src/components/FileList.vue +421 -0
- package/src/components/FileListView.vue +321 -0
- package/src/components/FileSidebar.vue +158 -0
- package/src/components/ProgressDialog.vue +368 -0
- package/src/components/SortIndicator.vue +22 -0
- package/src/components/StatusBar.vue +43 -0
- package/src/components/Toolbar.vue +271 -0
- package/src/components/Window.vue +561 -0
- package/src/composables/useApplicationIcon.ts +79 -0
- package/src/composables/useDragAndDrop.ts +103 -0
- package/src/composables/useMediaPlayer.ts +174 -0
- package/src/composables/useSelection.ts +107 -0
- package/src/composables/useWindowDrag.ts +66 -0
- package/src/composables/useWindowResize.ts +134 -0
- package/src/index.ts +32 -0
- package/src/types/index.ts +273 -0
- package/src/utils/fileTypeIcon.ts +309 -0
- package/src/utils/folderTypeIcon.ts +132 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import type { FileItem } from '../types';
|
|
3
|
+
import { FileType } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 拖拽操作管理
|
|
7
|
+
*/
|
|
8
|
+
export function useDragAndDrop(
|
|
9
|
+
getSelectedIds: () => Set<string>,
|
|
10
|
+
onSelect: (id: string, multi: boolean, range: boolean) => void,
|
|
11
|
+
onMove: (targetFolderId: string, itemIds: Set<string>) => void
|
|
12
|
+
) {
|
|
13
|
+
const dragOverId = ref<string | null>(null);
|
|
14
|
+
const isDragging = ref(false);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 开始拖拽
|
|
18
|
+
*/
|
|
19
|
+
const handleDragStart = (e: DragEvent, itemId: string) => {
|
|
20
|
+
if (!e.dataTransfer) return;
|
|
21
|
+
|
|
22
|
+
const selectedIds = getSelectedIds();
|
|
23
|
+
|
|
24
|
+
// 如果拖拽的项目未被选中,先选中它
|
|
25
|
+
if (!selectedIds.has(itemId)) {
|
|
26
|
+
onSelect(itemId, false, false);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 设置拖拽数据
|
|
30
|
+
const draggedIds = selectedIds.has(itemId) ? selectedIds : new Set([itemId]);
|
|
31
|
+
e.dataTransfer.setData('text/plain', JSON.stringify([...draggedIds]));
|
|
32
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
33
|
+
|
|
34
|
+
isDragging.value = true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 拖拽经过
|
|
39
|
+
*/
|
|
40
|
+
const handleDragOver = (e: DragEvent, item: FileItem) => {
|
|
41
|
+
if (!isDragging.value) return;
|
|
42
|
+
|
|
43
|
+
// 只有文件夹可以作为放置目标
|
|
44
|
+
if (item.type === FileType.FOLDER) {
|
|
45
|
+
const selectedIds = getSelectedIds();
|
|
46
|
+
// 不能拖拽到自己身上
|
|
47
|
+
if (!selectedIds.has(item.id)) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
dragOverId.value = item.id;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 拖拽离开
|
|
56
|
+
*/
|
|
57
|
+
const handleDragLeave = () => {
|
|
58
|
+
dragOverId.value = null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 放置
|
|
63
|
+
*/
|
|
64
|
+
const handleDrop = (e: DragEvent, targetItem: FileItem) => {
|
|
65
|
+
dragOverId.value = null;
|
|
66
|
+
isDragging.value = false;
|
|
67
|
+
|
|
68
|
+
if (!e.dataTransfer || targetItem.type !== FileType.FOLDER) return;
|
|
69
|
+
|
|
70
|
+
const data = e.dataTransfer.getData('text/plain');
|
|
71
|
+
if (!data) return;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const draggedIds: string[] = JSON.parse(data);
|
|
75
|
+
const itemIds = new Set(draggedIds);
|
|
76
|
+
|
|
77
|
+
// 不能移动到自己
|
|
78
|
+
if (itemIds.has(targetItem.id)) return;
|
|
79
|
+
|
|
80
|
+
onMove(targetItem.id, itemIds);
|
|
81
|
+
} catch {
|
|
82
|
+
// 忽略解析错误
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 拖拽结束
|
|
88
|
+
*/
|
|
89
|
+
const handleDragEnd = () => {
|
|
90
|
+
dragOverId.value = null;
|
|
91
|
+
isDragging.value = false;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
dragOverId,
|
|
96
|
+
isDragging,
|
|
97
|
+
handleDragStart,
|
|
98
|
+
handleDragOver,
|
|
99
|
+
handleDragLeave,
|
|
100
|
+
handleDrop,
|
|
101
|
+
handleDragEnd
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ref, nextTick } from 'vue';
|
|
2
|
+
import { FileType } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 媒体播放器功能管理
|
|
6
|
+
*/
|
|
7
|
+
export function useMediaPlayer(
|
|
8
|
+
mediaType: FileType,
|
|
9
|
+
mediaRef: () => HTMLVideoElement | HTMLAudioElement | null
|
|
10
|
+
) {
|
|
11
|
+
const isPlaying = ref(false);
|
|
12
|
+
const progress = ref(0);
|
|
13
|
+
const currentTime = ref(0);
|
|
14
|
+
const duration = ref(0);
|
|
15
|
+
const isMuted = ref(false);
|
|
16
|
+
const volume = ref(1);
|
|
17
|
+
const lastVolume = ref(1);
|
|
18
|
+
const showControls = ref(false);
|
|
19
|
+
|
|
20
|
+
const isAudio = mediaType === FileType.MUSIC;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 更新播放进度
|
|
24
|
+
*/
|
|
25
|
+
const updateProgress = () => {
|
|
26
|
+
const media = mediaRef();
|
|
27
|
+
if (media) {
|
|
28
|
+
const curr = media.currentTime;
|
|
29
|
+
const dur = media.duration;
|
|
30
|
+
currentTime.value = curr;
|
|
31
|
+
if (dur && !isNaN(dur)) {
|
|
32
|
+
duration.value = dur;
|
|
33
|
+
progress.value = (curr / dur) * 100;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 切换播放/暂停
|
|
40
|
+
*/
|
|
41
|
+
const togglePlay = () => {
|
|
42
|
+
const media = mediaRef();
|
|
43
|
+
if (media) {
|
|
44
|
+
if (isPlaying.value) {
|
|
45
|
+
media.pause();
|
|
46
|
+
isPlaying.value = false;
|
|
47
|
+
} else {
|
|
48
|
+
media.play();
|
|
49
|
+
isPlaying.value = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 切换静音
|
|
56
|
+
*/
|
|
57
|
+
const toggleMute = () => {
|
|
58
|
+
const media = mediaRef();
|
|
59
|
+
if (media) {
|
|
60
|
+
if (isMuted.value) {
|
|
61
|
+
const volToRestore = lastVolume.value || 1;
|
|
62
|
+
media.volume = volToRestore;
|
|
63
|
+
media.muted = false;
|
|
64
|
+
volume.value = volToRestore;
|
|
65
|
+
isMuted.value = false;
|
|
66
|
+
} else {
|
|
67
|
+
lastVolume.value = volume.value;
|
|
68
|
+
media.volume = 0;
|
|
69
|
+
media.muted = true;
|
|
70
|
+
volume.value = 0;
|
|
71
|
+
isMuted.value = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 音量变化
|
|
78
|
+
*/
|
|
79
|
+
const handleVolumeChange = (val: number) => {
|
|
80
|
+
volume.value = val;
|
|
81
|
+
const media = mediaRef();
|
|
82
|
+
if (media) {
|
|
83
|
+
media.volume = val;
|
|
84
|
+
if (val === 0) {
|
|
85
|
+
isMuted.value = true;
|
|
86
|
+
media.muted = true;
|
|
87
|
+
} else {
|
|
88
|
+
isMuted.value = false;
|
|
89
|
+
media.muted = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 跳转到指定时间
|
|
96
|
+
*/
|
|
97
|
+
const seekTo = (time: number) => {
|
|
98
|
+
const media = mediaRef();
|
|
99
|
+
if (media && duration.value) {
|
|
100
|
+
media.currentTime = time;
|
|
101
|
+
currentTime.value = time;
|
|
102
|
+
progress.value = (time / duration.value) * 100;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 格式化时间显示
|
|
108
|
+
*/
|
|
109
|
+
const formatTime = (time: number) => {
|
|
110
|
+
if (isNaN(time)) return '0:00';
|
|
111
|
+
const minutes = Math.floor(time / 60);
|
|
112
|
+
const seconds = Math.floor(time % 60);
|
|
113
|
+
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 自动播放
|
|
118
|
+
*/
|
|
119
|
+
const autoPlay = () => {
|
|
120
|
+
nextTick(() => {
|
|
121
|
+
const media = mediaRef();
|
|
122
|
+
if ((mediaType === FileType.VIDEO || isAudio) && media) {
|
|
123
|
+
media.volume = volume.value;
|
|
124
|
+
media.play().catch(() => {});
|
|
125
|
+
isPlaying.value = true;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 监听媒体事件
|
|
132
|
+
*/
|
|
133
|
+
const setupMediaListeners = () => {
|
|
134
|
+
const media = mediaRef();
|
|
135
|
+
if (media) {
|
|
136
|
+
media.addEventListener('timeupdate', updateProgress);
|
|
137
|
+
media.addEventListener('loadedmetadata', updateProgress);
|
|
138
|
+
media.addEventListener('ended', () => {
|
|
139
|
+
isPlaying.value = false;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 清理媒体事件监听
|
|
146
|
+
*/
|
|
147
|
+
const cleanupMediaListeners = () => {
|
|
148
|
+
const media = mediaRef();
|
|
149
|
+
if (media) {
|
|
150
|
+
media.removeEventListener('timeupdate', updateProgress);
|
|
151
|
+
media.removeEventListener('loadedmetadata', updateProgress);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
isPlaying,
|
|
157
|
+
progress,
|
|
158
|
+
currentTime,
|
|
159
|
+
duration,
|
|
160
|
+
isMuted,
|
|
161
|
+
volume,
|
|
162
|
+
showControls,
|
|
163
|
+
isAudio,
|
|
164
|
+
togglePlay,
|
|
165
|
+
toggleMute,
|
|
166
|
+
handleVolumeChange,
|
|
167
|
+
seekTo,
|
|
168
|
+
formatTime,
|
|
169
|
+
autoPlay,
|
|
170
|
+
setupMediaListeners,
|
|
171
|
+
cleanupMediaListeners,
|
|
172
|
+
updateProgress
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import type { FileItem } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 文件选择状态管理
|
|
6
|
+
*/
|
|
7
|
+
export function useSelection() {
|
|
8
|
+
const selectedIds = ref<Set<string>>(new Set());
|
|
9
|
+
const lastSelectedId = ref<string | null>(null);
|
|
10
|
+
const editingId = ref<string | null>(null);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 清除选择
|
|
14
|
+
*/
|
|
15
|
+
const clearSelection = () => {
|
|
16
|
+
selectedIds.value = new Set();
|
|
17
|
+
lastSelectedId.value = null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 选择项目
|
|
22
|
+
* @param id 项目 ID(null 表示清除选择)
|
|
23
|
+
* @param items 当前显示的项目列表
|
|
24
|
+
* @param multi 是否多选(Cmd/Ctrl)
|
|
25
|
+
* @param range 是否范围选择(Shift)
|
|
26
|
+
*/
|
|
27
|
+
const selectItem = (
|
|
28
|
+
id: string | null,
|
|
29
|
+
items: FileItem[],
|
|
30
|
+
multi: boolean = false,
|
|
31
|
+
range: boolean = false
|
|
32
|
+
) => {
|
|
33
|
+
if (!id) {
|
|
34
|
+
clearSelection();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 范围选择
|
|
39
|
+
if (range && lastSelectedId.value) {
|
|
40
|
+
const indexA = items.findIndex(i => i.id === lastSelectedId.value);
|
|
41
|
+
const indexB = items.findIndex(i => i.id === id);
|
|
42
|
+
|
|
43
|
+
if (indexA !== -1 && indexB !== -1) {
|
|
44
|
+
const start = Math.min(indexA, indexB);
|
|
45
|
+
const end = Math.max(indexA, indexB);
|
|
46
|
+
const newSet = new Set(multi ? selectedIds.value : []);
|
|
47
|
+
for (let i = start; i <= end; i++) {
|
|
48
|
+
const item = items[i];
|
|
49
|
+
if (item) {
|
|
50
|
+
newSet.add(item.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
selectedIds.value = newSet;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 多选切换
|
|
59
|
+
if (multi) {
|
|
60
|
+
const newSet = new Set(selectedIds.value);
|
|
61
|
+
if (newSet.has(id)) {
|
|
62
|
+
newSet.delete(id);
|
|
63
|
+
} else {
|
|
64
|
+
newSet.add(id);
|
|
65
|
+
}
|
|
66
|
+
lastSelectedId.value = id;
|
|
67
|
+
selectedIds.value = newSet;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 单选
|
|
72
|
+
lastSelectedId.value = id;
|
|
73
|
+
selectedIds.value = new Set([id]);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 全选
|
|
78
|
+
*/
|
|
79
|
+
const selectAll = (items: FileItem[]) => {
|
|
80
|
+
selectedIds.value = new Set(items.map(i => i.id));
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 设置编辑状态
|
|
85
|
+
*/
|
|
86
|
+
const setEditing = (id: string | null) => {
|
|
87
|
+
editingId.value = id;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 检查是否选中
|
|
92
|
+
*/
|
|
93
|
+
const isSelected = (id: string): boolean => {
|
|
94
|
+
return selectedIds.value.has(id);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
selectedIds,
|
|
99
|
+
lastSelectedId,
|
|
100
|
+
editingId,
|
|
101
|
+
clearSelection,
|
|
102
|
+
selectItem,
|
|
103
|
+
selectAll,
|
|
104
|
+
setEditing,
|
|
105
|
+
isSelected
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ref, watch, onUnmounted } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 窗口拖拽功能管理
|
|
5
|
+
*/
|
|
6
|
+
export function useWindowDrag() {
|
|
7
|
+
const position = ref({ x: 0, y: 0 });
|
|
8
|
+
const isDragging = ref(false);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 鼠标移动处理
|
|
12
|
+
*/
|
|
13
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
14
|
+
if (!isDragging.value) return;
|
|
15
|
+
position.value = {
|
|
16
|
+
x: position.value.x + e.movementX,
|
|
17
|
+
y: position.value.y + e.movementY
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 鼠标释放处理
|
|
23
|
+
*/
|
|
24
|
+
const handleMouseUp = () => {
|
|
25
|
+
isDragging.value = false;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 开始拖拽
|
|
30
|
+
*/
|
|
31
|
+
const startDrag = (e: MouseEvent) => {
|
|
32
|
+
const target = e.target as HTMLElement;
|
|
33
|
+
// 检查是否点击在可拖拽区域,但排除按钮
|
|
34
|
+
if (target.closest('.draggable-area') && !target.closest('button')) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
isDragging.value = true;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 监听拖拽状态变化
|
|
42
|
+
*/
|
|
43
|
+
watch(isDragging, (dragging) => {
|
|
44
|
+
if (dragging) {
|
|
45
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
46
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
47
|
+
} else {
|
|
48
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
49
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 清理事件监听
|
|
55
|
+
*/
|
|
56
|
+
onUnmounted(() => {
|
|
57
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
58
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
position,
|
|
63
|
+
isDragging,
|
|
64
|
+
startDrag
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { ref, watch, onUnmounted } from 'vue';
|
|
2
|
+
|
|
3
|
+
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 窗口调整大小功能管理
|
|
7
|
+
*/
|
|
8
|
+
export function useWindowResize(
|
|
9
|
+
initialWidth: number,
|
|
10
|
+
initialHeight: number,
|
|
11
|
+
minWidth: number,
|
|
12
|
+
minHeight: number,
|
|
13
|
+
maxWidth: number,
|
|
14
|
+
maxHeight: number
|
|
15
|
+
) {
|
|
16
|
+
const width = ref(initialWidth);
|
|
17
|
+
const height = ref(initialHeight);
|
|
18
|
+
const isResizing = ref(false);
|
|
19
|
+
const resizeDirection = ref<ResizeDirection | null>(null);
|
|
20
|
+
const startX = ref(0);
|
|
21
|
+
const startY = ref(0);
|
|
22
|
+
const startWidth = ref(0);
|
|
23
|
+
const startHeight = ref(0);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 鼠标移动处理
|
|
27
|
+
*/
|
|
28
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
29
|
+
if (!isResizing.value || !resizeDirection.value) return;
|
|
30
|
+
|
|
31
|
+
const deltaX = e.clientX - startX.value;
|
|
32
|
+
const deltaY = e.clientY - startY.value;
|
|
33
|
+
|
|
34
|
+
let newWidth = startWidth.value;
|
|
35
|
+
let newHeight = startHeight.value;
|
|
36
|
+
|
|
37
|
+
const direction = resizeDirection.value;
|
|
38
|
+
|
|
39
|
+
// 处理水平方向
|
|
40
|
+
if (direction.includes('e')) {
|
|
41
|
+
newWidth = Math.min(Math.max(startWidth.value + deltaX, minWidth), maxWidth);
|
|
42
|
+
} else if (direction.includes('w')) {
|
|
43
|
+
newWidth = Math.min(Math.max(startWidth.value - deltaX, minWidth), maxWidth);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 处理垂直方向
|
|
47
|
+
if (direction.includes('s')) {
|
|
48
|
+
newHeight = Math.min(Math.max(startHeight.value + deltaY, minHeight), maxHeight);
|
|
49
|
+
} else if (direction.includes('n')) {
|
|
50
|
+
newHeight = Math.min(Math.max(startHeight.value - deltaY, minHeight), maxHeight);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
width.value = newWidth;
|
|
54
|
+
height.value = newHeight;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 鼠标释放处理
|
|
59
|
+
*/
|
|
60
|
+
const handleMouseUp = () => {
|
|
61
|
+
isResizing.value = false;
|
|
62
|
+
resizeDirection.value = null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 开始调整大小
|
|
67
|
+
*/
|
|
68
|
+
const startResize = (
|
|
69
|
+
e: MouseEvent,
|
|
70
|
+
direction: ResizeDirection,
|
|
71
|
+
currentWidth: number,
|
|
72
|
+
currentHeight: number
|
|
73
|
+
) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
isResizing.value = true;
|
|
77
|
+
resizeDirection.value = direction;
|
|
78
|
+
startX.value = e.clientX;
|
|
79
|
+
startY.value = e.clientY;
|
|
80
|
+
startWidth.value = currentWidth;
|
|
81
|
+
startHeight.value = currentHeight;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取对应方向的鼠标样式
|
|
86
|
+
*/
|
|
87
|
+
const getCursorForDirection = (direction: ResizeDirection): string => {
|
|
88
|
+
const cursors: Record<ResizeDirection, string> = {
|
|
89
|
+
n: 'n-resize',
|
|
90
|
+
s: 's-resize',
|
|
91
|
+
e: 'e-resize',
|
|
92
|
+
w: 'w-resize',
|
|
93
|
+
ne: 'ne-resize',
|
|
94
|
+
nw: 'nw-resize',
|
|
95
|
+
se: 'se-resize',
|
|
96
|
+
sw: 'sw-resize'
|
|
97
|
+
};
|
|
98
|
+
return cursors[direction];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 监听调整大小状态变化
|
|
103
|
+
*/
|
|
104
|
+
watch(isResizing, (resizing) => {
|
|
105
|
+
if (resizing) {
|
|
106
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
107
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
108
|
+
document.body.style.cursor = getCursorForDirection(resizeDirection.value || 'se');
|
|
109
|
+
document.body.style.userSelect = 'none';
|
|
110
|
+
} else {
|
|
111
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
112
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
113
|
+
document.body.style.cursor = '';
|
|
114
|
+
document.body.style.userSelect = '';
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 清理事件监听
|
|
120
|
+
*/
|
|
121
|
+
onUnmounted(() => {
|
|
122
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
123
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
124
|
+
document.body.style.cursor = '';
|
|
125
|
+
document.body.style.userSelect = '';
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
width,
|
|
130
|
+
height,
|
|
131
|
+
isResizing,
|
|
132
|
+
startResize
|
|
133
|
+
};
|
|
134
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export * from './types';
|
|
3
|
+
|
|
4
|
+
// Core Components
|
|
5
|
+
export { default as FileListView } from './components/FileListView.vue';
|
|
6
|
+
export { default as FileSidebar } from './components/FileSidebar.vue';
|
|
7
|
+
export { default as Toolbar } from './components/Toolbar.vue';
|
|
8
|
+
export { default as Breadcrumb } from './components/Breadcrumb.vue';
|
|
9
|
+
export { default as StatusBar } from './components/StatusBar.vue';
|
|
10
|
+
|
|
11
|
+
// Base Components
|
|
12
|
+
export { default as FileIcon } from './components/FileIcon.vue';
|
|
13
|
+
export { default as FileGrid } from './components/FileGrid.vue';
|
|
14
|
+
export { default as FileList } from './components/FileList.vue';
|
|
15
|
+
export { default as ContextMenu } from './components/ContextMenu.vue';
|
|
16
|
+
export { default as SortIndicator } from './components/SortIndicator.vue';
|
|
17
|
+
|
|
18
|
+
// Window Component
|
|
19
|
+
export { default as Window } from './components/Window.vue';
|
|
20
|
+
|
|
21
|
+
// Dialog Components
|
|
22
|
+
export { default as CompressDialog } from './components/CompressDialog.vue';
|
|
23
|
+
export { default as ProgressDialog } from './components/ProgressDialog.vue';
|
|
24
|
+
export { default as FileInfoDialog } from './components/FileInfoDialog.vue';
|
|
25
|
+
|
|
26
|
+
// Composables
|
|
27
|
+
export { useSelection } from './composables/useSelection';
|
|
28
|
+
export { useDragAndDrop } from './composables/useDragAndDrop';
|
|
29
|
+
export { useMediaPlayer } from './composables/useMediaPlayer';
|
|
30
|
+
export { useWindowDrag } from './composables/useWindowDrag';
|
|
31
|
+
export { useWindowResize } from './composables/useWindowResize';
|
|
32
|
+
export { useApplicationIcon } from './composables/useApplicationIcon';
|