update
This commit is contained in:
@@ -655,7 +655,9 @@ const handleFileSelected = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
// 恢复使用 props.wsDeps.isConnected
|
||||
if (!input.files || !props.wsDeps.isConnected.value) return;
|
||||
Array.from(input.files).forEach(startFileUpload); // Use startFileUpload from useFileUploader
|
||||
// --- 修正:使用匿名函数包装 startFileUpload 调用 ---
|
||||
Array.from(input.files).forEach(file => startFileUpload(file)); // 只传递 file 参数
|
||||
// --- 结束修正 ---
|
||||
input.value = '';
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface UseFileManagerDragAndDropOptions {
|
||||
|
||||
// 函数依赖
|
||||
joinPath: (base: string, target: string) => string; // 路径拼接函数
|
||||
onFileUpload: (file: File) => void; // 触发文件上传的回调
|
||||
onFileUpload: (file: File, relativePath?: string) => void; // 修改:触发文件上传的回调,增加相对路径
|
||||
onItemMove: (sourceItem: FileListItem, newFullPath: string) => void; // 触发文件/文件夹移动的回调
|
||||
}
|
||||
|
||||
@@ -146,40 +146,76 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
||||
}
|
||||
};
|
||||
|
||||
// --- 新增:递归遍历文件树的辅助函数 ---
|
||||
const traverseFileTree = (item: FileSystemEntry, path = '') => {
|
||||
path = path || '';
|
||||
if (item.isFile) {
|
||||
// 文件处理
|
||||
(item as FileSystemFileEntry).file((file) => {
|
||||
// 调用上传函数,传递文件和相对路径
|
||||
console.log(`[DragDrop] Uploading file: ${path}${file.name}`);
|
||||
onFileUpload(file, path); // 传递相对路径
|
||||
}, (err) => {
|
||||
console.error(`[DragDrop] Error getting file from entry: ${path}${item.name}`, err);
|
||||
});
|
||||
} else if (item.isDirectory) {
|
||||
// 目录处理
|
||||
const dirReader = (item as FileSystemDirectoryEntry).createReader();
|
||||
dirReader.readEntries((entries) => {
|
||||
console.log(`[DragDrop] Traversing directory: ${path}${item.name}, found ${entries.length} entries.`);
|
||||
// 递归遍历目录中的每个条目
|
||||
entries.forEach((entry) => {
|
||||
traverseFileTree(entry, path + item.name + '/'); // 更新相对路径
|
||||
});
|
||||
}, (err) => {
|
||||
console.error(`[DragDrop] Error reading directory entries: ${path}${item.name}`, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
// --- 结束新增 ---
|
||||
|
||||
|
||||
const handleDrop = (event: DragEvent) => {
|
||||
const wasDraggingOver = isDraggingOver.value;
|
||||
const currentDragTarget = dragOverTarget.value;
|
||||
const currentDragTarget = dragOverTarget.value; // 拖放目标文件夹名称
|
||||
isDraggingOver.value = false;
|
||||
dragOverTarget.value = null;
|
||||
stopAutoScroll();
|
||||
|
||||
const files = event.dataTransfer?.files;
|
||||
if (!files || files.length === 0 || !isConnected.value) {
|
||||
// --- 修改:使用 DataTransferItemList 和 webkitGetAsEntry 处理拖放 ---
|
||||
const items = event.dataTransfer?.items;
|
||||
if (!items || items.length === 0 || !isConnected.value) {
|
||||
if (draggedItem.value) draggedItem.value = null; // 清理内部拖拽状态
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查放置目标是否有效 (由 handleDragOver 决定)
|
||||
// 对于外部文件,要么容器高亮 (wasDraggingOver),要么行高亮 (currentDragTarget)
|
||||
if (!wasDraggingOver && !currentDragTarget) {
|
||||
console.log(`[DragDrop] Drop ignored: Drop target was not valid according to handleDragOver.`);
|
||||
return;
|
||||
// 注意:拖放到子文件夹的功能暂时移除,所有拖放都上传到当前目录或根目录
|
||||
// 如果需要拖放到子文件夹,需要重新设计 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.`);
|
||||
|
||||
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); // 开始遍历文件树,初始相对路径为空
|
||||
} else {
|
||||
console.warn(`[DragDrop] Could not get entry for item ${i}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[DragDrop] Skipping non-file item kind: ${item.kind}`);
|
||||
}
|
||||
}
|
||||
// --- 结束修改 ---
|
||||
|
||||
|
||||
const fileListArray = Array.from(files);
|
||||
let targetFolderPath = currentPath.value; // 默认上传到当前目录
|
||||
|
||||
// 如果是放置在特定子文件夹行上
|
||||
if (currentDragTarget && currentDragTarget !== '..') {
|
||||
targetFolderPath = joinPath(currentPath.value, currentDragTarget);
|
||||
console.log(`[DragDrop] Dropped ${fileListArray.length} external files onto folder '${currentDragTarget}'. Uploading to: ${targetFolderPath}`);
|
||||
} else {
|
||||
console.log(`[DragDrop] Dropped ${fileListArray.length} external files onto current path '${currentPath.value}'.`);
|
||||
}
|
||||
|
||||
// 注意:原始代码中 startFileUpload 没有使用 targetFolderPath,这里暂时保持一致
|
||||
// 如果需要上传到子目录,需要修改 useFileUploader 或此处的调用方式
|
||||
fileListArray.forEach(onFileUpload);
|
||||
draggedItem.value = null; // 确保清理内部拖拽状态
|
||||
};
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export function useFileUploader(
|
||||
};
|
||||
|
||||
|
||||
const startFileUpload = (file: File) => {
|
||||
const startFileUpload = (file: File, relativePath?: string) => { // 保持签名修改
|
||||
if (!isConnected.value) {
|
||||
console.warn('[文件上传模块] 无法开始上传:WebSocket 未连接。');
|
||||
// 可以选择向用户显示错误消息
|
||||
@@ -131,16 +131,35 @@ export function useFileUploader(
|
||||
}
|
||||
|
||||
const uploadId = generateUploadId();
|
||||
const remotePath = joinPath(currentPathRef.value, file.name);
|
||||
// --- 修正:直接构建最终远程路径 ---
|
||||
let finalRemotePath: string;
|
||||
if (relativePath) {
|
||||
// 确保 currentPathRef.value 结尾有斜杠
|
||||
const basePath = currentPathRef.value.endsWith('/') ? currentPathRef.value : `${currentPathRef.value}/`;
|
||||
// 确保 relativePath 开头没有斜杠,末尾有斜杠 (如果非空)
|
||||
let cleanRelativePath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
||||
// 移除末尾斜杠(如果有),因为文件名会加上
|
||||
cleanRelativePath = cleanRelativePath.endsWith('/') ? cleanRelativePath.slice(0, -1) : cleanRelativePath;
|
||||
// 拼接路径,确保 cleanRelativePath 和 file.name 之间只有一个斜杠
|
||||
finalRemotePath = `${basePath}${cleanRelativePath ? cleanRelativePath + '/' : ''}${file.name}`;
|
||||
} else {
|
||||
finalRemotePath = joinPath(currentPathRef.value, file.name); // 对于非文件夹上传,保持原样
|
||||
}
|
||||
// 规范化路径,移除多余的斜杠 e.g. /root//dir -> /root/dir
|
||||
finalRemotePath = finalRemotePath.replace(/\/+/g, '/');
|
||||
console.log(`[文件上传模块] Calculated finalRemotePath: ${finalRemotePath} (current: ${currentPathRef.value}, relative: ${relativePath}, filename: ${file.name})`); // 添加日志
|
||||
// --- 结束修正 ---
|
||||
|
||||
// 使用传入的 fileListRef 检查是否覆盖
|
||||
// fileListRef.value 现在是 readonly FileListItem[]
|
||||
if (fileListRef.value.some((item: FileListItem) => item.filename === file.name && !item.attrs.isDirectory)) { // 添加 item 类型注解
|
||||
if (!confirm(t('fileManager.prompts.confirmOverwrite', { name: file.name }))) {
|
||||
console.log(`[文件上传模块] 用户取消了 ${file.name} 的上传`);
|
||||
// --- 修正:检查覆盖逻辑需要使用 finalRemotePath 的 basename ---
|
||||
const finalFilename = finalRemotePath.substring(finalRemotePath.lastIndexOf('/') + 1);
|
||||
// 检查是否覆盖 *同名文件* (忽略目录)
|
||||
if (fileListRef.value.some((item: FileListItem) => item.filename === finalFilename && !item.attrs.isDirectory)) {
|
||||
if (!confirm(t('fileManager.prompts.confirmOverwrite', { name: finalFilename }))) {
|
||||
console.log(`[文件上传模块] 用户取消了 ${finalFilename} 的上传`);
|
||||
return; // 用户取消覆盖
|
||||
}
|
||||
}
|
||||
// --- 结束修正 ---
|
||||
|
||||
// 添加到响应式 uploads 字典
|
||||
uploads[uploadId] = {
|
||||
@@ -151,10 +170,10 @@ export function useFileUploader(
|
||||
status: 'pending' // 初始状态
|
||||
};
|
||||
|
||||
console.log(`[文件上传模块] 开始上传 ${uploadId} 到 ${remotePath}`);
|
||||
console.log(`[文件上传模块] 开始上传 ${uploadId} 到 ${finalRemotePath}`); // 使用 finalRemotePath
|
||||
sendMessage({
|
||||
type: 'sftp:upload:start',
|
||||
payload: { uploadId, remotePath, size: file.size }
|
||||
payload: { uploadId, remotePath: finalRemotePath, size: file.size, relativePath: relativePath || undefined } // 发送修正后的 remotePath
|
||||
});
|
||||
// 后端应该响应 sftp:upload:ready
|
||||
};
|
||||
|
||||
@@ -586,17 +586,35 @@ export function createSftpActionsManager(
|
||||
return false;
|
||||
};
|
||||
|
||||
// *** 新增:辅助函数 - 向文件树添加或更新节点 ***
|
||||
// *** 修改:辅助函数 - 向文件树添加或更新节点 (允许创建父节点占位符) ***
|
||||
const addOrUpdateNodeInTree = (parentPath: string, item: FileListItem): boolean => {
|
||||
const parentNode = findNodeByPath(fileTree, parentPath);
|
||||
if (parentNode && parentNode.childrenLoaded && parentNode.children) { // 确保父节点已加载子节点
|
||||
const newNode: FileTreeNode = {
|
||||
// --- 修改:调用 findNodeByPath 时允许创建缺失的父节点 ---
|
||||
const parentNode = findNodeByPath(fileTree, parentPath, true);
|
||||
// --- 结束修改 ---
|
||||
|
||||
// 如果父节点被成功找到或创建
|
||||
if (parentNode) {
|
||||
// 如果父节点的 children 为 null (可能刚被创建为占位符),则初始化为空数组
|
||||
if (parentNode.children === null) {
|
||||
parentNode.children = [];
|
||||
// 注意:此时 childrenLoaded 应该仍然是 false,除非它是叶子节点
|
||||
// findNodeByPath 创建占位符时 childrenLoaded 设为 false
|
||||
}
|
||||
|
||||
// 确保 children 是一个数组再继续
|
||||
if (!Array.isArray(parentNode.children)) {
|
||||
console.error(`[SFTP ${instanceSessionId}] Logic error: parentNode.children is not an array after findNodeByPath in addOrUpdateNodeInTree for path ${parentPath}`);
|
||||
return false; // 无法继续
|
||||
}
|
||||
|
||||
// --- 现有逻辑:添加或更新子节点 ---
|
||||
const newNode: FileTreeNode = reactive({ // 确保新节点也是响应式的
|
||||
filename: item.filename,
|
||||
longname: item.longname,
|
||||
attrs: item.attrs,
|
||||
children: item.attrs.isDirectory ? null : [],
|
||||
childrenLoaded: !item.attrs.isDirectory,
|
||||
};
|
||||
});
|
||||
|
||||
const existingIndex = parentNode.children.findIndex(node => node.filename === item.filename);
|
||||
if (existingIndex !== -1) {
|
||||
@@ -612,16 +630,14 @@ export function createSftpActionsManager(
|
||||
parentNode.children.splice(insertIndex, 0, newNode);
|
||||
console.log(`[SFTP ${instanceSessionId}] 添加文件树节点: ${parentPath}/${item.filename}`);
|
||||
}
|
||||
return true;
|
||||
} else if (parentNode && !parentNode.childrenLoaded) {
|
||||
// 父节点存在但子节点未加载,标记为需要重新加载
|
||||
parentNode.childrenLoaded = false; // 下次访问时会重新加载
|
||||
console.log(`[SFTP ${instanceSessionId}] 父节点 ${parentPath} 子节点未加载,标记为需要刷新`);
|
||||
// 可以在这里触发一次 loadDirectory(parentPath) 如果需要立即更新
|
||||
// --- 结束现有逻辑 ---
|
||||
return true; // 添加/更新成功
|
||||
|
||||
} else {
|
||||
console.warn(`[SFTP ${instanceSessionId}] 尝试向文件树 ${parentPath} 添加/更新节点 ${item.filename} 失败,父节点未找到或未加载`);
|
||||
// 如果 findNodeByPath 即使在 createIfMissing=true 时也失败了,说明有更深层的问题
|
||||
console.error(`[SFTP ${instanceSessionId}] Failed to find or create parent node ${parentPath} in addOrUpdateNodeInTree for item ${item.filename}.`);
|
||||
return false; // 添加/更新失败
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -841,22 +857,47 @@ export function createSftpActionsManager(
|
||||
// *** 新增:处理上传成功 ***
|
||||
const onUploadSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||
const newItem = payload as FileListItem | null; // 后端应发送 FileListItem 或 null
|
||||
const parentPath = currentPathRef.value; // 上传总是发生在当前路径
|
||||
const filename = newItem?.filename; // 从 newItem 获取文件名
|
||||
const fullPath = message.path; // 后端现在应该在 message 中包含完整的上传路径
|
||||
|
||||
console.log(`[SFTP ${instanceSessionId}] 上传文件成功: ${filename ? joinPath(parentPath, filename) : '(未知文件名)'}`); // 改进日志
|
||||
if (!fullPath) {
|
||||
console.error(`[SFTP ${instanceSessionId}] Received upload success but message is missing 'path'. Payload:`, payload);
|
||||
// 尝试从 newItem 获取文件名,但无法确定父路径,只能刷新当前目录
|
||||
const filename = newItem?.filename;
|
||||
console.warn(`[SFTP ${instanceSessionId}] Upload success for ${filename || '(unknown file)'} but cannot determine parent path. Reloading current directory.`);
|
||||
loadDirectory(currentPathRef.value); // Fallback to reloading current dir
|
||||
return;
|
||||
}
|
||||
|
||||
// *** 修改:直接修改文件树 ***
|
||||
// --- 修正:从完整路径推断父路径和文件名 ---
|
||||
const parentPath = fullPath.substring(0, fullPath.lastIndexOf('/')) || '/';
|
||||
const filename = fullPath.substring(fullPath.lastIndexOf('/') + 1);
|
||||
// --- 结束修正 ---
|
||||
|
||||
console.log(`[SFTP ${instanceSessionId}] 上传文件成功: ${fullPath}`);
|
||||
|
||||
// *** 修改:使用推断出的 parentPath 更新文件树 ***
|
||||
if (newItem) {
|
||||
// 确保 newItem 的 filename 与从路径中提取的一致
|
||||
if (newItem.filename !== filename) {
|
||||
console.warn(`[SFTP ${instanceSessionId}] Upload success: filename mismatch between message.path ('${filename}') and payload.filename ('${newItem.filename}'). Using filename from path.`);
|
||||
// 可以选择信任哪个,这里信任从路径提取的
|
||||
newItem.filename = filename;
|
||||
}
|
||||
addOrUpdateNodeInTree(parentPath, newItem);
|
||||
} else {
|
||||
// 如果后端未能提供更新信息,标记父节点需要重新加载
|
||||
// 如果后端未能提供更新信息,标记推断出的父节点需要重新加载
|
||||
const parentNode = findNodeByPath(fileTree, parentPath);
|
||||
if (parentNode) {
|
||||
parentNode.childrenLoaded = false;
|
||||
console.warn(`[SFTP ${instanceSessionId}] Upload success for ${message.path || filename} but no item details received. Marking parent ${parentPath} for reload.`);
|
||||
// 上传总是在当前目录,所以直接触发刷新
|
||||
loadDirectory(currentPathRef.value);
|
||||
console.warn(`[SFTP ${instanceSessionId}] Upload success for ${fullPath} but no item details received. Marking parent ${parentPath} for reload.`);
|
||||
// 如果上传发生在当前目录或其子目录,触发当前目录刷新可能有用
|
||||
if (parentPath === currentPathRef.value || parentPath.startsWith(currentPathRef.value + '/')) {
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[SFTP ${instanceSessionId}] Upload success for ${fullPath}, no item details, and parent node ${parentPath} not found in tree.`);
|
||||
// 可能需要刷新根目录或当前目录
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user