【vue项目el-tree跨树批量拖拽】

在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];
  }
}

通过上述方案,可实现左侧树多选节点批量拖拽至右侧树的功能,同时支持数据校验、实时同步及交互优化‌。

### 实现 Element UI 的 el-tree 拖拽功能 以下是基于 Element UI 的 `el-tree` 组件实现拖拽的具体方法。此方案通过监听拖拽事件以及手动调整数据来完成节点移动的功能。 #### 1. HTML 结构 创建两个独立的 `el-tree` 容器用于模拟不同的结构: ```html <div class="tree-container"> <!-- 左侧 --> <div class="tree-box"> <h4>源</h4> <el-tree ref="sourceTree" :data="sourceData" node-key="id" draggable @node-drag-start="handleDragStart" @node-drop="handleDrop($event, 'source')" /> </div> <!-- 右侧 --> <div class="tree-box"> <h4>目标</h4> <el-tree ref="targetTree" :data="targetData" node-key="id" draggable @node-drag-end="handleDragEnd" @node-drop="handleDrop($event, 'target')" /> </div> </div> ``` #### 2. 样式设置 为了更好地展示两棵的效果,可以通过简单的 CSS 来美化界面布局: ```css .tree-container { display: flex; justify-content: space-around; } .tree-box { width: 45%; border: 1px solid #ddd; padding: 10px; border-radius: 4px; } ``` #### 3. 数据初始化与逻辑处理 在 Vue 的脚本部分定义初始数据和相应的事件处理器: ```javascript export default { data() { return { sourceData: [ { id: 1, label: '节点一' }, { id: 2, label: '节点二', children: [{ id: 3, label: '子节点二-1' }] } ], targetData: [] }; }, methods: { // 开始拖拽时触发 handleDragStart(node) { this.draggingNode = node; // 记录当前被拖拽的节点 }, // 拖拽结束时触发 handleDragEnd() { delete this.draggingNode; // 清除记录 }, // 拖拽完成后触发 handleDrop(dropInfo, treeType) { const { node, dragNode, dropType } = dropInfo; if (dropType === 'none') return; // 如果没有合法放置区域,则忽略操作 let targetArray = []; if (treeType === 'source') { targetArray = this.sourceData; } else if (treeType === 'target') { targetArray = this.targetData; } // 获取父级节点索引 const parentKey = node.parent ? node.parent.data.id : null; const parentNode = this.getNodeByKey(parentKey, targetArray); const isRootDrop = !parentNode || parentNode.level === 0; // 插入到指定位置 if (isRootDrop && dropType === 'before') { targetArray.unshift(dragNode.data); // 添加至顶部 } else if (isRootDrop && dropType === 'after') { targetArray.push(dragNode.data); // 添加到底部 } else if (!isRootDrop) { if (dropType === 'inner') { parentNode.children = parentNode.children || []; // 确保存在子节点数组 parentNode.children.push(dragNode.data); // 移动到内部 } else if (dropType === 'before') { const index = parentNode.parent.children.indexOf(parentNode); parentNode.parent.children.splice(index, 0, dragNode.data); // 放置在兄弟节点之前 } else if (dropType === 'after') { const index = parentNode.parent.children.indexOf(parentNode); parentNode.parent.children.splice(index + 1, 0, dragNode.data); // 放置在兄弟节点之后 } } // 删除原位置中的节点 if (this.isInSource(treeType)) { this.removeTreeNode(this.sourceData, dragNode.data.id); } else { this.removeTreeNode(this.targetData, dragNode.data.id); } }, // 查找特定 key 的节点 getNodeByKey(key, array) { let result = null; function traverse(nodes) { nodes.forEach(node => { if (result) return; if (node.id === key) { result = node; } if (node.children) { traverse(node.children); } }); } traverse(array); return result; }, // 从中移除某个节点 removeTreeNode(array, nodeId) { for (let i = 0; i < array.length; i++) { if (array[i].id === nodeId) { array.splice(i, 1); break; } else if (array[i].children) { this.removeTreeNode(array[i].children, nodeId); } } }, // 判断是否属于源的操作 isInSource(type) { return type === 'source'; } } }; ``` --- ### 关键点解析 - **`@node-drag-start`**:当用户开始拖拽某节点时会触发该事件,可用于存储当前正在拖拽的节点信息。 - **`@node-drop`**:当拖拽动作完成并释放鼠标时触发,参数包含拖拽的目标节点、原始节点及相关上下文信息[^1]。 - **动态更新数据**:通过遍历形结构找到合适的插入位置,并同步删除旧位置上的对应条目以保持一致性。 - **性能优化提示**:对于非常庞大的状列表来说,推荐采用懒加载技术减少一次性渲染的压力[^2]。 --- ### 注意事项 由于不同业务场景可能存在特殊需求(比如不允许某些类型的节点互相转移),因此实际开发过程中还需要针对具体情况进行适配扩展。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值