From 2ace5a535262573d7b62db3e10f2b1ed441e7fb8 Mon Sep 17 00:00:00 2001 From: Awuqing <3184394176@qq.com> Date: Tue, 31 Mar 2026 00:32:02 +0800 Subject: [PATCH] fix: directory picker cannot navigate into subdirectories (#19) Root cause: ArcoDesign Tree loadMore callback receives NodeInstance where the key is at node.props.dataRef.key, not node.props.key. The old code passed node.props directly which resulted in undefined key, causing child directory loading to silently fail. Fix: - Access node key via node.props.dataRef?.key ?? node.props._key - Add showLine + blockNode + folder icons for better visual hierarchy - Add path display with copy button in selection modal - Add unmountOnExit to reset state on close Closes #19 --- web/src/components/common/DirectoryPicker.tsx | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) 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: }} />
)}