vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue vxe-tree树形穿梭框

vue实现 vxe-tree 树形穿梭框与跨树拖拽功能

作者:zhiy3

在使用Vue.js开发项目时,如果你想要实现 xe-tree(一个基于 Vue 的树形控件)的穿梭框(Transfer Tree)和跨树拖拽的功能,你需要结合一些额外的逻辑来处理树节点在不同树之间的移动,下面将提供一个基本的步骤和示例代码来实现这一功能

后台管理系统中,树形结构的数据选择与分配是常见需求——例如角色权限分配、组织架构调整、分类迁移等。传统的穿梭框(Transfer)仅支持平铺列表,无法保留数据的层级关系。
本文将利用 vxe-tree(Vxe 组件库中的树形组件)快速构建一个支持勾选穿梭 + 跨树拖拽的双树穿梭组件,并讲解其核心配置与交互细节。

功能预览

核心配置解析

vxe-tree 提供了丰富的配置项,本方案中关键配置如下:

配置项值说明
transformtrue自动将扁平数据转换为树形结构(依据 parentField)
titleField'title'节点显示文本字段
keyField'id'节点唯一标识字段(跨树拖拽时必须确保两棵树的主键不重复)
parentField'parentId'父节点关联字段
showCheckboxtrue显示复选框
dragtrue开启拖拽
dragConfig.isCrossDragtrue允许同树内拖拽调整层级
dragConfig.isCrossTreeDragtrue允许跨树拖拽(关键)

跨树拖拽核心前提:两棵树的 keyField 指定字段值必须全局唯一,否则拖拽后数据错乱。若数据来自不同源,需提前加工主键(如加前缀)。

穿梭逻辑

通过 ref 获取两个树实例,调用其内置方法:

跨树拖拽

开启 isCrossTreeDrag: true 后,用户可直接用鼠标将任意节点从一棵树拖拽到另一棵树。
拖拽过程中,组件会校验主键是否重复,并自动完成 remove + insertAt 操作。
开发者无需编写额外事件处理,仅需保证两棵树的 keyField 一致且值唯一。

数据变更追踪

业务场景中,穿梭后需要知道哪些节点是新增的、哪些被删除了,以便提交后台。
vxe-tree 的 getRecordset() 方法返回当前树相对于初始数据的变更集合:

const { insertRecords, removeRecords } = $tree1.getRecordset()
const treeData = $tree1.getFullData()   // 当前所有节点(树形结构)

用这些数据,可以准确生成操作日志或提交参数。

代码

<template>
  <div>
    <vxe-button status="success" @click="resultEvent1">获取数据1</vxe-button>
    <vxe-button status="success" @click="resultEvent2">获取数据2</vxe-button>
    <div class="my-tree-transfer">
      <div class="my-tree-transfer-left">
        <vxe-tree ref="treeRef1" v-bind="treeOptions1"></vxe-tree>
      </div>
      <div class="my-tree-transfer-handle">
        <vxe-button class="my-tree-transfer-btn" status="primary" icon="vxe-icon-arrow-double-right" style="width: 100%" @click="addEvent"></vxe-button>
        <vxe-button class="my-tree-transfer-btn" status="error" icon="vxe-icon-arrow-double-left" style="width: 100%" @click="delEvent"></vxe-button>
      </div>
      <div class="my-tree-transfer-right">
        <vxe-tree ref="treeRef2" v-bind="treeOptions2"></vxe-tree>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
