vue实现树穿梭
Vue 实现树穿梭功能
树穿梭功能通常用于在两个树形结构之间移动节点,常见于权限管理、数据分类等场景。以下是基于 Vue 的实现方法:
使用 Element UI 的 Tree 组件
Element UI 提供了成熟的 Tree 和 Transfer 组件,可以组合实现树穿梭功能。
安装 Element UI:
npm install element-ui
示例代码:
<template>
<div>
<el-transfer
v-model="selectedNodes"
:data="treeData"
:props="treeProps"
:left-default-checked="leftChecked"
:right-default-checked="rightChecked"
filterable
:titles="['源树', '目标树']"
>
<el-tree
slot="left"
ref="leftTree"
:data="leftTreeData"
:props="treeProps"
show-checkbox
node-key="id"
@check-change="handleLeftCheckChange"
></el-tree>
<el-tree
slot="right"
ref="rightTree"
:data="rightTreeData"
:props="treeProps"
show-checkbox
node-key="id"
@check-change="handleRightCheckChange"
></el-tree>
</el-transfer>
</div>
</template>
<script>
export default {
data() {
return {
leftTreeData: [
{ id: 1, label: '一级 1', children: [{ id: 4, label: '二级 1-1' }] },
{ id: 2, label: '一级 2', children: [{ id: 5, label: '二级 2-1' }] },
{ id: 3, label: '一级 3', children: [{ id: 6, label: '二级 3-1' }] }
],
rightTreeData: [],
selectedNodes: [],
treeProps: {
label: 'label',
children: 'children'
},
leftChecked: [],
rightChecked: []
}
},
methods: {
handleLeftCheckChange(node, checked) {
if (checked) {
this.leftChecked = [...this.leftChecked, node.id]
} else {
this.leftChecked = this.leftChecked.filter(id => id !== node.id)
}
},
handleRightCheckChange(node, checked) {
if (checked) {
this.rightChecked = [...this.rightChecked, node.id]
} else {
this.rightChecked = this.rightChecked.filter(id => id !== node.id)
}
}
},
computed: {
treeData() {
return [...this.leftTreeData, ...this.rightTreeData]
}
}
}
</script>
自定义实现树穿梭
如果需要更自定义的功能,可以完全自己实现:
<template>
<div class="tree-transfer">
<div class="tree-container">
<h3>源树</h3>
<tree
:data="sourceTree"
:props="treeProps"
show-checkbox
node-key="id"
ref="sourceTree"
@check-change="handleSourceCheckChange"
></tree>
</div>
<div class="transfer-buttons">
<button @click="moveToTarget">→</button>
<button @click="moveToSource">←</button>
</div>
<div class="tree-container">
<h3>目标树</h3>
<tree
:data="targetTree"
:props="treeProps"
show-checkbox
node-key="id"
ref="targetTree"
@check-change="handleTargetCheckChange"
></tree>
</div>
</div>
</template>
<script>
export default {
data() {
return {
sourceTree: [
{ id: 1, label: '节点1', children: [{ id: 2, label: '子节点1' }] },
{ id: 3, label: '节点2' }
],
targetTree: [],
treeProps: {
label: 'label',
children: 'children'
},
checkedSourceNodes: [],
checkedTargetNodes: []
}
},
methods: {
handleSourceCheckChange(node, checked) {
if (checked) {
this.checkedSourceNodes.push(node.id)
} else {
this.checkedSourceNodes = this.checkedSourceNodes.filter(id => id !== node.id)
}
},
handleTargetCheckChange(node, checked) {
if (checked) {
this.checkedTargetNodes.push(node.id)
} else {
this.checkedTargetNodes = this.checkedTargetNodes.filter(id => id !== node.id)
}
},
moveToTarget() {
const nodesToMove = this.$refs.sourceTree.getCheckedNodes()
this.targetTree = [...this.targetTree, ...nodesToMove]
this.sourceTree = this.removeNodes(this.sourceTree, this.checkedSourceNodes)
this.checkedSourceNodes = []
},
moveToSource() {
const nodesToMove = this.$refs.targetTree.getCheckedNodes()
this.sourceTree = [...this.sourceTree, ...nodesToMove]
this.targetTree = this.removeNodes(this.targetTree, this.checkedTargetNodes)
this.checkedTargetNodes = []
},
removeNodes(tree, ids) {
return tree.filter(node => {
if (ids.includes(node.id)) return false
if (node.children) {
node.children = this.removeNodes(node.children, ids)
}
return true
})
}
}
}
</script>
<style>
.tree-transfer {
display: flex;
justify-content: space-between;
}
.tree-container {
width: 45%;
}
.transfer-buttons {
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
关键实现要点
-
双向数据绑定:维护源树和目标树两个数据源,通过操作这两个数组实现节点移动。
-
节点选择:利用 Tree 组件的复选框功能,记录选中的节点。
-
节点移动:将选中节点从源树移到目标树,或反向移动。
-
树形结构处理:递归处理子节点,确保移动时保持树形结构完整。
-
性能优化:对于大型树结构,考虑使用虚拟滚动或懒加载技术。
注意事项
-
节点标识:确保每个节点有唯一标识符(如id),便于操作和查找。
-
状态同步:移动节点后及时清空已选节点列表。
-
深度复制:移动节点时进行深拷贝,避免引用问题。
-
自定义样式:根据需求调整穿梭框和按钮的样式布局。
以上两种方法都可以实现树穿梭功能,Element UI 方案更快捷,自定义方案更灵活。可以根据项目需求选择适合的实现方式。







