@@ -18,7 +18,7 @@ import {
1818 AlertDialogTitle ,
1919} from "@/components/ui/alert-dialog"
2020import { IconCopy , IconDotsVertical , IconDownload , IconFile , IconFolder , IconReload , IconTrash , IconTransfer , IconUpload } from "@tabler/icons-react"
21- import { normalizePath , downloadFile } from "@/utils/common"
21+ import { normalizePath } from "@/utils/common"
2222import { apiRequest } from "@/utils/requestUtils"
2323import { toast } from "sonner"
2424import { RepoFileEntryMode , type RepoFileStatus } from "./task-shared"
@@ -27,6 +27,13 @@ import CreateFileDialog from "@/components/console/files/create-file"
2727import UploadFileDialog from "@/components/console/files/upload-file"
2828import CopyFileDialog from "@/components/console/files/copy"
2929import MoveFileDialog from "@/components/console/files/move"
30+ import { FileDownloadDialog } from "./file-download-dialog"
31+
32+ type WindowWithSaveFilePicker = Window & {
33+ showSaveFilePicker ?: ( options ?: {
34+ suggestedName ?: string
35+ } ) => Promise < FileSystemFileHandle >
36+ }
3037
3138interface FileActionsDropdownProps {
3239 file : RepoFileStatus
@@ -48,12 +55,14 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
4855 const [ uploadFileDialogOpen , setUploadFileDialogOpen ] = useState ( false )
4956 const [ copyFileDialogOpen , setCopyFileDialogOpen ] = useState ( false )
5057 const [ moveFileDialogOpen , setMoveFileDialogOpen ] = useState ( false )
58+ const [ downloadDialogOpen , setDownloadDialogOpen ] = useState ( false )
59+ const [ downloadFileHandle , setDownloadFileHandle ] = useState < FileSystemFileHandle | null > ( null )
5160
5261 const isDirectory = file . entry_mode === RepoFileEntryMode . RepoEntryModeTree
5362 // 完整路径用于 API 调用
5463 const filePath = normalizePath ( '/workspace/' + file . path )
55- // 显示路径用于界面展示(不含 /workspace 前缀)
56- const displayPath = file . path
64+ // 展示路径统一使用完整工作区路径
65+ const displayPath = filePath
5766 const downloadFilename = isDirectory ? `${ file . name } .zip` : file . name
5867 const fileType = isDirectory ? '目录' : '文件'
5968
@@ -74,6 +83,26 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
7483 setDeleteDialogOpen ( false )
7584 }
7685
86+ const handleDownloadClick = async ( ) => {
87+ let nextFileHandle : FileSystemFileHandle | null = null
88+ const typedWindow = window as WindowWithSaveFilePicker
89+
90+ if ( typeof typedWindow . showSaveFilePicker === "function" ) {
91+ try {
92+ nextFileHandle = await typedWindow . showSaveFilePicker ( {
93+ suggestedName : downloadFilename ,
94+ } )
95+ } catch ( error ) {
96+ if ( error instanceof DOMException && error . name === "AbortError" ) {
97+ return
98+ }
99+ }
100+ }
101+
102+ setDownloadFileHandle ( nextFileHandle )
103+ setDownloadDialogOpen ( true )
104+ }
105+
77106 return (
78107 < >
79108 < DropdownMenu >
@@ -128,13 +157,8 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
128157 移动
129158 </ DropdownMenuItem >
130159 ) }
131- < DropdownMenuItem onClick = { async ( ) => {
132- if ( ! envid ) return
133- try {
134- await downloadFile ( envid , filePath , downloadFilename )
135- } catch ( error ) {
136- toast . error ( '下载失败:' + ( error instanceof Error ? error . message : '未知错误' ) )
137- }
160+ < DropdownMenuItem onClick = { ( ) => {
161+ void handleDownloadClick ( )
138162 } } >
139163 < IconDownload className = "h-4 w-4" />
140164 下载
@@ -170,14 +194,32 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
170194 </ AlertDialogContent >
171195 </ AlertDialog >
172196
197+ { envid && (
198+ < FileDownloadDialog
199+ open = { downloadDialogOpen }
200+ onOpenChange = { ( open ) => {
201+ setDownloadDialogOpen ( open )
202+ if ( ! open ) {
203+ setDownloadFileHandle ( null )
204+ }
205+ } }
206+ envid = { envid }
207+ filePath = { filePath }
208+ displayPath = { displayPath }
209+ downloadFilename = { downloadFilename }
210+ fileName = { file . name }
211+ fileType = { fileType }
212+ fileHandle = { downloadFileHandle }
213+ />
214+ ) }
215+
173216 { /* 创建文件夹对话框 */ }
174217 { envid && (
175218 < CreateFolderDialog
176219 open = { createFolderDialogOpen }
177220 onOpenChange = { setCreateFolderDialogOpen }
178221 targetDir = { displayPath }
179222 envid = { envid }
180- baseDir = "/workspace"
181223 onSuccess = { onSuccess }
182224 />
183225 ) }
@@ -189,7 +231,6 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
189231 onOpenChange = { setCreateFileDialogOpen }
190232 targetDir = { displayPath }
191233 envid = { envid }
192- baseDir = "/workspace"
193234 onSuccess = { onSuccess }
194235 />
195236 ) }
@@ -201,7 +242,6 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
201242 onOpenChange = { setUploadFileDialogOpen }
202243 targetDir = { displayPath }
203244 envid = { envid }
204- baseDir = "/workspace"
205245 onSuccess = { onSuccess }
206246 />
207247 ) }
@@ -213,7 +253,6 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
213253 onOpenChange = { setCopyFileDialogOpen }
214254 sourcePath = { displayPath }
215255 envid = { envid }
216- baseDir = "/workspace"
217256 onSuccess = { onSuccess }
218257 />
219258 ) }
@@ -225,7 +264,6 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
225264 onOpenChange = { setMoveFileDialogOpen }
226265 sourcePath = { displayPath }
227266 envid = { envid }
228- baseDir = "/workspace"
229267 onSuccess = { onSuccess }
230268 />
231269 ) }
0 commit comments