import { useQuery } from '@tanstack/react-query' import { useEffect, useRef, useState } from 'react' import { listDirectories } from '../api/client' import { useFocusTrap } from '../lib/useFocusTrap' interface DirectoryPickerDialogProps { open: boolean initialPath: string onSelect: (path: string) => void onClose: () => void } /** * 页面内的目录浏览器:从盘符开始逐级进入文件夹,最终把绝对路径回填到表单。 * 浏览器拿不到系统文件对话框的真实路径,因此目录浏览由后端提供列表接口。 */ export function DirectoryPickerDialog({ open, initialPath, onSelect, onClose, }: DirectoryPickerDialogProps) { const [currentPath, setCurrentPath] = useState(null) const [draftPath, setDraftPath] = useState('') const dialogRef = useRef(null) useFocusTrap(open, dialogRef, '[data-picker-path]') useEffect(() => { if (!open) { return } const start = initialPath.trim() setCurrentPath(start || null) setDraftPath(start) }, [initialPath, open]) const listing = useQuery({ queryKey: ['directories', currentPath], queryFn: ({ signal }) => listDirectories(currentPath ?? undefined, signal), enabled: open, retry: false, }) useEffect(() => { if (listing.data?.path) { setDraftPath(listing.data.path) } }, [listing.data?.path]) useEffect(() => { if (!open) { return } const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose() } } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) }, [onClose, open]) if (!open) { return null } const entries = listing.data?.entries ?? [] const currentDirectory = listing.data?.path ?? null const parent = listing.data?.parent ?? null const navigate = (path: string | null) => { setCurrentPath(path) setDraftPath(path ?? '') } return (
{ if (event.currentTarget === event.target) { onClose() } }} >

WORKING DIRECTORY

选择项目工作目录

{ event.preventDefault() navigate(draftPath.trim() || null) }} > setDraftPath(event.target.value)} placeholder="输入或粘贴绝对路径,例如 D:\Projects\sync" aria-label="目录路径" data-picker-path spellCheck={false} />
{listing.isPending ? (

正在读取目录…

) : null} {listing.isError ? (
{listing.error.message}
) : null} {!listing.isPending && !listing.isError && entries.length === 0 ? (

{currentDirectory ? '该目录下没有子文件夹' : '没有可用的盘符'}

) : null} {entries.length > 0 ? (
    {entries.map((entry) => (
  • ))}
) : null} {listing.data?.truncated ? (

文件夹过多,仅列出前 500 个。

) : null}
) }