vue实现树形菜单组件
Vue 树形菜单组件实现方法
递归组件实现基础树形结构
使用 Vue 的递归组件特性可以轻松实现树形菜单。需要定义一个能够调用自身的组件。
<template>
<ul>
<li v-for="item in treeData" :key="item.id">
{{ item.label }}
<tree-menu
v-if="item.children && item.children.length"
:treeData="item.children"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeMenu',
props: {
treeData: {
type: Array,
required: true
}
}
}
</script>
添加展开/折叠功能
通过添加一个 isOpen 状态来控制子菜单的显示与隐藏。

<template>
<ul>
<li v-for="item in treeData" :key="item.id">
<span @click="toggle(item)">
{{ item.label }}
<span v-if="item.children && item.children.length">
{{ item.isOpen ? '[-]' : '[+]' }}
</span>
</span>
<tree-menu
v-if="item.children && item.children.length && item.isOpen"
:treeData="item.children"
@toggle="toggle"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeMenu',
props: {
treeData: {
type: Array,
required: true
}
},
methods: {
toggle(item) {
this.$emit('toggle', item)
}
}
}
</script>
添加选中状态
在树形菜单中实现单选或多选功能。
<template>
<ul>
<li
v-for="item in treeData"
:key="item.id"
:class="{ 'selected': item.isSelected }"
@click="selectItem(item)"
>
<span @click.stop="toggle(item)">
{{ item.label }}
<span v-if="item.children && item.children.length">
{{ item.isOpen ? '[-]' : '[+]' }}
</span>
</span>
<tree-menu
v-if="item.children && item.children.length && item.isOpen"
:treeData="item.children"
@toggle="toggle"
@select="selectItem"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeMenu',
props: {
treeData: {
type: Array,
required: true
}
},
methods: {
toggle(item) {
this.$emit('toggle', item)
},
selectItem(item) {
this.$emit('select', item)
}
}
}
</script>
<style>
.selected {
background-color: #f0f0f0;
}
</style>
动态加载数据
对于大型树形结构,可以实现按需加载子节点数据。

<template>
<ul>
<li v-for="item in treeData" :key="item.id">
<span @click="toggle(item)">
{{ item.label }}
<span v-if="item.hasChildren && !item.children">
[>]
</span>
<span v-else-if="item.children && item.children.length">
{{ item.isOpen ? '[-]' : '[+]' }}
</span>
</span>
<tree-menu
v-if="item.children && item.children.length && item.isOpen"
:treeData="item.children"
@toggle="toggle"
@load="loadChildren"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeMenu',
props: {
treeData: {
type: Array,
required: true
}
},
methods: {
toggle(item) {
if (item.hasChildren && !item.children) {
this.$emit('load', item)
} else {
this.$emit('toggle', item)
}
}
}
}
</script>
添加拖拽功能
实现树形菜单节点的拖拽排序功能。
<template>
<ul>
<li
v-for="item in treeData"
:key="item.id"
draggable="true"
@dragstart="dragStart(item)"
@dragover.prevent
@drop="drop(item)"
>
<span @click="toggle(item)">
{{ item.label }}
<span v-if="item.children && item.children.length">
{{ item.isOpen ? '[-]' : '[+]' }}
</span>
</span>
<tree-menu
v-if="item.children && item.children.length && item.isOpen"
:treeData="item.children"
@toggle="toggle"
@dragStart="dragStart"
@drop="drop"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeMenu',
props: {
treeData: {
type: Array,
required: true
}
},
data() {
return {
draggedItem: null
}
},
methods: {
toggle(item) {
this.$emit('toggle', item)
},
dragStart(item) {
this.draggedItem = item
},
drop(targetItem) {
if (this.draggedItem !== targetItem) {
this.$emit('drop', { draggedItem: this.draggedItem, targetItem })
}
}
}
}
</script>
优化树形菜单性能
对于大型树形结构,可以使用虚拟滚动来提高性能。
<template>
<div class="tree-container">
<div
v-for="item in visibleItems"
:key="item.id"
:style="{ paddingLeft: `${item.level * 20}px` }"
>
<span @click="toggle(item)">
{{ item.label }}
<span v-if="item.hasChildren">
{{ item.isOpen ? '[-]' : '[+]' }}
</span>
</span>
</div>
</div>
</template>
<script>
export default {
props: {
treeData: {
type: Array,
required: true
}
},
computed: {
visibleItems() {
// 实现扁平化处理,只计算当前可见的节点
return this.flattenTree(this.treeData)
}
},
methods: {
flattenTree(nodes, level = 0, result = []) {
nodes.forEach(node => {
result.push({ ...node, level })
if (node.isOpen && node.children) {
this.flattenTree(node.children, level + 1, result)
}
})
return result
},
toggle(item) {
this.$emit('toggle', item)
}
}
}
</script>
<style>
.tree-container {
height: 500px;
overflow-y: auto;
}
</style>






