首页 前端知识 vue3 element plus树穿梭框

vue3 element plus树穿梭框

2024-06-08 22:06:42 前端知识 前端哥 725 556 我要收藏

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

Expose

字段说明类型
initData初始化穿梭框中的数据function
clearData清空穿梭框中的数据function
转载请注明出处或者链接地址:https://www.qianduange.cn//article/11590.html
标签
评论
发布的文章

小米商城

2024-06-16 15:06:28

JSON转日期,变为数字串

2024-06-16 09:06:45

使用axios读取本地json文件

2024-06-16 09:06:39

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!