vue3+element plus tree shuttle box

HTML

<template>
  <div class='tree-transfer'>
    <div class="left-tree">
      <div class="tree-tit">{<!-- -->{leftTit || 'Left column'}}</div>
      <div class="list">
        <el-tree
          ref="treeRefL"
          v-if="reLoad"
          :data="leftData"
          show-checkbox
          default-expand-all
          :node-key="nodeKey"
          highlight-current
          :props="defaultProps"
        />
      </div>
    </div>
    <div class="btn-div">
      <el-button :icon="Back" type="primary" :disabled="disabled" @click="toLeft()" />
      <el-button :icon="Right" type="primary" :disabled="disabled" @click="toRight()" />
    </div>
    <div class="right-tree">
      <div class="tree-tit">{<!-- -->{rightTit || 'right column'}}</div>
      <div class="list">
        <el-tree
          ref="treeRefR"
          v-if="reLoad"
          :data="rightData"
          show-checkbox
          default-expand-all
          :node-key="nodeKey"
          highlight-current
          :props="defaultProps"
        />
      </div>
    </div>
  </div>
</template>

Shuttle box control logic

import { ref, nextTick, defineExpose } from 'vue'
import { Right, Back } from '@element-plus/icons-vue';
const props = defineProps({
  nodeKey: String,
  fromData: Array,
  toData: Array,
  defaultProps: {},
  leftTit: String,
  rightTit: String,
  disabled: {
    type: Boolean,
    default: false
  }
})
//Define emit
const emit = defineEmits(['checkVal'])
const treeRefL = ref([])
const treeRefR = ref([])
const leftData = ref([])
const rightData = ref([])
const reLoad = ref(true)

//Data on the right
const toData = ref([])
// Data to be removed on the right
const removeData = ref([])

defineExpose({
  /**
   *Clear data
   */
  clearData() {
    toData.value = []
  },
  /**
   * Initialization data
   */
  initData() {
    const originalLeft = JSON.parse(JSON.stringify(props.fromData))
    const originalRight = JSON.parse(JSON.stringify(props.fromData))
    if (props.toData.length > 0) {
      leftData.value = sortData(originalLeft, props.toData, 'left')
      rightData.value = sortData(originalRight, props.toData, 'right')
    }else{
      leftData.value = originalLeft
      rightData.value = []
    }
  }
})

//method
//Go to the right
const toRight = () =>{
  // Save the selected data to toData
  const checkNodes = treeRefL.value.getCheckedNodes(false, false)
  const newArr = toData.value.concat(checkNodes)
  const obj = {};
  const peon = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true & amp; & amp; cur.push(next);
    return cur;
  },[]) //Set the default type of cur to an array, and the initial value is an empty array
  toData.value = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  //Extract the id from the selected data
  const ids = extractId(toData.value)
  //Reorganize the data in the trees on both sides
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
//Go to the left
const toLeft = () =>{
  // Save the selected data to toData
  const checkNodes = treeRefR.value.getCheckedNodes(false, false)

  const newArr = removeData.value.concat(checkNodes)
  const obj = {};
  const peon = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true & amp; & amp; cur.push(next);
    return cur;
  },[]) //Set the default type of cur to an array, and the initial value is an empty array
  const dataNeedRemove = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  //Extract the id from the selected data
  const idsNeedRemove = extractId(dataNeedRemove)
  // Delete the same id
  const oldData = removeId(toData.value, idsNeedRemove)
  toData.value = oldData
  //The id of the data that needs to be retained in the right list
  const ids = extractId(oldData)
  //Reorganize the data in the trees on both sides
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
/**
 * Organize the data in the tree and determine whether the data is displayed in the tree.
 * @param data tree data
 * @param condition selected data
 * @param leftRight Whether to sort the data in the tree on the left or in the tree on the right
 */
