From be845c2d9f639b067cc2ea4ca6b8805987a057a3 Mon Sep 17 00:00:00 2001
From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com>
Date: Tue, 29 Apr 2025 22:50:15 +0800
Subject: [PATCH] update
---
.../frontend/src/components/FileManager.vue | 25 +-
.../file-manager/useFileManagerDragAndDrop.ts | 259 +++++++++---------
2 files changed, 150 insertions(+), 134 deletions(-)
diff --git a/packages/frontend/src/components/FileManager.vue b/packages/frontend/src/components/FileManager.vue
index f9c99bb..0303e3b 100644
--- a/packages/frontend/src/components/FileManager.vue
+++ b/packages/frontend/src/components/FileManager.vue
@@ -617,14 +617,16 @@ const {
// --- 拖放逻辑 (使用 Composable) ---
const {
- isDraggingOver, // 容器拖拽悬停状态 (外部文件)
- dragOverTarget, // 行拖拽悬停目标 (内部/外部)
+ // isDraggingOver, // 不再直接使用容器的悬停状态
+ showExternalDropOverlay, // 新增:控制蒙版显示
+ dragOverTarget, // 行拖拽悬停目标 (内部)
// draggedItem, // 内部状态,不需要在 FileManager 中直接使用
// --- 事件处理器 ---
handleDragEnter,
- handleDragOver,
+ handleDragOver, // 容器的 dragover (主要处理内部滚动)
handleDragLeave,
- handleDrop,
+ handleDrop, // 容器的 drop (主要用于清理)
+ handleOverlayDrop, // 新增:蒙版的 drop
handleDragStart,
handleDragEnd,
handleDragOverRow,
@@ -1260,9 +1262,6 @@ defineExpose({ focusSearchInput, startPathEdit });
-
-
+
+
{{ t('fileManager.dropFilesHere', 'Drop files here to upload') }}
-
+
diff --git a/packages/frontend/src/composables/file-manager/useFileManagerDragAndDrop.ts b/packages/frontend/src/composables/file-manager/useFileManagerDragAndDrop.ts
index 1f14e81..28f01a1 100644
--- a/packages/frontend/src/composables/file-manager/useFileManagerDragAndDrop.ts
+++ b/packages/frontend/src/composables/file-manager/useFileManagerDragAndDrop.ts
@@ -29,9 +29,10 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
} = options;
// --- 拖放状态 Refs ---
- const isDraggingOver = ref(false); // 是否有文件拖拽悬停在容器上 (用于外部文件)
+ // const isDraggingOver = ref(false); // 不再使用,由 showExternalDropOverlay 替代外部拖拽状态
+ const showExternalDropOverlay = ref(false); // 新增:控制外部文件拖拽蒙版的显示
const draggedItem = ref(null); // 内部拖拽时,被拖拽的项
- const dragOverTarget = ref(null); // 内部/外部拖拽时,悬停的目标文件夹名称 (用于行高亮)
+ const dragOverTarget = ref(null); // 内部拖拽时,悬停的目标文件夹名称 (用于行高亮)
const scrollIntervalId = ref(null); // 自动滚动计时器 ID
// --- 自动滚动常量 ---
@@ -48,102 +49,118 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
// --- 事件处理函数 ---
const handleDragEnter = (event: DragEvent) => {
- if (isConnected.value && event.dataTransfer?.types.includes('Files')) {
- isDraggingOver.value = true;
- }
+ // 检查是否是外部文件拖拽
+ const isExternalFileDrag = event.dataTransfer?.types.includes('Files') ?? false;
+ if (isConnected.value && isExternalFileDrag && !draggedItem.value) { // 确保不是内部拖拽触发
+ // console.log("[DragDrop] External file drag entered container.");
+ showExternalDropOverlay.value = true; // 显示蒙版
+ } else if (draggedItem.value) {
+ // console.log("[DragDrop] Internal item drag entered container area.");
+ // 内部拖拽进入容器但不在行上,可能需要处理效果,但不显示蒙版
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'none'; // 默认在容器空白处无效
+ }
};
- const handleDragOver = (event: DragEvent) => {
- event.preventDefault(); // 必须阻止默认行为以允许 drop
+ const handleDragOver = (event: DragEvent) => {
+ // 这个函数现在主要负责处理内部拖拽时的自动滚动
+ // 外部文件拖拽的 dragover 由蒙版处理 (只阻止默认行为)
const isExternalFileDrag = event.dataTransfer?.types.includes('Files') ?? false;
const isInternalDrag = !!draggedItem.value;
- let effect: 'copy' | 'move' | 'none' = 'none';
- let currentTargetFilename: string | null = null;
- let highlightContainer = false;
+ // 1. 如果是外部文件拖拽,确保蒙版是显示的,并设置效果
+ if (isExternalFileDrag && isConnected.value && !isInternalDrag) { // 再次确认不是内部拖拽
+ showExternalDropOverlay.value = true; // 确保蒙版显示
+ event.preventDefault(); // 必须阻止默认行为以允许 drop
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'; // 指示效果
+ // 外部拖拽时,不处理容器的自动滚动,因为鼠标在蒙版上
+ stopAutoScroll();
+ return; // 外部拖拽不由容器 handleDragOver 处理滚动
+ }
- const targetElement = event.target as HTMLElement;
- const targetRow = targetElement.closest('tr.file-row');
- const targetFilename = (targetRow instanceof HTMLElement) ? targetRow.dataset.filename : undefined;
- const targetIsFolder = targetRow?.classList.contains('folder-row');
+ // 2. 如果是内部拖拽
+ if (isInternalDrag && isConnected.value) {
+ // 内部拖拽时,dragover 主要由行处理 (handleDragOverRow)
+ // 但如果鼠标在行之间或空白区域,容器的 dragover 会触发
+ // 此时需要处理自动滚动,并阻止默认行为
+ event.preventDefault(); // 阻止默认行为(如文本选择),允许滚动逻辑
+ // 效果由 handleDragOverRow 设置,但在空白区域应为 none
+ const targetElement = event.target as HTMLElement;
+ const targetRow = targetElement.closest('tr.file-row');
+ if (!targetRow && event.dataTransfer) { // 如果不在行上
+ event.dataTransfer.dropEffect = 'none';
+ dragOverTarget.value = null; // 清除行高亮
+ } else if (event.dataTransfer) {
+ // 如果在行上,效果由 handleDragOverRow 控制,这里假设为 move
+ event.dataTransfer.dropEffect = 'move';
+ }
- if (isConnected.value) {
- if (isExternalFileDrag) {
- effect = 'copy';
- highlightContainer = true;
- if (targetIsFolder && targetFilename && targetFilename !== '..') {
- currentTargetFilename = targetFilename;
+
+ // --- 处理自动滚动 ---
+ const container = fileListContainerRef.value;
+ // 只有在内部拖拽且悬停目标有效(在行上)时才滚动
+ if (container && dragOverTarget.value) { // 依赖 handleDragOverRow 设置的 dragOverTarget
+ const rect = container.getBoundingClientRect();
+ const mouseY = event.clientY - rect.top;
+
+ if (mouseY < SCROLL_ZONE_HEIGHT) {
+ if (scrollIntervalId.value === null) {
+ scrollIntervalId.value = window.setInterval(() => {
+ if (container.scrollTop > 0) {
+ container.scrollTop -= SCROLL_SPEED;
+ } else {
+ stopAutoScroll();
+ }
+ }, 30);
+ }
+ } else if (mouseY > container.clientHeight - SCROLL_ZONE_HEIGHT) {
+ if (scrollIntervalId.value === null) {
+ scrollIntervalId.value = window.setInterval(() => {
+ if (container.scrollTop < container.scrollHeight - container.clientHeight) {
+ container.scrollTop += SCROLL_SPEED;
+ } else {
+ stopAutoScroll();
+ }
+ }, 30);
+ }
} else {
- currentTargetFilename = null;
- }
- } else if (isInternalDrag && draggedItem.value) {
- highlightContainer = false;
- if (targetIsFolder && targetFilename && targetFilename !== draggedItem.value.filename) {
- effect = 'move';
- currentTargetFilename = targetFilename;
- } else {
- effect = 'none';
- currentTargetFilename = null;
+ stopAutoScroll(); // 在中间区域停止滚动
}
} else {
- effect = 'none';
- currentTargetFilename = null;
- highlightContainer = false;
+ stopAutoScroll(); // 如果不在滚动区域或目标无效,停止滚动
}
- } else {
- effect = 'none';
- currentTargetFilename = null;
- highlightContainer = false;
+ return; // 内部拖拽处理完毕
}
- if (event.dataTransfer) {
- event.dataTransfer.dropEffect = effect;
- }
- isDraggingOver.value = highlightContainer;
- dragOverTarget.value = currentTargetFilename;
-
- // --- 处理自动滚动 ---
- const container = fileListContainerRef.value;
- if (container && (isExternalFileDrag || isInternalDrag) && effect !== 'none') {
- const rect = container.getBoundingClientRect();
- const mouseY = event.clientY - rect.top;
-
- if (mouseY < SCROLL_ZONE_HEIGHT) {
- if (scrollIntervalId.value === null) {
- scrollIntervalId.value = window.setInterval(() => {
- if (container.scrollTop > 0) {
- container.scrollTop -= SCROLL_SPEED;
- } else {
- stopAutoScroll();
- }
- }, 30);
- }
- } else if (mouseY > container.clientHeight - SCROLL_ZONE_HEIGHT) {
- if (scrollIntervalId.value === null) {
- scrollIntervalId.value = window.setInterval(() => {
- if (container.scrollTop < container.scrollHeight - container.clientHeight) {
- container.scrollTop += SCROLL_SPEED;
- } else {
- stopAutoScroll();
- }
- }, 30);
- }
- } else {
- stopAutoScroll();
- }
- } else {
- stopAutoScroll();
- }
+ // 3. 其他情况 (非文件、非内部拖拽、未连接)
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'none';
+ stopAutoScroll(); // 停止滚动
+ // 不一定需要阻止默认行为 event.preventDefault();
};
const handleDragLeave = (event: DragEvent) => {
const target = event.relatedTarget as Node | null;
- const container = (event.currentTarget as HTMLElement);
- if (!target || !container.contains(target)) {
- isDraggingOver.value = false;
- dragOverTarget.value = null;
- stopAutoScroll();
- }
+ const container = (event.currentTarget as HTMLElement);
+ // 检查是否真的离开了容器边界
+ if (!target || !container.contains(target)) {
+ // console.log("[DragDrop] Drag left container boundary.");
+ if (showExternalDropOverlay.value) {
+ // console.log("[DragDrop] Hiding external drop overlay due to leaving container.");
+ showExternalDropOverlay.value = false; // 隐藏蒙版
+ }
+ // isDraggingOver.value = false; // 不再使用
+ dragOverTarget.value = null; // 清除行高亮
+ stopAutoScroll(); // 停止滚动
+ } else {
+ // console.log("[DragDrop] Drag left fired but still inside container.");
+ // 鼠标仍在容器内(可能移到了子元素上),不隐藏蒙版或清除状态
+ // 但如果是内部拖拽移到了非行区域,需要清除行高亮
+ if (draggedItem.value) {
+ const relatedRow = (target instanceof HTMLElement) ? target.closest('tr.file-row') : null;
+ if (!relatedRow) {
+ dragOverTarget.value = null;
+ }
+ }
+ }
};
// --- 新增:递归遍历文件树的辅助函数 ---
@@ -175,48 +192,46 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
// --- 结束新增 ---
- const handleDrop = (event: DragEvent) => {
- const wasDraggingOver = isDraggingOver.value;
- const currentDragTarget = dragOverTarget.value; // 拖放目标文件夹名称
- isDraggingOver.value = false;
- dragOverTarget.value = null;
- stopAutoScroll();
+ // 新增:处理蒙版上的 Drop 事件
+ const handleOverlayDrop = (event: DragEvent) => {
+ event.preventDefault(); // 必须阻止,以防浏览器打开文件
+ // console.log("[DragDrop] Drop event on overlay.");
+ showExternalDropOverlay.value = false; // 隐藏蒙版
+ stopAutoScroll(); // 停止滚动
- // --- 修改:使用 DataTransferItemList 和 webkitGetAsEntry 处理拖放 ---
const items = event.dataTransfer?.items;
if (!items || items.length === 0 || !isConnected.value) {
- if (draggedItem.value) draggedItem.value = null; // 清理内部拖拽状态
+ console.log("[DragDrop] Overlay drop ignored: No items or not connected.");
return;
}
- // 检查放置目标是否有效 (由 handleDragOver 决定)
- // 对于外部文件,要么容器高亮 (wasDraggingOver),要么行高亮 (currentDragTarget)
- // 注意:拖放到子文件夹的功能暂时移除,所有拖放都上传到当前目录或根目录
- // 如果需要拖放到子文件夹,需要重新设计 targetFolderPath 的逻辑
- // if (!wasDraggingOver && !currentDragTarget) {
- // console.log(`[DragDrop] Drop ignored: Drop target was not valid according to handleDragOver.`);
- // return;
- // }
-
- console.log(`[DragDrop] Drop event detected with ${items.length} items.`);
-
+ console.log(`[DragDrop] Processing ${items.length} items from overlay drop.`);
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry();
if (entry) {
- console.log(`[DragDrop] Processing entry: ${entry.name}, isFile: ${entry.isFile}, isDirectory: ${entry.isDirectory}`);
- traverseFileTree(entry); // 开始遍历文件树,初始相对路径为空
+ // console.log(`[DragDrop] Processing entry from overlay: ${entry.name}`);
+ traverseFileTree(entry); // 处理文件/文件夹
} else {
- console.warn(`[DragDrop] Could not get entry for item ${i}`);
+ console.warn(`[DragDrop] Could not get entry for item ${i} from overlay.`);
}
- } else {
- console.log(`[DragDrop] Skipping non-file item kind: ${item.kind}`);
}
}
- // --- 结束修改 ---
+ };
- draggedItem.value = null; // 确保清理内部拖拽状态
+ // 原有的 handleDrop (容器的 drop) 现在基本不需要了,
+ // 因为外部 drop 由蒙版处理,内部 drop 由行处理并阻止冒泡。
+ // 保留一个空的或只做清理的函数以防万一。
+ const handleDrop = (event: DragEvent) => {
+ // console.log("[DragDrop] Container drop event triggered (should be rare).");
+ // 清理所有状态以防异常情况
+ showExternalDropOverlay.value = false;
+ draggedItem.value = null; // 清理内部拖拽状态
+ dragOverTarget.value = null; // 清理行高亮
+ stopAutoScroll(); // 停止滚动
+ // 阻止默认行为以防万一
+ event.preventDefault();
};
const handleDragStart = (item: FileListItem) => {
@@ -235,7 +250,9 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
};
const handleDragOverRow = (targetItem: FileListItem, event: DragEvent) => {
- event.preventDefault(); // 允许 drop
+ event.preventDefault(); // 允许 drop 在行上
+ event.stopPropagation(); // 阻止事件冒泡到容器的 handleDragOver
+
// 内部拖拽逻辑: 只能拖拽非 '..' 项,目标必须是文件夹或 '..',且不能是自身
if (!draggedItem.value || draggedItem.value.filename === '..' || (targetItem.filename !== '..' && (!targetItem.attrs.isDirectory || draggedItem.value.filename === targetItem.filename))) {
if (event.dataTransfer) event.dataTransfer.dropEffect = 'none';
@@ -244,7 +261,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
}
// 设置放置效果为 'move' 并记录目标
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
- dragOverTarget.value = targetItem.filename;
+ dragOverTarget.value = targetItem.filename; // 更新悬停目标
};
const handleDragLeaveRow = (targetItem: FileListItem) => {
@@ -255,26 +272,18 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
};
const handleDropOnRow = (targetItem: FileListItem, event: DragEvent) => {
- event.preventDefault();
- // 检查是否是外部文件拖拽 (dataTransfer.files 存在)
- const files = event.dataTransfer?.files;
- if (files && files.length > 0) {
- // 如果是外部文件拖拽,不阻止冒泡,让父容器的 handleDrop 处理上传
- // console.log(`[DragDrop] External file drop detected on row, letting parent handle.`);
- // 不需要清除 draggedItem.value,因为外部拖拽时它应该为 null
- // dragOverTarget.value = null; // 清除悬停状态 (父容器 handleDrop 会处理)
- return; // 让事件冒泡到父 div 的 handleDrop
- }
+ event.preventDefault(); // 确保阻止默认行为(例如导航)
+ event.stopPropagation(); // 阻止事件冒泡到容器的 handleDrop
- // --- 以下是处理内部文件移动的逻辑 ---
- event.stopPropagation(); // 仅在处理内部移动时阻止冒泡
+ // --- 处理内部文件移动的逻辑 ---
const sourceItem = draggedItem.value;
const currentDragOverTarget = dragOverTarget.value; // 保存当前目标,然后清除
dragOverTarget.value = null; // 清除悬停状态
// 验证内部拖放操作的有效性
+ // 检查: 是否有拖拽项? 拖拽项不是 '..'? 目标项是文件夹或 '..'? 拖拽项不是目标项自身? 放置的目标确实是悬停的目标?
if (!sourceItem || sourceItem.filename === '..' || (targetItem.filename !== '..' && !targetItem.attrs.isDirectory) || sourceItem.filename === targetItem.filename || targetItem.filename !== currentDragOverTarget) {
- // console.log(`[DragDrop] Internal drop on row ignored: Invalid target, source, or drop occurred outside the intended target row. Source: ${sourceItem?.filename}, Target: ${targetItem.filename}, Drop Target: ${currentDragOverTarget}`);
+ console.log(`[DragDrop] Internal drop on row ignored: Invalid conditions. Source: ${sourceItem?.filename}, Target: ${targetItem.filename}, Drop Target: ${currentDragOverTarget}`);
if (sourceItem) draggedItem.value = null; // 清理拖拽状态
return;
}
@@ -359,14 +368,16 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
// --- 返回状态和处理函数 ---
return {
- isDraggingOver,
+ // isDraggingOver, // 不再导出
+ showExternalDropOverlay, // 新增导出
dragOverTarget,
draggedItem, // 需要暴露以供 handleDragOverRow 等函数内部判断
// --- 事件处理器 ---
handleDragEnter,
handleDragOver,
handleDragLeave,
- handleDrop,
+ handleDrop, // 容器的 drop (主要用于清理)
+ handleOverlayDrop, // 新增导出:蒙版的 drop
handleDragStart,
handleDragEnd,
handleDragOverRow,