const treeRef1 = ref()
const treeRef2 = ref()
const treeOptions1 = reactive({
  height: '100%',
  transform: true,
  titleField: 'title',
  keyField: 'id',
  parentField: 'parentId',
  showCheckbox: true,
  drag: true,
  dragConfig: {
    isCrossDrag: true,
    isCrossTreeDrag: true
  },
  data: [
    { title: '节点2', id: '2', parentId: null },
    { title: '节点3', id: '3', parentId: null },
    { title: '节点3-1', id: '31', parentId: '3' },
    { title: '节点3-2', id: '32', parentId: '3' },
    { title: '节点3-2-1', id: '321', parentId: '32' },
    { title: '节点3-2-2', id: '322', parentId: '32' },
    { title: '节点3-3', id: '33', parentId: '3' },
    { title: '节点3-3-1', id: '331', parentId: '33' },
    { title: '节点3-3-2', id: '332', parentId: '33' },
    { title: '节点3-3-3', id: '333', parentId: '33' },
    { title: '节点3-4', id: '34', parentId: '3' },
    { title: '节点4', id: '4', parentId: null },
    { title: '节点4-1', id: '41', parentId: '4' },
    { title: '节点4-1-1', id: '411', parentId: '42' },
    { title: '节点4-1-2', id: '412', parentId: '42' },
    { title: '节点4-2', id: '42', parentId: '4' },
    { title: '节点4-3', id: '43', parentId: '4' },
    { title: '节点4-3-1', id: '431', parentId: '43' },
    { title: '节点4-3-2', id: '432', parentId: '43' },
    { title: '节点5', id: '5', parentId: null }
  ]
})
const treeOptions2 = reactive({
  height: '100%',
  transform: true,
  titleField: 'title',
  keyField: 'id',
  parentField: 'parentId',
  showCheckbox: true,
  drag: true,
  dragConfig: {
    isCrossDrag: true,
    isCrossTreeDrag: true
  },
  data: []
})
const addEvent = () => {
  const $tree1 = treeRef1.value
  const $tree2 = treeRef2.value
  if ($tree1 && $tree2) {
    const selectRecords = $tree1.getCheckboxRecords()
    if (selectRecords.length) {
      $tree1.clearCheckboxNode()
      $tree1.remove(selectRecords)
      $tree2.insertAt(selectRecords, -1)
    } else {
      VxeUI.modal.message({
        content: '请勾选左侧数据',
        status: 'warning'
      })
    }
  }
}
const delEvent = () => {
  const $tree1 = treeRef1.value
  const $tree2 = treeRef2.value
  if ($tree1 && $tree2) {
    const selectRecords = $tree2.getCheckboxRecords()
    if (selectRecords.length) {
      $tree2.clearCheckboxNode()
      $tree2.remove(selectRecords)
      $tree1.insertAt(selectRecords, -1)
    } else {
      VxeUI.modal.message({
        content: '请勾选右侧数据',
        status: 'warning'
      })
    }
  }
}
const resultEvent1 = () => {
  const $tree1 = treeRef1.value
  if ($tree1) {
    const { insertRecords, removeRecords } = $tree1.getRecordset()
    const treeData = $tree1.getFullData()
    VxeUI.modal.message({
      content: `新增:${insertRecords.length} 删除:${removeRecords.length} 现有:${treeData.length}`,
      status: 'success'
    })
  }
}
const resultEvent2 = () => {
  const $tree2 = treeRef2.value
  if ($tree2) {
    const { insertRecords, removeRecords } = $tree2.getRecordset()
    const treeData = $tree2.getFullData()
    VxeUI.modal.message({
      content: `新增:${insertRecords.length} 删除:${removeRecords.length} 现有:${treeData.length}`,
      status: 'success'
    })
  }
}
</script>
<style lang="scss" scoped>
.my-tree-transfer {
  display: flex;
  height: 400px;
  overflow: hidden;
}
.my-tree-transfer-handle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex-shrink: 0;
  padding: 0 5px;
  width: 50px;
}
.my-tree-transfer-btn {
  margin: 2px 0;
}
.my-tree-transfer-left,
.my-tree-transfer-right {
  width: 50%;
  padding: 8px;
  border-radius: 4px;
  border: 1px solid #999999;
  overflow: hidden;
}
</style>

注意事项与最佳实践

利用 vxe-tree 的 内置拖拽系统 和 数据变更追踪,我们以极少的代码实现了一个功能完整、交互流畅的树形穿梭框。相比手写拖拽逻辑,该方案开发效率高、维护成本低,且天然支持层级数据。
若您的项目中需要树形结构的双向选择,不妨直接复用上述代码,并根据业务微调样式与交互提示。

https://vxeui.com

到此这篇关于vue实现 vxe-tree 树形穿梭框与跨树拖拽功能的文章就介绍到这了,更多相关vue vxe-tree树形穿梭框内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文