feat(remote): 下载改成本地面板选择目录

This commit is contained in:
2026-03-10 02:10:21 +08:00
parent 073abafdfd
commit 8839ec244a
10 changed files with 41 additions and 31 deletions

View File

@@ -351,13 +351,15 @@ ipcMain.handle("remote-upload-file", async (_event, serverHost, port, filePath,
return { success: false, error: error.message };
}
});
ipcMain.handle("remote-download-file", async (_event, serverHost, port, fileName, remotePath, password) => {
ipcMain.handle("remote-download-file", async (_event, serverHost, port, fileName, remotePath, localPath, password) => {
try {
log2.info("Remote download params:", { serverHost, port, fileName, remotePath, localPath, password });
const win = electronState.getMainWindow();
if (!win) {
throw new Error("No window found");
}
let url = `http://${serverHost}:${port}/api/files/${encodeURIComponent(fileName)}`;
const fullRemotePath = remotePath ? `${remotePath}\\${fileName}` : fileName;
let url = `http://${serverHost}:${port}/api/files/${encodeURIComponent(fullRemotePath)}`;
if (password) {
url += `?password=${encodeURIComponent(password)}`;
}
@@ -366,15 +368,13 @@ ipcMain.handle("remote-download-file", async (_event, serverHost, port, fileName
throw new Error(`Download failed: ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
const { filePath } = await dialog2.showSaveDialog(win, {
title: "\u4FDD\u5B58\u6587\u4EF6",
defaultPath: fileName
});
if (!filePath) {
return { success: false, canceled: true };
const targetDir = localPath || "C:\\";
const targetPath = path2.join(targetDir, fileName);
if (!fs2.existsSync(targetDir)) {
fs2.mkdirSync(targetDir, { recursive: true });
}
fs2.writeFileSync(filePath, Buffer.from(buffer));
return { success: true, filePath };
fs2.writeFileSync(targetPath, Buffer.from(buffer));
return { success: true, filePath: targetPath };
} catch (error) {
log2.error("Remote download failed:", error);
return { success: false, error: error.message };

File diff suppressed because one or more lines are too long

View File

@@ -23,6 +23,6 @@ import_electron.contextBridge.exposeInMainWorld("electronAPI", {
remoteFetchDrives: (serverHost, port, password) => import_electron.ipcRenderer.invoke("remote-fetch-drives", serverHost, port, password),
remoteFetchFiles: (serverHost, port, filePath, password) => import_electron.ipcRenderer.invoke("remote-fetch-files", serverHost, port, filePath, password),
remoteUploadFile: (serverHost, port, filePath, remotePath, password) => import_electron.ipcRenderer.invoke("remote-upload-file", serverHost, port, filePath, remotePath, password),
remoteDownloadFile: (serverHost, port, fileName, remotePath, password) => import_electron.ipcRenderer.invoke("remote-download-file", serverHost, port, fileName, remotePath, password)
remoteDownloadFile: (serverHost, port, fileName, remotePath, localPath, password) => import_electron.ipcRenderer.invoke("remote-download-file", serverHost, port, fileName, remotePath, localPath, password)
});
//# sourceMappingURL=preload.cjs.map

View File

@@ -1 +1 @@
{"version":3,"sources":["../electron/preload.ts"],"sourcesContent":["import { contextBridge, ipcRenderer } from 'electron'\r\n\r\nconsole.log('--- PRELOAD SCRIPT LOADED SUCCESSFULLY ---')\r\n\r\ncontextBridge.exposeInMainWorld('electronAPI', {\r\n exportPDF: (title: string, htmlContent?: string) => ipcRenderer.invoke('export-pdf', title, htmlContent),\r\n selectHtmlFile: () => ipcRenderer.invoke('select-html-file'),\r\n updateTitlebarButtons: (symbolColor: string) => ipcRenderer.invoke('update-titlebar-buttons', symbolColor),\r\n onRemoteClipboardSyncToRemote: (callback: () => void) => {\r\n ipcRenderer.on('remote-clipboard-sync-to-remote', callback);\r\n return () => ipcRenderer.removeListener('remote-clipboard-sync-to-remote', callback);\r\n },\r\n onRemoteClipboardSyncFromRemote: (callback: () => void) => {\r\n ipcRenderer.on('remote-clipboard-sync-from-remote', callback);\r\n return () => ipcRenderer.removeListener('remote-clipboard-sync-from-remote', callback);\r\n },\r\n onRemoteClipboardAutoSync: (callback: (text: string) => void) => {\r\n const handler = (_event: Electron.IpcRendererEvent, text: string) => callback(text);\r\n ipcRenderer.on('remote-clipboard-auto-sync', handler);\r\n return () => ipcRenderer.removeListener('remote-clipboard-auto-sync', handler);\r\n },\r\n clipboardReadText: () => ipcRenderer.invoke('clipboard-read-text'),\r\n clipboardWriteText: (text: string) => ipcRenderer.invoke('clipboard-write-text', text),\r\n remoteFetchDrives: (serverHost: string, port: number, password?: string) => \r\n ipcRenderer.invoke('remote-fetch-drives', serverHost, port, password),\r\n remoteFetchFiles: (serverHost: string, port: number, filePath: string, password?: string) => \r\n ipcRenderer.invoke('remote-fetch-files', serverHost, port, filePath, password),\r\n remoteUploadFile: (serverHost: string, port: number, filePath: string, remotePath: string, password?: string) => \r\n ipcRenderer.invoke('remote-upload-file', serverHost, port, filePath, remotePath, password),\r\n remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, password?: string) => \r\n ipcRenderer.invoke('remote-download-file', serverHost, port, fileName, remotePath, password),\r\n})\r\n"],"mappings":";AAAA,sBAA2C;AAE3C,QAAQ,IAAI,4CAA4C;AAExD,8BAAc,kBAAkB,eAAe;AAAA,EAC7C,WAAW,CAAC,OAAe,gBAAyB,4BAAY,OAAO,cAAc,OAAO,WAAW;AAAA,EACvG,gBAAgB,MAAM,4BAAY,OAAO,kBAAkB;AAAA,EAC3D,uBAAuB,CAAC,gBAAwB,4BAAY,OAAO,2BAA2B,WAAW;AAAA,EACzG,+BAA+B,CAAC,aAAyB;AACvD,gCAAY,GAAG,mCAAmC,QAAQ;AAC1D,WAAO,MAAM,4BAAY,eAAe,mCAAmC,QAAQ;AAAA,EACrF;AAAA,EACA,iCAAiC,CAAC,aAAyB;AACzD,gCAAY,GAAG,qCAAqC,QAAQ;AAC5D,WAAO,MAAM,4BAAY,eAAe,qCAAqC,QAAQ;AAAA,EACvF;AAAA,EACA,2BAA2B,CAAC,aAAqC;AAC/D,UAAM,UAAU,CAAC,QAAmC,SAAiB,SAAS,IAAI;AAClF,gCAAY,GAAG,8BAA8B,OAAO;AACpD,WAAO,MAAM,4BAAY,eAAe,8BAA8B,OAAO;AAAA,EAC/E;AAAA,EACA,mBAAmB,MAAM,4BAAY,OAAO,qBAAqB;AAAA,EACjE,oBAAoB,CAAC,SAAiB,4BAAY,OAAO,wBAAwB,IAAI;AAAA,EACrF,mBAAmB,CAAC,YAAoB,MAAc,aACpD,4BAAY,OAAO,uBAAuB,YAAY,MAAM,QAAQ;AAAA,EACtE,kBAAkB,CAAC,YAAoB,MAAc,UAAkB,aACrE,4BAAY,OAAO,sBAAsB,YAAY,MAAM,UAAU,QAAQ;AAAA,EAC/E,kBAAkB,CAAC,YAAoB,MAAc,UAAkB,YAAoB,aACzF,4BAAY,OAAO,sBAAsB,YAAY,MAAM,UAAU,YAAY,QAAQ;AAAA,EAC3F,oBAAoB,CAAC,YAAoB,MAAc,UAAkB,YAAoB,aAC3F,4BAAY,OAAO,wBAAwB,YAAY,MAAM,UAAU,YAAY,QAAQ;AAC/F,CAAC;","names":[]}
{"version":3,"sources":["../electron/preload.ts"],"sourcesContent":["import { contextBridge, ipcRenderer } from 'electron'\r\n\r\nconsole.log('--- PRELOAD SCRIPT LOADED SUCCESSFULLY ---')\r\n\r\ncontextBridge.exposeInMainWorld('electronAPI', {\r\n exportPDF: (title: string, htmlContent?: string) => ipcRenderer.invoke('export-pdf', title, htmlContent),\r\n selectHtmlFile: () => ipcRenderer.invoke('select-html-file'),\r\n updateTitlebarButtons: (symbolColor: string) => ipcRenderer.invoke('update-titlebar-buttons', symbolColor),\r\n onRemoteClipboardSyncToRemote: (callback: () => void) => {\r\n ipcRenderer.on('remote-clipboard-sync-to-remote', callback);\r\n return () => ipcRenderer.removeListener('remote-clipboard-sync-to-remote', callback);\r\n },\r\n onRemoteClipboardSyncFromRemote: (callback: () => void) => {\r\n ipcRenderer.on('remote-clipboard-sync-from-remote', callback);\r\n return () => ipcRenderer.removeListener('remote-clipboard-sync-from-remote', callback);\r\n },\r\n onRemoteClipboardAutoSync: (callback: (text: string) => void) => {\r\n const handler = (_event: Electron.IpcRendererEvent, text: string) => callback(text);\r\n ipcRenderer.on('remote-clipboard-auto-sync', handler);\r\n return () => ipcRenderer.removeListener('remote-clipboard-auto-sync', handler);\r\n },\r\n clipboardReadText: () => ipcRenderer.invoke('clipboard-read-text'),\r\n clipboardWriteText: (text: string) => ipcRenderer.invoke('clipboard-write-text', text),\r\n remoteFetchDrives: (serverHost: string, port: number, password?: string) => \r\n ipcRenderer.invoke('remote-fetch-drives', serverHost, port, password),\r\n remoteFetchFiles: (serverHost: string, port: number, filePath: string, password?: string) => \r\n ipcRenderer.invoke('remote-fetch-files', serverHost, port, filePath, password),\r\n remoteUploadFile: (serverHost: string, port: number, filePath: string, remotePath: string, password?: string) => \r\n ipcRenderer.invoke('remote-upload-file', serverHost, port, filePath, remotePath, password),\r\n remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, localPath: string, password?: string) => \r\n ipcRenderer.invoke('remote-download-file', serverHost, port, fileName, remotePath, localPath, password),\r\n})\r\n"],"mappings":";AAAA,sBAA2C;AAE3C,QAAQ,IAAI,4CAA4C;AAExD,8BAAc,kBAAkB,eAAe;AAAA,EAC7C,WAAW,CAAC,OAAe,gBAAyB,4BAAY,OAAO,cAAc,OAAO,WAAW;AAAA,EACvG,gBAAgB,MAAM,4BAAY,OAAO,kBAAkB;AAAA,EAC3D,uBAAuB,CAAC,gBAAwB,4BAAY,OAAO,2BAA2B,WAAW;AAAA,EACzG,+BAA+B,CAAC,aAAyB;AACvD,gCAAY,GAAG,mCAAmC,QAAQ;AAC1D,WAAO,MAAM,4BAAY,eAAe,mCAAmC,QAAQ;AAAA,EACrF;AAAA,EACA,iCAAiC,CAAC,aAAyB;AACzD,gCAAY,GAAG,qCAAqC,QAAQ;AAC5D,WAAO,MAAM,4BAAY,eAAe,qCAAqC,QAAQ;AAAA,EACvF;AAAA,EACA,2BAA2B,CAAC,aAAqC;AAC/D,UAAM,UAAU,CAAC,QAAmC,SAAiB,SAAS,IAAI;AAClF,gCAAY,GAAG,8BAA8B,OAAO;AACpD,WAAO,MAAM,4BAAY,eAAe,8BAA8B,OAAO;AAAA,EAC/E;AAAA,EACA,mBAAmB,MAAM,4BAAY,OAAO,qBAAqB;AAAA,EACjE,oBAAoB,CAAC,SAAiB,4BAAY,OAAO,wBAAwB,IAAI;AAAA,EACrF,mBAAmB,CAAC,YAAoB,MAAc,aACpD,4BAAY,OAAO,uBAAuB,YAAY,MAAM,QAAQ;AAAA,EACtE,kBAAkB,CAAC,YAAoB,MAAc,UAAkB,aACrE,4BAAY,OAAO,sBAAsB,YAAY,MAAM,UAAU,QAAQ;AAAA,EAC/E,kBAAkB,CAAC,YAAoB,MAAc,UAAkB,YAAoB,aACzF,4BAAY,OAAO,sBAAsB,YAAY,MAAM,UAAU,YAAY,QAAQ;AAAA,EAC3F,oBAAoB,CAAC,YAAoB,MAAc,UAAkB,YAAoB,WAAmB,aAC9G,4BAAY,OAAO,wBAAwB,YAAY,MAAM,UAAU,YAAY,WAAW,QAAQ;AAC1G,CAAC;","names":[]}

View File

@@ -255,15 +255,17 @@ ipcMain.handle('remote-upload-file', async (_event, serverHost: string, port: nu
}
});
ipcMain.handle('remote-download-file', async (_event, serverHost: string, port: number, fileName: string, remotePath: string, password?: string) => {
ipcMain.handle('remote-download-file', async (_event, serverHost: string, port: number, fileName: string, remotePath: string, localPath: string, password?: string) => {
try {
log.info('Remote download params:', { serverHost, port, fileName, remotePath, localPath, password });
const win = electronState.getMainWindow();
if (!win) {
throw new Error('No window found');
}
const fullPath = remotePath ? `${remotePath}\\${fileName}` : fileName;
let url = `http://${serverHost}:${port}/api/files/${encodeURIComponent(fullPath)}`;
const fullRemotePath = remotePath ? `${remotePath}\\${fileName}` : fileName;
let url = `http://${serverHost}:${port}/api/files/${encodeURIComponent(fullRemotePath)}`;
if (password) {
url += `?password=${encodeURIComponent(password)}`;
}
@@ -275,18 +277,16 @@ ipcMain.handle('remote-download-file', async (_event, serverHost: string, port:
const buffer = await response.arrayBuffer();
const { filePath } = await dialog.showSaveDialog(win, {
title: '保存文件',
defaultPath: fileName,
});
const targetDir = localPath || 'C:\\';
const targetPath = path.join(targetDir, fileName);
if (!filePath) {
return { success: false, canceled: true };
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
fs.writeFileSync(filePath, Buffer.from(buffer));
fs.writeFileSync(targetPath, Buffer.from(buffer));
return { success: true, filePath };
return { success: true, filePath: targetPath };
} catch (error: any) {
log.error('Remote download failed:', error);
return { success: false, error: error.message };

View File

@@ -27,6 +27,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
ipcRenderer.invoke('remote-fetch-files', serverHost, port, filePath, password),
remoteUploadFile: (serverHost: string, port: number, filePath: string, remotePath: string, password?: string) =>
ipcRenderer.invoke('remote-upload-file', serverHost, port, filePath, remotePath, password),
remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, password?: string) =>
ipcRenderer.invoke('remote-download-file', serverHost, port, fileName, remotePath, password),
remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, localPath: string, password?: string) =>
ipcRenderer.invoke('remote-download-file', serverHost, port, fileName, remotePath, localPath, password),
})

View File

@@ -115,11 +115,12 @@ export const downloadFileFromRemote = async (
port: number,
fileName: string,
remotePath: string,
localPath: string,
password?: string,
onProgress?: (progress: number) => void
): Promise<void> => {
onProgress?.(50)
const result = await window.electronAPI.remoteDownloadFile(serverHost, port, fileName, remotePath, password)
const result = await window.electronAPI.remoteDownloadFile(serverHost, port, fileName, remotePath, localPath, password)
onProgress?.(100)
if (!result.success) {
if (result.canceled) {

View File

@@ -17,6 +17,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
const [localSelected, setLocalSelected] = useState<FileItem | null>(null)
const [remoteSelected, setRemoteSelected] = useState<RemoteFileItem | null>(null)
const [remotePath, setRemotePath] = useState('')
const [localPath, setLocalPath] = useState('')
const [transfers, setTransfers] = useState<TransferItem[]>([])
const [transferring, setTransferring] = useState(false)
const [transferQueueHeight, setTransferQueueHeight] = useState(128)
@@ -67,7 +68,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
}, [localSelected, serverHost, port, remotePath, password])
const handleDownload = useCallback(async () => {
if (!remoteSelected) return
if (!remoteSelected || !localPath) return
setTransferring(true)
const transferId = Date.now().toString()
@@ -82,7 +83,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
setTransfers((prev) => [...prev, newTransfer])
try {
await downloadFileFromRemote(serverHost, port, remoteSelected.name, remotePath, password, (progress) => {
await downloadFileFromRemote(serverHost, port, remoteSelected.name, remotePath, localPath, password, (progress) => {
setTransfers((prev) =>
prev.map((t) => (t.id === transferId ? { ...t, progress } : t))
)
@@ -106,7 +107,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
} finally {
setTransferring(false)
}
}, [remoteSelected, serverHost, port])
}, [remoteSelected, serverHost, port, remotePath, localPath, password])
const handleClearTransfers = () => {
setTransfers((prev) => prev.filter((t) => t.status === 'transferring'))
@@ -153,6 +154,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
selectedFile={localSelected}
onSelect={setLocalSelected}
onUpload={handleUpload}
onPathChange={setLocalPath}
disabled={transferring}
/>
</div>

View File

@@ -8,6 +8,7 @@ interface LocalFilePanelProps {
selectedFile: FileItem | null
onSelect: (file: FileItem | null) => void
onUpload: () => void
onPathChange?: (path: string) => void
disabled?: boolean
}
@@ -15,6 +16,7 @@ export const LocalFilePanel: React.FC<LocalFilePanelProps> = ({
selectedFile,
onSelect,
onUpload,
onPathChange,
disabled,
}) => {
const [currentPath, setCurrentPath] = useState('')
@@ -50,6 +52,7 @@ export const LocalFilePanel: React.FC<LocalFilePanelProps> = ({
useEffect(() => {
if (isAtDrives) {
loadDrives()
onPathChange?.('')
} else {
loadFiles(currentPath)
}
@@ -64,6 +67,9 @@ export const LocalFilePanel: React.FC<LocalFilePanelProps> = ({
setCurrentPath(prevPath)
if (prevPath === '') {
setIsAtDrives(true)
onPathChange?.('')
} else {
onPathChange?.(prevPath)
}
onSelect(null)
}
@@ -74,6 +80,7 @@ export const LocalFilePanel: React.FC<LocalFilePanelProps> = ({
setPathHistory([...pathHistory, file.path])
setCurrentPath(file.path)
onSelect(null)
onPathChange?.(file.path)
}
const handleRefresh = () => {

View File

@@ -30,7 +30,7 @@ export interface ElectronAPI {
success: boolean
error?: string
}>
remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, password?: string) => Promise<{
remoteDownloadFile: (serverHost: string, port: number, fileName: string, remotePath: string, localPath: string, password?: string) => Promise<{
success: boolean
filePath?: string
error?: string