在Vue项目中实现跨树批量拖拽功能,需结合多选、拖拽事件监听及跨树数据交互,以下是具体实现方案及代码示例:
一、基础配置
1.双树结构与多选支持
设置左右两棵el-tree,分别绑定不同数据源,开启多选和拖拽功能:
<!-- 左侧树(可拖出) -->
<el-tree
:data="leftData"
node-key="id"
show-checkbox
draggable
:allow-drop="() => false" // 禁止内部拖拽
@node-drag-start="handleLeftDragStart"
@check-change="handleCheckChange"
/>
<!-- 右侧树(可拖入) -->
<el-tree
:data="rightData"
node-key="id"
draggable
:allow-drop="allowRightDrop"
@node-drop="handleRightDrop"
/>
2.数据初始化
定义左右树数据及多选存储变量:
data() {
return {
leftData: [ /* 左侧树数据 */ ],
rightData: [ /* 右侧树数据 */ ],
checkedNodes: [], // 存储左侧勾选的节点
draggingNodes: [] // 当前拖拽中的节点
};
}
二、跨树拖拽实现
1.勾选节点存储
通过@check-change事件收集多选节点:
handleCheckChange(currentNode, isChecked) {
if (isChecked) {
this.checkedNodes.push(currentNode);
} else {
const index = this.checkedNodes.findIndex(n => n.id === currentNode.id);
this.checkedNodes.splice(index, 1);
}
}
2.拖拽开始事件
触发拖拽时,绑定待移动的节点集合:
handleLeftDragStart(node) {
if (this.checkedNodes.length === 0) {
this.draggingNodes = [node]; // 未多选时默认当前节点
} else {
this.draggingNodes = this.checkedNodes;
}
}
3.右侧树拖入校验
在右侧树中定义allow-drop方法,限制拖入条件(如层级、类型):
allowRightDrop(draggingNode, dropNode, type) {
// 校验所有拖拽节点是否允许放入(如只能放入同级或特定父级)
return this.draggingNodes.every(node =>
node.level <= 2 && // 限制最大层级
node.type !== 'system' // 限制类型
);
}
4.拖拽释放处理
在右侧树的@node-drop事件中更新数据:
async handleRightDrop(draggingNode, dropNode, type) {
// 1. 深拷贝拖拽节点数据
const clonedNodes = JSON.parse(JSON.stringify(this.draggingNodes));
// 2. 从左侧树移除选中节点
this.leftData = this.removeNodesFromTree(this.leftData, this.draggingNodes);
// 3. 插入到右侧树目标位置
this.rightData = this.insertNodesToTree(
this.rightData,
clonedNodes,
dropNode.parent ? dropNode.parent.id : null, // 目标父节点ID
type // 'before'/'after'/'inner'
);
// 4. 调用后端接口同步
await this.$api.batchMoveNodes({
movedIds: this.draggingNodes.map(n => n.id),
targetParentId: dropNode.parent?.id || null
});
}
三、核心工具方法
1.节点移除逻辑
递归删除左侧树中被拖走的节点:
removeNodesFromTree(tree, nodes) {
const nodeIds = nodes.map(n => n.id);
const filterFn = (data) => {
return data.filter(item => {
if (nodeIds.includes(item.id)) return false;
if (item.children) item.children = filterFn(item.children);
return true;
});
};
return filterFn(JSON.parse(JSON.stringify(tree)));
}
2.节点插入逻辑
根据目标位置插入到右侧树:
insertNodesToTree(tree, nodes, targetParentId, insertType) {
const clonedTree = JSON.parse(JSON.stringify(tree));
const findAndInsert = (data) => {
return data.map(item => {
if (item.id === targetParentId) {
if (!item.children) item.children = [];
// 根据类型插入(before/after/inner)
if (insertType === 'inner') {
item.children.push(...nodes);
} else {
const index = data.findIndex(i => i.id === targetParentId);
data.splice(index + (insertType === 'after' ? 1 : 0), 0, ...nodes);
}
}
if (item.children) findAndInsert(item.children);
return item;
});
};
return findAndInsert(clonedTree);
}
四、交互优化
1. 拖拽视觉反馈
添加拖拽时的CSS样式提示(如占位符高亮):
.el-tree-node__content.drag-over {
background: #f0f7ff;
border: 1px dashed #409eff;
}
2.选提示
未勾选时拖拽单个节点给出提示:
handleLeftDragStart(node) {
if (this.checkedNodes.length === 0) {
this.$message.info('未选择节点,默认拖拽当前节点');
this.draggingNodes = [node];
}
}
通过上述方案,可实现左侧树多选节点批量拖拽至右侧树的功能,同时支持数据校验、实时同步及交互优化。