update
This commit is contained in:
@@ -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 });
|
||||
<div
|
||||
ref="fileListContainerRef"
|
||||
class="flex-grow overflow-y-auto relative outline-none"
|
||||
:class="{
|
||||
'outline-dashed outline-2 outline-offset-[-2px] outline-primary bg-primary/5': isDraggingOver
|
||||
}"
|
||||
@dragenter.prevent="handleDragEnter"
|
||||
@dragover.prevent="handleDragOver"
|
||||
@dragleave.prevent="handleDragLeave"
|
||||
@@ -1274,13 +1273,19 @@ defineExpose({ focusSearchInput, startPathEdit });
|
||||
tabindex="0"
|
||||
:style="{ '--row-size-multiplier': rowSizeMultiplier }"
|
||||
>
|
||||
<!-- Drag over overlay text (optional) -->
|
||||
<div v-if="isDraggingOver" class="absolute inset-0 flex items-center justify-center bg-black/60 text-white text-lg font-medium rounded pointer-events-none z-10">
|
||||
<!-- 新增:外部文件拖拽蒙版 -->
|
||||
<div
|
||||
v-if="showExternalDropOverlay"
|
||||
class="absolute inset-0 flex items-center justify-center bg-black/70 text-white text-xl font-semibold rounded z-50 pointer-events-auto"
|
||||
@dragover.prevent
|
||||
@dragleave.prevent="handleDragLeave"
|
||||
@drop.prevent="handleOverlayDrop"
|
||||
>
|
||||
{{ t('fileManager.dropFilesHere', 'Drop files here to upload') }}
|
||||
</div>
|
||||
|
||||
<!-- File Table -->
|
||||
<table ref="tableRef" class="w-full border-collapse table-fixed border-border rounded" @contextmenu.prevent>
|
||||
<table ref="tableRef" class="w-full border-collapse table-fixed border-border rounded" :class="{'pointer-events-none': showExternalDropOverlay}" @contextmenu.prevent>
|
||||
<colgroup>
|
||||
<col :style="{ width: `${colWidths.type}px` }">
|
||||
<col :style="{ width: `${colWidths.name}px` }">
|
||||
|
||||
@@ -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<FileListItem | null>(null); // 内部拖拽时,被拖拽的项
|
||||
const dragOverTarget = ref<string | null>(null); // 内部/外部拖拽时,悬停的目标文件夹名称 (用于行高亮)
|
||||
const dragOverTarget = ref<string | null>(null); // 内部拖拽时,悬停的目标文件夹名称 (用于行高亮)
|
||||
const scrollIntervalId = ref<number | null>(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,
|
||||
|
||||
Reference in New Issue
Block a user