fix: 树状视图展开文件夹为空 - 递归拍平渲染
原模板只遍历 treeData 根级节点, 从不渲染 node.children, 导致展开后为空。 改为 flattenedTree computed 将树拍平为列表: - 只包含已展开文件夹的子节点 - 每项带 level 用于缩进 (paddingLeft) - 文件夹和叶子节点在列表中按正确顺序排列 - 叶子节点支持 active/selected 高亮和右键菜单
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user