vue3+element plus树穿梭框

 HTML

<template>
  <div class='tree-transfer'>
    <div class="left-tree">
      <div class="tree-tit">{
   
   {leftTit || '左侧栏'}}</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 || '右侧栏'}}</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>

穿梭框控制逻辑

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
  }
})
//定义emit
const emit = defineEmits(['checkVal'])
const treeRefL = ref([])
const treeRefR = ref([])
const leftData = ref([])
const rightData = ref([])
const reLoad = ref(true)

//右侧数据
const toData = ref([])
// 右侧需要移除的数据
const removeData = ref([])

defineExpose({
  /**
   * 清空数据
   */
  clearData() {
    toData.value = []
  },
  /**
   * 初始化数据
   */
  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 = []
    }
  }
})

//方法
//去右边
const toRight = () =>{
  // 将勾选中的数据保存到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 && cur.push(next);
    return cur;
  },[]) //设置cur默认类型为数组,并且初始值为空的数组
  toData.value = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  // 抽离出选中数据中的id
  const ids = extractId(toData.value)
  // 重新整理两侧树中数据
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
//去左边
const toLeft = () =>{
  // 将勾选中的数据保存到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 && cur.push(next);
    return cur;
  },[]) //设置cur默认类型为数组,并且初始值为空的数组
  const dataNeedRemove = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  // 抽离出选中数据中的id
  const idsNeedRemove = extractId(dataNeedRemove)
  // 删除相同id
  const oldData = removeId(toData.value, idsNeedRemove)
  toData.value = oldData
  // 右侧列表需要保留的数据的id
  const ids = extractId(oldData)
  // 重新整理两侧树中数据
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
/**
 * 将tree中的整理进行整理,判断数据是否再tree中显示
 * @param data tree数据
 * @param condition 被选中的数据
 * @param leftRight 整理左侧tree中的数据还是整理右侧tree中的数据
 */
const sortData = (data: any, condition: Array<string>, leftRight: string) => {
  if(leftRight === 'left'){
    const result = [];
    for (const item of data) {
      // 判断item的id是否在condition中,如果不在,说明不需要删除
      if (!condition.includes(item.id)) {
        // 如果item有children属性,递归调用本函数,传入item的children和condition
        if (item.children) {
          item.children = sortData(item.children, condition, leftRight);
        }
        // 如果item的children为空数组,删除item的children属性
        if (item.children && item.children.length === 0) {
          delete item.children;
        }
        result.push(item);
      }
    }
    return result;
  }else{
    const result = [];
    for (const item of data) {
      // 如果item的id在condition中,说明该数据需要保留
      if (condition.includes(item.id)) {
        result.push(item);
      } else {
        // 否则,判断item是否有children属性
        if (item.children) {
          const subResult = sortData(item.children, condition, leftRight);
          // 如果返回的结果数组不为空,说明有符合条件的子数据
          if (subResult.length > 0) {
            // 将item的children属性更新为返回的结果数组
            item.children = subResult;
            result.push(item);
          }
        }
      }
    }
    return result;
  }
}
/**
 * 如果新数组中的id再旧数组中存在则删除原始数组中的id
 * @param oldIds 原始id
 * @param newIds 新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
}
/**
 * 将id从备选中的数据取出
 * @param arr tree中被选中的数据
 */
const extractId = (arr: any) => {
  const newArr = []
  for(const i in arr){
    newArr.push(arr[i].id)
  }
  return newArr
}
//返回父组件
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;
  }
}

父组件调用

<tree-transfer ref="treeTransfer" :nodeKey="'id'" :fromData="menuList" :toData="ruleForm.menuIds"
          :defaultProps="transferProps" :leftTit="'可选菜单'" :rightTit="'已选菜单'" @checkVal="checkVal"/>
/**
 * 将选中菜单存入表单
 * @param val 子组件穿梭框返回
 */
const checkVal = (val: any) => {
  const arr = []
  for(const i in val){
      arr.push(val[i].id)
  }
  ruleForm.menuIds = arr
}

穿梭框参数文档

属性

列表

参数说明
字段 说明 类型 是否必传
nodeKey 树中项目对应的唯一id值 string true
fromData 菜单树 ( 必须包含id name ) array[object] true
toData 已经选中的值 array[string] true
defaultProps 列表的列宽 object true
leftTit 左侧菜单名称 string false
rightTit 右侧菜单名称 string false
disabled 是否禁用穿梭框的左右按钮 boolean false
dataLabel 中的 fromData说明
字段 说明 类型 是否必传
id 唯一id值 string true
name id对应展示渲染值 string true
children 树的子层级 string false
dataLabel 中的 defaultProps说明
字段 说明 类型 是否必传
label 指定节点标签为节点对象的某个属性值 string, function(data, node) true
children 指定子树为节点对象的某个属性值 string true
disabled 指定节点选择框是否禁用为节点对象的某个属性值 string, function(data, node) true

Expose

字段 说明 类型
initData 初始化穿梭框中的数据 function
clearData 清空穿梭框中的数据 function

猜你喜欢

转载自blog.csdn.net/m0_46114541/article/details/134338997
今日推荐