const sortData = (data: any, condition: Array<string>, leftRight: string) => {
  if(leftRight === 'left'){
    const result = [];
    for (const item of data) {
      // Determine whether the item's id is in the condition. If not, it means no need to delete it.
      if (!condition.includes(item.id)) {
        // If the item has the children attribute, call this function recursively and pass in the item's children and condition.
        if (item.children) {
          item.children = sortData(item.children, condition, leftRight);
        }
        // If the item's children is an empty array, delete the item's children attribute.
        if (item.children & amp; & amp; item.children.length === 0) {
          delete item.children;
        }
        result.push(item);
      }
    }
    return result;
  }else{
    const result = [];
    for (const item of data) {
      // If the item's id is in condition, it means that the data needs to be retained
      if (condition.includes(item.id)) {
        result.push(item);
      } else {
        // Otherwise, determine whether the item has the children attribute
        if (item.children) {
          const subResult = sortData(item.children, condition, leftRight);
          // If the returned result array is not empty, it means there is sub-data that meets the conditions.
          if (subResult. length > 0) {
            //Update the children attribute of item to the returned result array
            item.children = subResult;
            result.push(item);
          }
        }
      }
    }
    return result;
  }
}
/**
 * If the id in the new array exists in the old array, delete the id in the original array
 * @param oldIds original id
 * @param newIds new id
 */
const removeId = (data: any, newIds: Array<string>) => {
  const ids = []
  for (const item of data) {
    if(!newIds.includes(item.id)){
      ids.push(item)
    }
  }
  return ids
}
/**
 * Take out the id from the data in the candidate
 * @param arr The selected data in the tree
 */
const extractId = (arr: any) => {
  const newArr = []
  for(const i in arr){
    newArr.push(arr[i].id)
  }
  return newArr
}
//Return to parent component
const checkVal = () =>{
  emit('checkVal', toData.value)
}

CSS

.tree-transfer{
  width: 100%;
  display: flex;
  justify-content: space-between;
  .left-tree,.right-tree{
    flex-grow: 1;
    width: calc((100% - 60px) / 2);
    .tree-tit{
      margin-bottom: 10px;
    }
    .list{
      overflow: auto;
      height: 300px;
      border: 1px solid #ddd;
      border-radius: 4px;
      .item{
        padding: 0 10px;
        font-size: 14px;
        line-height: 26px;
        cursor: pointer;
         &.active{
          background: #b9d7fa;
        }
      }
      .item-checkbox{
        height: 26px;
        padding: 0 10px;
        font-size: 14px;
        line-height: 26px;
         &>.el-checkbox{
          height: 26px;
        }
      }
    }
  }
  .btn-div{
    width: 120px;
    flex-shrink: 0;
    display: flex;
    // flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  .el-checkbox__input.is-disabled .el-checkbox__inner{
    display: none;
  }
}

Parent component call

<tree-transfer ref="treeTransfer" :nodeKey="'id'" :fromData="menuList" :toData="ruleForm.menuIds"
          :defaultProps="transferProps" :leftTit="'Optional menu'" :rightTit="'Selected menu'" @checkVal="checkVal"/>
/**
 * Save the selected menu into the form
 * @param val subcomponent shuttle box return
 */
const checkVal = (val: any) => {
  const arr = []
  for(const i in val){
      arr.push(val[i].id)
  }
  ruleForm.menuIds = arr
}

Shuttle box parameter document

Properties

List

Parameter description
Field Description Type Whether it must be passed
nodeKey The unique id value corresponding to the item in the tree string true
fromData Menu tree (must contain id name) array[object] true
toData Selected value array[string] true
defaultProps Column width of list object true
leftTit Left menu name string false
rightTit Right menu name string false
disabled Whether to disable the left and right buttons of the shuttle box boolean false
fromData description in dataLabel
Field Description Type Whether it must be passed
id Unique id value string true
name id corresponds to the display rendering value string true
children Sublevel of tree string false
Description of defaultProps in dataLabel
Field Description Type Whether it must be passed
label Specify the node label as an attribute value of the node object string, function(data, node) true
children Specify the subtree as a certain attribute value of the node object string true
disabled Specifies whether the node selection box is disabled as a property value of the node object string, function(data, node) true

Expose

Field Description Type
initData Initialize the data in the shuttle box function
clearData Clear the data in the shuttle box function