修复文件管理器多个关键问题

- 修复侧边栏API路由问题:将数据API从fileManagerApi(8084)切换到authApi(8081)
- 实现PIN功能:添加文件固定/取消固定功能,支持右键菜单操作
- 修复FileWindow组件props传递错误:正确传递file对象和sshHost参数
- 添加侧边栏数据刷新机制:PIN/Recent/Shortcut操作后自动更新显示
- 修复目录树展开问题:handleItemClick正确传递folderPath参数
- 新增FileManagerSidebar组件:支持Recent、Pinned、Shortcuts和目录树

主要修复:
1. API路由从localhost:8084/ssh/file_manager/* 修正为 localhost:8081/ssh/file_manager/*
2. 双击文件不再报错"Cannot read properties of undefined (reading 'name')"
3. 侧边栏实时同步数据更新,提升用户体验
This commit is contained in:
ZacharyZcR
2025-09-17 09:55:42 +08:00
parent 7fc474b9e4
commit 4e915a1b3e
6 changed files with 636 additions and 6 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { FileManagerGrid } from "./FileManagerGrid";
import { FileManagerSidebar } from "./FileManagerSidebar";
import { FileManagerContextMenu } from "./FileManagerContextMenu";
import { useFileSelection } from "./hooks/useFileSelection";
import { useDragAndDrop } from "./hooks/useDragAndDrop";
@@ -37,7 +38,12 @@ import {
moveSSHItem,
connectSSH,
getSSHStatus,
identifySSHSymlink
identifySSHSymlink,
addRecentFile,
addPinnedFile,
removePinnedFile,
addFolderShortcut,
getPinnedFiles
} from "@/ui/main-axios.ts";
@@ -59,6 +65,8 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
const [sshSessionId, setSshSessionId] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [pinnedFiles, setPinnedFiles] = useState<Set<string>>(new Set());
const [sidebarRefreshTrigger, setSidebarRefreshTrigger] = useState(0);
// Context menu state
const [contextMenu, setContextMenu] = useState<{
@@ -1222,6 +1230,130 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
toast.success(t("fileManager.runningFile", { file: file.name }));
}
// 加载固定文件列表
async function loadPinnedFiles() {
if (!currentHost?.id) return;
try {
const pinnedData = await getPinnedFiles(currentHost.id);
const pinnedPaths = new Set(pinnedData.map((item: any) => item.path));
setPinnedFiles(pinnedPaths);
} catch (error) {
console.error('Failed to load pinned files:', error);
}
}
// PIN文件
async function handlePinFile(file: FileItem) {
if (!currentHost?.id) return;
try {
await addPinnedFile(currentHost.id, file.path, file.name);
setPinnedFiles(prev => new Set([...prev, file.path]));
setSidebarRefreshTrigger(prev => prev + 1); // 触发侧边栏刷新
toast.success(`文件"${file.name}"已固定`);
} catch (error) {
console.error('Failed to pin file:', error);
toast.error('固定文件失败');
}
}
// UNPIN文件
async function handleUnpinFile(file: FileItem) {
if (!currentHost?.id) return;
try {
await removePinnedFile(currentHost.id, file.path);
setPinnedFiles(prev => {
const newSet = new Set(prev);
newSet.delete(file.path);
return newSet;
});
setSidebarRefreshTrigger(prev => prev + 1); // 触发侧边栏刷新
toast.success(`文件"${file.name}"已取消固定`);
} catch (error) {
console.error('Failed to unpin file:', error);
toast.error('取消固定失败');
}
}
// 添加文件夹快捷方式
async function handleAddShortcut(path: string) {
if (!currentHost?.id) return;
try {
const folderName = path.split('/').pop() || path;
await addFolderShortcut(currentHost.id, path, folderName);
setSidebarRefreshTrigger(prev => prev + 1); // 触发侧边栏刷新
toast.success(`文件夹快捷方式"${folderName}"已添加`);
} catch (error) {
console.error('Failed to add shortcut:', error);
toast.error('添加快捷方式失败');
}
}
// 检查文件是否已固定
function isPinnedFile(file: FileItem): boolean {
return pinnedFiles.has(file.path);
}
// 记录最近访问的文件
async function recordRecentFile(file: FileItem) {
if (!currentHost?.id || file.type === 'directory') return;
try {
await addRecentFile(currentHost.id, file.path, file.name);
setSidebarRefreshTrigger(prev => prev + 1); // 触发侧边栏刷新
} catch (error) {
console.error('Failed to record recent file:', error);
}
}
// 处理文件打开
async function handleFileOpen(file: FileItem) {
if (file.type === 'directory') {
// 如果是目录,切换到该目录
setCurrentPath(file.path);
} else {
// 如果是文件,记录到最近访问并打开文件窗口
await recordRecentFile(file);
// 创建文件窗口
const windowCount = Date.now() % 10;
const offsetX = 100 + (windowCount * 30);
const offsetY = 100 + (windowCount * 30);
const createFileWindow = (windowId: string) => (
<FileWindow
windowId={windowId}
file={file}
sshHost={currentHost!}
sshSessionId={sshSessionId!}
initialX={offsetX}
initialY={offsetY}
/>
);
openWindow({
title: file.name,
x: offsetX,
y: offsetY,
width: 800,
height: 600,
isMaximized: false,
isMinimized: false,
component: createFileWindow
});
}
}
// 加载固定文件列表(当主机或连接改变时)
useEffect(() => {
if (currentHost?.id) {
loadPinnedFiles();
}
}, [currentHost?.id]);
// 过滤文件并添加新建的临时项目
let filteredFiles = files.filter(file =>
file.name.toLowerCase().includes(searchQuery.toLowerCase())
@@ -1347,8 +1479,22 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
</div>
{/* 主内容区域 */}
<div className="flex-1 relative" {...dragHandlers}>
<FileManagerGrid
<div className="flex-1 flex" {...dragHandlers}>
{/* 左侧边栏 */}
<div className="w-64 flex-shrink-0">
<FileManagerSidebar
currentHost={currentHost}
currentPath={currentPath}
onPathChange={setCurrentPath}
onLoadDirectory={loadDirectory}
sshSessionId={sshSessionId}
refreshTrigger={sidebarRefreshTrigger}
/>
</div>
{/* 右侧文件网格 */}
<div className="flex-1 relative">
<FileManagerGrid
files={filteredFiles}
selectedFiles={selectedFiles}
onFileSelect={() => {}} // 不再需要这个回调使用onSelectionChange
@@ -1407,8 +1553,13 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
onDragToDesktop={() => handleDragToDesktop(contextMenu.files)}
onOpenTerminal={(path) => handleOpenTerminal(path)}
onRunExecutable={(file) => handleRunExecutable(file)}
onPinFile={handlePinFile}
onUnpinFile={handleUnpinFile}
onAddShortcut={handleAddShortcut}
isPinned={isPinnedFile}
currentPath={currentPath}
/>
</div>
</div>
</div>