fix: 树状视图展开文件夹为空 - 递归拍平渲染

原模板只遍历 treeData 根级节点, 从不渲染 node.children, 导致展开后为空。
改为 flattenedTree computed 将树拍平为列表:
- 只包含已展开文件夹的子节点
- 每项带 level 用于缩进 (paddingLeft)
- 文件夹和叶子节点在列表中按正确顺序排列
- 叶子节点支持 active/selected 高亮和右键菜单
This commit is contained in:
2026-07-11 15:13:51 +08:00
parent 4602ec5979
commit fd40207af5
+40 -29
View File
@@ -474,6 +474,27 @@ function isFolderExpanded(path: string): boolean {
return expandedFolders.value.has(path)
}
interface FlatTreeNode {
node: TreeNode
level: number
expanded: boolean
}
const flattenedTree = computed<FlatTreeNode[]>(() => {
const result: FlatTreeNode[] = []
function walk(nodes: TreeNode[], level: number) {
for (const node of nodes) {
const expanded = isFolderExpanded(node.fullPath)
result.push({ node, level, expanded })
if (!node.isLeaf && expanded) {
walk(node.children, level + 1)
}
}
}
walk(treeData.value, 0)
return result
})
function isKeySelected(keyName: string): boolean {
return keyStore.selectedKey === keyName && !multiSelect.value
}
@@ -674,30 +695,32 @@ onUnmounted(() => {
<!-- Tree View -->
<div v-else class="key-tree">
<template v-for="node in treeData" :key="node.fullPath">
<template v-for="item in flattenedTree" :key="item.node.fullPath">
<!-- Folder -->
<div
v-if="!node.isLeaf"
v-if="!item.node.isLeaf"
class="tree-item folder-item"
@click="toggleFolder(node.fullPath)"
:style="{ paddingLeft: `${12 + item.level * 16}px` }"
@click="toggleFolder(item.node.fullPath)"
>
<el-icon class="tree-arrow" :class="{ expanded: isFolderExpanded(node.fullPath) }">
<el-icon class="tree-arrow" :class="{ expanded: item.expanded }">
<ArrowRight />
</el-icon>
<el-icon class="tree-icon folder-icon"><Folder /></el-icon>
<span class="tree-name">{{ formatKeyName(node.name) }}</span>
<span class="tree-count">{{ node.count }}</span>
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
<span class="tree-count">{{ item.node.count }}</span>
</div>
<div v-else class="tree-children" :class="{ hidden: !isFolderExpanded(node.fullPath) }">
<div
class="tree-item leaf-item"
@click="selectKey(node.fullPath, $event)"
@contextmenu.prevent="onKeyContextMenu($event, node.fullPath)"
>
<span class="tree-spacer"></span>
<el-icon class="tree-icon"><Document /></el-icon>
<span class="tree-name">{{ formatKeyName(node.name) }}</span>
</div>
<!-- Leaf -->
<div
v-else
class="tree-item leaf-item"
:class="{ active: isKeySelected(item.node.fullPath), selected: isKeyMultiSelected(item.node.fullPath) }"
:style="{ paddingLeft: `${12 + item.level * 16}px` }"
@click="selectKey(item.node.fullPath, $event)"
@contextmenu.prevent="onKeyContextMenu($event, item.node.fullPath)"
>
<el-icon class="tree-icon"><Document /></el-icon>
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
</div>
</template>
</div>
@@ -945,20 +968,8 @@ onUnmounted(() => {
font-weight: 500;
}
.tree-children {
padding-left: 20px;
}
.tree-children.hidden {
display: none;
}
.tree-spacer {
width: 16px;
}
.leaf-item {
padding-left: 12px;
/* padding handled by inline style based on tree depth */
}
.key-footer {