This commit is contained in:
Baobhan Sith
2025-04-21 20:12:02 +08:00
parent 23bc3f4b3d
commit f49b735290
2 changed files with 291 additions and 133 deletions
+127 -133
View File
@@ -12,6 +12,7 @@ import { useSessionStore } from '../stores/session.store';
import { useSettingsStore } from '../stores/settings.store'; import { useSettingsStore } from '../stores/settings.store';
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++ import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
import { useFileManagerContextMenu } from '../composables/file-manager/useFileManagerContextMenu'; // +++ 导入上下文菜单 Composable +++ import { useFileManagerContextMenu } from '../composables/file-manager/useFileManagerContextMenu'; // +++ 导入上下文菜单 Composable +++
import { useFileManagerSelection } from '../composables/file-manager/useFileManagerSelection'; // +++ 导入选择 Composable +++
// WebSocket composable 不再直接使用 // WebSocket composable 不再直接使用
import FileUploadPopup from './FileUploadPopup.vue'; import FileUploadPopup from './FileUploadPopup.vue';
import FileManagerContextMenu from './FileManagerContextMenu.vue'; // +++ 导入上下文菜单组件 +++ import FileManagerContextMenu from './FileManagerContextMenu.vue'; // +++ 导入上下文菜单组件 +++
@@ -120,8 +121,9 @@ const { shareFileEditorTabsBoolean } = storeToRefs(settingsStore); // 使用 sto
// --- UI 状态 Refs (Remain mostly the same) --- // --- UI 状态 Refs (Remain mostly the same) ---
const fileInputRef = ref<HTMLInputElement | null>(null); const fileInputRef = ref<HTMLInputElement | null>(null);
const selectedItems = ref(new Set<string>()); // --- 选择状态 (移至 useFileManagerSelection) ---
const lastClickedIndex = ref(-1); // const selectedItems = ref(new Set<string>()); // 移除旧的 ref
// const lastClickedIndex = ref(-1); // 移除旧的 ref
// --- 上下文菜单状态 (移至 useFileManagerContextMenu) --- // --- 上下文菜单状态 (移至 useFileManagerContextMenu) ---
// const contextMenuVisible = ref(false); // const contextMenuVisible = ref(false);
// const contextMenuPosition = ref({ x: 0, y: 0 }); // const contextMenuPosition = ref({ x: 0, y: 0 });
@@ -183,9 +185,107 @@ const formatMode = (mode: number): string => {
return str; return str;
}; };
// --- 排序与过滤逻辑 (保持在此处,Selection 和 ContextMenu 依赖它) ---
const sortedFileList = computed(() => {
// Ensure fileList.value is used (it's reactive from the manager)
if (!fileList.value) return [];
const list = [...fileList.value];
const key = sortKey.value;
const direction = sortDirection.value === 'asc' ? 1 : -1;
list.sort((a, b) => {
if (key !== 'type') {
if (a.attrs.isDirectory && !b.attrs.isDirectory) return -1;
if (!a.attrs.isDirectory && b.attrs.isDirectory) return 1;
}
let valA: string | number | boolean;
let valB: string | number | boolean;
switch (key) {
case 'type':
valA = a.attrs.isDirectory ? 0 : (a.attrs.isSymbolicLink ? 1 : 2);
valB = b.attrs.isDirectory ? 0 : (b.attrs.isSymbolicLink ? 1 : 2);
break;
case 'filename': valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase(); break;
case 'size': valA = a.attrs.isFile ? a.attrs.size : -1; valB = b.attrs.isFile ? b.attrs.size : -1; break;
case 'mtime': valA = a.attrs.mtime; valB = b.attrs.mtime; break;
default: valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase();
}
if (valA < valB) return -1 * direction;
if (valA > valB) return 1 * direction;
if (key !== 'filename') return a.filename.localeCompare(b.filename);
return 0;
});
return list;
});
const filteredFileList = computed(() => {
if (!searchQuery.value) {
return sortedFileList.value; // 如果没有搜索查询,返回原始排序列表
}
const lowerCaseQuery = searchQuery.value.toLowerCase();
return sortedFileList.value.filter(item =>
item.filename.toLowerCase().includes(lowerCaseQuery)
);
});
const handleSort = (key: keyof FileListItem | 'type' | 'size' | 'mtime') => {
if (sortKey.value === key) {
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
} else {
sortKey.value = key;
sortDirection.value = 'asc';
}
};
// --- 列表项点击与选择逻辑 (使用 Composable) ---
// 定义单击时的动作回调 (移到 Selection 实例化之前)
const handleItemAction = (item: FileListItem) => {
if (item.attrs.isDirectory) {
if (isLoading.value) {
console.log(`[FileManager ${props.sessionId}] Ignoring directory click, already loading...`);
return;
}
const newPath = item.filename === '..'
? currentPath.value.substring(0, currentPath.value.lastIndexOf('/')) || '/'
: joinPath(currentPath.value, item.filename);
loadDirectory(newPath);
} else if (item.attrs.isFile) {
const filePath = joinPath(currentPath.value, item.filename);
const fileInfo: FileInfo = { name: item.filename, fullPath: filePath };
if (settingsStore.showPopupFileEditorBoolean) {
console.log(`[FileManager ${props.sessionId}] Triggering popup for: ${filePath}`);
fileEditorStore.triggerPopup(filePath, props.sessionId);
}
if (shareFileEditorTabsBoolean.value) {
console.log(`[FileManager ${props.sessionId}] Opening file in shared mode (store handles loading): ${filePath}`);
fileEditorStore.openFile(filePath, props.sessionId);
} else {
console.log(`[FileManager ${props.sessionId}] Opening file in independent mode (store handles loading): ${filePath}`);
sessionStore.openFileInSession(props.sessionId, fileInfo);
}
}
};
// 实例化选择 Composable (需要 filteredFileList 和 handleItemAction)
const {
selectedItems, // 使用 Composable 返回的 selectedItems
lastClickedIndex, // 获取 lastClickedIndex 以传递给 ContextMenu
handleItemClick, // 使用 Composable 返回的 handleItemClick
clearSelection, // 获取清空选择的方法
} = useFileManagerSelection({
// 传递当前显示的列表 (已排序和过滤)
displayedFileList: filteredFileList, // 现在 filteredFileList 已定义
onItemAction: handleItemAction, // 传递动作回调
});
// --- SFTP 操作处理函数 (定义在此处,供 Composable 使用) --- // --- SFTP 操作处理函数 (定义在此处,供 Composable 使用) ---
const handleDeleteSelectedClick = () => { const handleDeleteSelectedClick = () => {
if (!props.wsDeps.isConnected.value || selectedItems.value.size === 0) return; // 恢复使用 props.wsDeps // 现在 selectedItems 来自 useFileManagerSelection
if (!props.wsDeps.isConnected.value || selectedItems.value.size === 0) return;
const itemsToDelete = Array.from(selectedItems.value) const itemsToDelete = Array.from(selectedItems.value)
.map(filename => fileList.value.find((f: FileListItem) => f.filename === filename)) // f 已有类型 .map(filename => fileList.value.find((f: FileListItem) => f.filename === filename)) // f 已有类型
.filter((item): item is FileListItem => item !== undefined); .filter((item): item is FileListItem => item !== undefined);
@@ -280,7 +380,7 @@ const triggerDownload = (item: FileListItem) => { // item 已有类型
}; };
// --- 上下文菜单逻辑 (使用 Composable) --- // --- 上下文菜单逻辑 (使用 Composable, 需要 Selection 和 Action Handlers) ---
const { const {
contextMenuVisible, contextMenuVisible,
contextMenuPosition, contextMenuPosition,
@@ -289,8 +389,8 @@ const {
showContextMenu, // 使用 Composable 提供的函数 showContextMenu, // 使用 Composable 提供的函数
hideContextMenu, // <-- 获取 hideContextMenu 函数 hideContextMenu, // <-- 获取 hideContextMenu 函数
} = useFileManagerContextMenu({ } = useFileManagerContextMenu({
selectedItems, selectedItems, // 传递来自 useFileManagerSelection 的 selectedItems
lastClickedIndex, lastClickedIndex, // 传递来自 useFileManagerSelection 的 lastClickedIndex
fileList, // 传递 sftpManager 的 fileList fileList, // 传递 sftpManager 的 fileList
currentPath, // 传递 sftpManager 的 currentPath currentPath, // 传递 sftpManager 的 currentPath
isConnected: props.wsDeps.isConnected, // 传递响应式引用 isConnected: props.wsDeps.isConnected, // 传递响应式引用
@@ -310,70 +410,6 @@ const {
// --- 目录加载与导航 --- // --- 目录加载与导航 ---
// loadDirectory is provided by props.sftpManager // loadDirectory is provided by props.sftpManager
// --- 列表项点击与选择逻辑 ---
// handleItemClick 中的 item 参数已有类型
// --- 列表项点击与选择逻辑 ---
const handleItemClick = (event: MouseEvent, item: FileListItem) => { // item 已有类型
const itemIndex = fileList.value.findIndex((f: FileListItem) => f.filename === item.filename); // f 已有类型
if (itemIndex === -1 && item.filename !== '..') return;
if (event.ctrlKey || event.metaKey) {
if (item.filename === '..') return;
if (selectedItems.value.has(item.filename)) selectedItems.value.delete(item.filename);
else selectedItems.value.add(item.filename);
lastClickedIndex.value = itemIndex;
} else if (event.shiftKey && lastClickedIndex.value !== -1) {
if (item.filename === '..') return;
selectedItems.value.clear();
const start = Math.min(lastClickedIndex.value, itemIndex);
const end = Math.max(lastClickedIndex.value, itemIndex);
for (let i = start; i <= end; i++) {
// Use fileList from props
if (fileList.value[i]) selectedItems.value.add(fileList.value[i].filename);
}
} else {
selectedItems.value.clear();
if (item.filename !== '..') {
selectedItems.value.add(item.filename);
lastClickedIndex.value = itemIndex;
} else {
lastClickedIndex.value = -1;
}
if (item.attrs.isDirectory) {
if (isLoading.value) { // Use isLoading from props
console.log(`[FileManager ${props.sessionId}] Ignoring directory click, already loading...`);
return;
}
const newPath = item.filename === '..'
? currentPath.value.substring(0, currentPath.value.lastIndexOf('/')) || '/' // 使用 sftpManager 的 currentPath
: joinPath(currentPath.value, item.filename); // Use joinPath from props
loadDirectory(newPath); // Use loadDirectory from props
} else if (item.attrs.isFile) {
const filePath = joinPath(currentPath.value, item.filename); // Use joinPath from props
const fileInfo: FileInfo = { name: item.filename, fullPath: filePath };
// 检查是否需要触发弹窗 (无论共享模式如何)
if (settingsStore.showPopupFileEditorBoolean) {
console.log(`[FileManager ${props.sessionId}] Triggering popup for: ${filePath}`);
fileEditorStore.triggerPopup(filePath, props.sessionId); // <-- 传递参数
}
// 根据共享模式决定如何打开/加载文件
if (shareFileEditorTabsBoolean.value) {
// 共享模式:调用全局 fileEditorStore (它会处理标签页和加载)
console.log(`[FileManager ${props.sessionId}] Opening file in shared mode (store handles loading): ${filePath}`);
fileEditorStore.openFile(filePath, props.sessionId);
} else {
// 独立模式:调用 sessionStore (它会处理标签页和加载)
console.log(`[FileManager ${props.sessionId}] Opening file in independent mode (store handles loading): ${filePath}`);
sessionStore.openFileInSession(props.sessionId, fileInfo);
}
}
}
};
// --- 拖放上传逻辑 --- // --- 拖放上传逻辑 ---
const handleDragEnter = (event: DragEvent) => { const handleDragEnter = (event: DragEvent) => {
if (props.wsDeps.isConnected.value && event.dataTransfer?.types.includes('Files')) { // 恢复使用 props.wsDeps.isConnected if (props.wsDeps.isConnected.value && event.dataTransfer?.types.includes('Files')) { // 恢复使用 props.wsDeps.isConnected
@@ -701,60 +737,6 @@ const handleFileSelected = (event: Event) => {
input.value = ''; input.value = '';
}; };
// --- 排序逻辑 ---
// Uses fileList from props.sftpManager
const sortedFileList = computed(() => {
// Ensure fileList.value is used (it's reactive from the manager)
if (!fileList.value) return [];
const list = [...fileList.value];
const key = sortKey.value;
const direction = sortDirection.value === 'asc' ? 1 : -1;
list.sort((a, b) => {
if (key !== 'type') {
if (a.attrs.isDirectory && !b.attrs.isDirectory) return -1;
if (!a.attrs.isDirectory && b.attrs.isDirectory) return 1;
}
let valA: string | number | boolean;
let valB: string | number | boolean;
switch (key) {
case 'type':
valA = a.attrs.isDirectory ? 0 : (a.attrs.isSymbolicLink ? 1 : 2);
valB = b.attrs.isDirectory ? 0 : (b.attrs.isSymbolicLink ? 1 : 2);
break;
case 'filename': valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase(); break;
case 'size': valA = a.attrs.isFile ? a.attrs.size : -1; valB = b.attrs.isFile ? b.attrs.size : -1; break;
case 'mtime': valA = a.attrs.mtime; valB = b.attrs.mtime; break;
default: valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase();
}
if (valA < valB) return -1 * direction;
if (valA > valB) return 1 * direction;
if (key !== 'filename') return a.filename.localeCompare(b.filename);
return 0;
});
return list;
});
// 新增:过滤后的文件列表计算属性
const filteredFileList = computed(() => {
if (!searchQuery.value) {
return sortedFileList.value; // 如果没有搜索查询,返回原始排序列表
}
const lowerCaseQuery = searchQuery.value.toLowerCase();
return sortedFileList.value.filter(item =>
item.filename.toLowerCase().includes(lowerCaseQuery)
);
});
const handleSort = (key: keyof FileListItem | 'type' | 'size' | 'mtime') => {
if (sortKey.value === key) {
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
} else {
sortKey.value = key;
sortDirection.value = 'asc';
}
};
// --- 键盘导航和执行 --- // --- 键盘导航和执行 ---
const handleKeydown = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
const list = filteredFileList.value; const list = filteredFileList.value;
@@ -817,11 +799,23 @@ const scrollToSelected = async () => {
} }
}; };
// --- 重置选中索引的 Watchers --- // --- 重置选中索引和清空选择的 Watchers ---
watch(currentPath, () => { selectedIndex.value = -1; }); watch(currentPath, () => {
watch(searchQuery, () => { selectedIndex.value = -1; }); selectedIndex.value = -1;
watch(sortKey, () => { selectedIndex.value = -1; }); clearSelection(); // 清空选择
watch(sortDirection, () => { selectedIndex.value = -1; }); });
watch(searchQuery, () => {
selectedIndex.value = -1;
clearSelection(); // 清空选择
});
watch(sortKey, () => {
selectedIndex.value = -1;
clearSelection(); // 清空选择
});
watch(sortDirection, () => {
selectedIndex.value = -1;
clearSelection(); // 清空选择
});
// --- 生命周期钩子 --- // --- 生命周期钩子 ---
@@ -889,9 +883,9 @@ watchEffect((onCleanup) => {
} else if (!props.wsDeps.isConnected.value && initialLoadDone.value) { // 恢复使用 props.wsDeps.isConnected } else if (!props.wsDeps.isConnected.value && initialLoadDone.value) { // 恢复使用 props.wsDeps.isConnected
console.log(`[FileManager ${props.sessionId}] 连接丢失 (之前已加载),重置状态。`); console.log(`[FileManager ${props.sessionId}] 连接丢失 (之前已加载),重置状态。`);
selectedItems.value.clear(); clearSelection(); // 清空选择
lastClickedIndex.value = -1;
initialLoadDone.value = false; // 重置初始加载状态 initialLoadDone.value = false; // 重置初始加载状态
// lastClickedIndex.value = -1; // 由 clearSelection 处理
isFetchingInitialPath.value = false; // 重置获取状态 isFetchingInitialPath.value = false; // 重置获取状态
cleanupListeners(); cleanupListeners();
} }
@@ -1202,8 +1196,8 @@ const handleWheel = (event: WheelEvent) => {
:class="[ :class="[
'file-row', 'file-row',
{ clickable: item.attrs.isDirectory || item.attrs.isFile }, { clickable: item.attrs.isDirectory || item.attrs.isFile },
/* { selected: selectedItems.has(item.filename) }, */ /* 移除鼠标选择的 selected 类,统一用键盘的 */ { selected: selectedItems.has(item.filename) }, /* 恢复:使用 selectedItems Set 控制选中高亮 */
{ selected: index + (currentPath !== '/' ? 1 : 0) === selectedIndex }, /* 键盘选中高亮 */ // { selected: index + (currentPath !== '/' ? 1 : 0) === selectedIndex }, /* 暂时移除键盘选中高亮 */
{ 'folder-row': item.attrs.isDirectory }, // 添加文件夹标识类 { 'folder-row': item.attrs.isDirectory }, // 添加文件夹标识类
{ 'drop-target': item.attrs.isDirectory && dragOverTarget === item.filename } // 拖拽悬停高亮 { 'drop-target': item.attrs.isDirectory && dragOverTarget === item.filename } // 拖拽悬停高亮
]" ]"
@@ -0,0 +1,164 @@
import { ref, type Ref } from 'vue';
import type { FileListItem } from '../../types/sftp.types'; // 确保路径正确
// 定义 Composable 的输入参数类型
export interface UseFileManagerSelectionOptions {
// 注意:这里传入的应该是当前渲染在表格中的列表 (可能已排序/过滤)
// 在 FileManager.vue 中,这通常是 filteredFileList 或 sortedFileList
displayedFileList: Ref<Readonly<FileListItem[]>>;
// 回调函数,当需要执行导航或打开文件时调用
onItemAction: (item: FileListItem) => void;
}
export function useFileManagerSelection(options: UseFileManagerSelectionOptions) {
const { displayedFileList, onItemAction } = options;
const selectedItems = ref(new Set<string>());
const lastClickedIndex = ref(-1); // 索引相对于 displayedFileList
const handleItemClick = (event: MouseEvent, item: FileListItem) => {
console.log(`[Selection] handleItemClick called for item: ${item.filename}`, { event, item });
let shouldPerformAction = false; // 初始化标志
const ctrlOrMeta = event.ctrlKey || event.metaKey;
const shift = event.shiftKey;
console.log(`[Selection] Modifiers: Ctrl/Meta=${ctrlOrMeta}, Shift=${shift}`);
// 查找点击项在当前显示列表中的索引
const itemIndex = displayedFileList.value.findIndex((f) => f.filename === item.filename);
console.log(`[Selection] Item index in displayed list: ${itemIndex}`);
// 如果找不到项(理论上不应发生),或者点击的是 '..'
// (注意: '..' 通常是单独处理或在列表开头,这里假设它不在 displayedFileList 中,或者其点击事件由外部单独处理)
// 我们主要处理 displayedFileList 中的项的选择逻辑
if (itemIndex === -1) {
// 如果点击的是 '..'
// 如果点击的是 '..'
if (item.filename === '..') {
console.log("[Selection] Clicked on '..'");
// 只有在没有修饰键时才执行 '..' 的动作
if (!ctrlOrMeta && !shift) {
console.log("[Selection] '..' clicked without modifiers. Clearing selection and marking for action.");
console.log('[Selection] Before clear:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
selectedItems.value.clear();
lastClickedIndex.value = -1;
shouldPerformAction = true; // 标记执行动作
console.log('[Selection] After clear:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
} else {
console.log("[Selection] '..' clicked with modifiers. Ignoring action.");
}
// 如果有修饰键,则不执行动作,直接返回 (修改:移到下面统一处理)
// (不需要修改 selectedItems 或 lastClickedIndex)
// 注意:这里没有 else,因为如果 shouldPerformAction 仍为 false,后面的 if 会阻止调用 onItemAction
} else {
// 如果不是 '..' 且找不到索引,则忽略无效点击
return;
}
// 如果是 '..' 且没有修饰键,会继续到函数末尾的 action 调用检查
// 如果是 '..' 且有修饰键,则在此处返回 (因为上面没有 return) -> 不对,应该在上面 if block 里 return
// 统一处理 '..' 的返回逻辑:如果有修饰键,则不继续执行后续的选择/动作逻辑
if (item.filename === '..' && (ctrlOrMeta || shift)) {
console.log("[Selection] Returning early for '..' click with modifiers.");
return;
}
// 如果不是 '..' 且找不到索引,也返回
if (item.filename !== '..' && itemIndex === -1) {
console.log("[Selection] Item not found in displayed list (and not '..'). Returning.");
return;
}
}
// --- 主要选择逻辑 ---
console.log('[Selection] Before selection logic:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
// --- 调整后的主要选择逻辑 ---
if (ctrlOrMeta) { // 1. 检查 Ctrl/Meta
console.log('[Selection] Branch: Ctrl/Meta Click');
event.preventDefault();
event.stopPropagation(); // <-- 阻止冒泡
// Ctrl/Cmd + Click: Toggle selection
// '..' 不应参与多选 (已在前面处理)
// if (item.filename === '..') return; // '..' 已在前面处理
if (selectedItems.value.has(item.filename)) {
console.log(`[Selection] Ctrl/Meta: Removing ${item.filename}`);
selectedItems.value.delete(item.filename);
} else {
console.log(`[Selection] Ctrl/Meta: Adding ${item.filename}`);
selectedItems.value.add(item.filename); // Keep the add operation
}
// Removed the extra else block here
lastClickedIndex.value = itemIndex; // 更新最后点击的索引
console.log('[Selection] After Ctrl/Meta:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
console.log('[Selection] After Ctrl/Meta:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
} else if (shift) { // 2. 检查 Shift (移除 lastClickedIndex !== -1 条件)
console.log('[Selection] Branch: Shift Click');
event.preventDefault();
event.stopPropagation(); // <-- 阻止冒泡
// Shift + Click: Range selection
// '..' 不应参与范围选择 (已在前面处理)
// if (item.filename === '..') return; // '..' 已在前面处理
console.log('[Selection] Shift: Clearing previous selection.');
selectedItems.value.clear();
// 如果 lastClickedIndex 是 -1 (例如第一次 Shift 点击),则只选中当前项
const start = lastClickedIndex.value === -1 ? itemIndex : Math.min(lastClickedIndex.value, itemIndex);
const end = lastClickedIndex.value === -1 ? itemIndex : Math.max(lastClickedIndex.value, itemIndex);
console.log(`[Selection] Shift: Range from ${start} to ${end}`);
for (let i = start; i <= end; i++) {
// 确保索引有效且不是 '..'
const fileToAdd = displayedFileList.value[i];
if (fileToAdd && fileToAdd.filename !== '..') {
console.log(`[Selection] Shift: Adding ${fileToAdd.filename}`);
selectedItems.value.add(fileToAdd.filename);
}
}
// Shift-click 也更新 lastClickedIndex 为当前点击项
lastClickedIndex.value = itemIndex;
console.log('[Selection] After Shift:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
console.log('[Selection] After Shift:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
} else { // 3. 处理普通单击 (没有修饰键)
console.log('[Selection] Branch: Single Click');
// Single Click: Select only the clicked item and perform action
console.log('[Selection] Single Click: Clearing previous selection.');
selectedItems.value.clear();
// '..' 不应被加入 selectedItems (已在前面处理 shouldPerformAction)
if (item.filename !== '..') {
console.log(`[Selection] Single Click: Adding ${item.filename}`);
selectedItems.value.add(item.filename);
lastClickedIndex.value = itemIndex; // 更新最后点击的索引
} else {
// 点击 '..' 的 lastClickedIndex 已在前面处理
// lastClickedIndex.value = -1;
}
console.log('[Selection] After Single Click selection update:', new Set(selectedItems.value), 'Last index:', lastClickedIndex.value);
// --- 调用外部传入的动作回调 ---
// 只有单击时才执行导航或打开文件
// 标记执行动作 (只在普通单击时)
shouldPerformAction = true;
}
// 在函数末尾根据标志决定是否执行动作
console.log(`[Selection] Final check: shouldPerformAction = ${shouldPerformAction}`);
if (shouldPerformAction) {
console.log(`[Selection] Calling onItemAction for ${item.filename}`);
onItemAction(item);
} else {
console.log(`[Selection] Skipping onItemAction for ${item.filename}`);
}
};
// 清空选择的辅助函数,可能在其他地方(如路径改变时)需要
const clearSelection = () => {
selectedItems.value.clear();
lastClickedIndex.value = -1;
};
return {
selectedItems,
lastClickedIndex, // 只读暴露,主要由内部管理
handleItemClick,
clearSelection, // 暴露清空选择的方法
};
}