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) 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 { function isKeySelected(keyName: string): boolean {
return keyStore.selectedKey === keyName && !multiSelect.value return keyStore.selectedKey === keyName && !multiSelect.value
} }
@@ -674,30 +695,32 @@ onUnmounted(() => {
<!-- Tree View --> <!-- Tree View -->
<div v-else class="key-tree"> <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 --> <!-- Folder -->
<div <div
v-if="!node.isLeaf" v-if="!item.node.isLeaf"
class="tree-item folder-item" 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 /> <ArrowRight />
</el-icon> </el-icon>
<el-icon class="tree-icon folder-icon"><Folder /></el-icon> <el-icon class="tree-icon folder-icon"><Folder /></el-icon>
<span class="tree-name">{{ formatKeyName(node.name) }}</span> <span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
<span class="tree-count">{{ node.count }}</span> <span class="tree-count">{{ item.node.count }}</span>
</div> </div>
<div v-else class="tree-children" :class="{ hidden: !isFolderExpanded(node.fullPath) }"> <!-- Leaf -->
<div <div
class="tree-item leaf-item" v-else
@click="selectKey(node.fullPath, $event)" class="tree-item leaf-item"
@contextmenu.prevent="onKeyContextMenu($event, node.fullPath)" :class="{ active: isKeySelected(item.node.fullPath), selected: isKeyMultiSelected(item.node.fullPath) }"
> :style="{ paddingLeft: `${12 + item.level * 16}px` }"
<span class="tree-spacer"></span> @click="selectKey(item.node.fullPath, $event)"
<el-icon class="tree-icon"><Document /></el-icon> @contextmenu.prevent="onKeyContextMenu($event, item.node.fullPath)"
<span class="tree-name">{{ formatKeyName(node.name) }}</span> >
</div> <el-icon class="tree-icon"><Document /></el-icon>
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
</div> </div>
</template> </template>
</div> </div>
@@ -945,20 +968,8 @@ onUnmounted(() => {
font-weight: 500; font-weight: 500;
} }
.tree-children {
padding-left: 20px;
}
.tree-children.hidden {
display: none;
}
.tree-spacer {
width: 16px;
}
.leaf-item { .leaf-item {
padding-left: 12px; /* padding handled by inline style based on tree depth */
} }
.key-footer { .key-footer {