diff --git a/web/src/components/common/DirectoryPicker.tsx b/web/src/components/common/DirectoryPicker.tsx index ba61959..3cf9db6 100644 --- a/web/src/components/common/DirectoryPicker.tsx +++ b/web/src/components/common/DirectoryPicker.tsx @@ -1,4 +1,5 @@ -import { Button, Input, Modal, Space, Spin, Tree, Typography } from '@arco-design/web-react' +import { Button, Input, Message, Modal, Space, Spin, Tree, Typography } from '@arco-design/web-react' +import { IconFolder, IconFile } from '@arco-design/web-react/icon' import { useCallback, useState } from 'react' import { listNodeDirectory } from '../../services/nodes' import type { DirEntry } from '../../types/nodes' @@ -14,8 +15,10 @@ interface DirectoryPickerProps { interface TreeNodeData { key: string title: string + icon?: React.ReactNode isLeaf: boolean children?: TreeNodeData[] + loaded?: boolean } function entriesToTreeNodes(entries: DirEntry[], mode: 'directory' | 'file'): TreeNodeData[] { @@ -24,8 +27,8 @@ function entriesToTreeNodes(entries: DirEntry[], mode: 'directory' | 'file'): Tr .map((entry) => ({ key: entry.path, title: entry.name, + icon: entry.isDir ? : , isLeaf: !entry.isDir, - children: entry.isDir ? [] : undefined, })) } @@ -34,16 +37,15 @@ export function DirectoryPicker({ value, onChange, placeholder, mode = 'director const [treeData, setTreeData] = useState([]) const [loading, setLoading] = useState(false) const [selectedPath, setSelectedPath] = useState('') - const [error, setError] = useState('') const loadDirectory = useCallback( - async (path: string) => { + async (path: string): Promise => { if (nodeId === undefined) return [] try { const entries = await listNodeDirectory(nodeId, path) return entriesToTreeNodes(entries, mode) } catch { - setError('加载目录失败') + Message.error(`加载目录失败: ${path}`) return [] } }, @@ -53,7 +55,6 @@ export function DirectoryPicker({ value, onChange, placeholder, mode = 'director async function handleOpen() { setModalVisible(true) setSelectedPath(value || '') - setError('') setLoading(true) try { const rootNodes = await loadDirectory('/') @@ -63,21 +64,26 @@ export function DirectoryPicker({ value, onChange, placeholder, mode = 'director } } - async function handleLoadMore(node: TreeNodeData) { - const children = await loadDirectory(node.key) + // ArcoDesign Tree loadMore: node.props.dataRef 指向 treeData 中的原始对象 + async function handleLoadMore(treeNode: any): Promise { + const nodeKey = treeNode.props.dataRef?.key ?? treeNode.props._key + if (!nodeKey) return + + const children = await loadDirectory(nodeKey) + setTreeData((prev) => { - function updateChildren(nodes: TreeNodeData[]): TreeNodeData[] { + function insertChildren(nodes: TreeNodeData[]): TreeNodeData[] { return nodes.map((n) => { - if (n.key === node.key) { - return { ...n, children } + if (n.key === nodeKey) { + return { ...n, children, loaded: true } } - if (n.children) { - return { ...n, children: updateChildren(n.children) } + if (n.children && n.children.length > 0) { + return { ...n, children: insertChildren(n.children) } } return n }) } - return updateChildren(prev) + return insertChildren(prev) }) } @@ -88,6 +94,7 @@ export function DirectoryPicker({ value, onChange, placeholder, mode = 'director setModalVisible(false) } + // 没有 nodeId 时退化为普通输入框 if (nodeId === undefined) { return } @@ -108,30 +115,37 @@ export function DirectoryPicker({ value, onChange, placeholder, mode = 'director onOk={handleConfirm} okText="选择" cancelText="取消" - style={{ width: 520 }} + style={{ width: 560 }} okButtonProps={{ disabled: !selectedPath }} + unmountOnExit > - {error && {error}} {selectedPath && ( - - 已选择: {selectedPath} - +
+ + {selectedPath} + +
)} {loading ? ( - + ) : treeData.length === 0 ? ( - 目录为空 + + 目录为空 + ) : ( -
+
{ if (keys.length > 0) { setSelectedPath(keys[0] as string) } }} - selectedKeys={selectedPath ? [selectedPath] : []} - loadMore={(node: any) => handleLoadMore(node.props)} + loadMore={handleLoadMore} + icons={{ switcherIcon: }} />
)}