vue实现自定义树形穿梭框功能

news2024/9/21 10:29:52

需求:

我们在开发过程中,会遇到需要将一个数据选择做成穿梭框,但是要求穿梭框左侧为树形结构、右侧为无层级结构的数据展示,ElementUI自身无法在穿梭框中添加树形结构,网上搜到了大佬封装的插件但是对于右侧的无树形结构一点还是不太满足。以下是我们简单的封装写的组件可以实现此功能

在这里插入图片描述

1,封装的treeTransfetr组件如下:
<template>
    <div class="treeTransfer">
      <!-- 左边 -->
      <div class="leftTree">
          <div class="list">
              <div class="left_lowline">
                  <div class="leftcheck_con">
                      <el-checkbox v-model="checkedLeft" :disabled="leftTreeData.length < 1" label="" size="large"
                      style="margin-right: 12px" @change="leftAllCheck" />
                      <p class="left_title">{{ props.title[0] }}</p>
                  </div>
                  <div>
                      {{ leftOperation.length }}/{{ leftAllselectIdarry.length }}
                  </div>
              </div>
              <el-tree 
                  ref="leftTreeRef" 
                  :data="leftTreeData" 
                  show-checkbox
                  node-key="id" 
                  :props="props.defaultProps" 
                  v-slot="{ node, data }" 
                  accordion
                  :check-strictly="true"
                  @check="onCheckLeft" 
                  default-expand-all>
                  <div>
                      {{ data.label }}
                  </div>
              </el-tree>
          </div>
      </div>
      <!-- 中间按钮 -->
      <div class="btnDiv">
          <div class="mg10">
              <el-button @click="toRight()" icon="ele-Right" type="primary"  circle :disabled="leftOperation.length < 1"/>
          </div>
          <div class="mg10">
              <el-button @click="toLeft()" icon="ele-Back" type="primary" circle :disabled="rightOperation.length < 1"/>
          </div>
      </div>
      <!-- 右边 -->
      <div class="rightTree">
          <div class="list">
              <div class="left_lowline">
                  <div class="leftcheck_con">
                      <el-checkbox v-model="checkedRight" :disabled="rightTreeData.length < 1" label="" size="large"
                      style="margin-right: 12px" @change="rightAllCheck" />
                      <p class="left_title">{{ props.title[1] }}</p>
                  </div>
                  <div>
                      {{ rightOperation.length }}/{{ rightAllselectIdarry.length }}
                  </div>
              </div>
              <el-tree ref="rightTreeRef" 
                  :data="rightTreeData" 
                  show-checkbox 
                  node-key="id"
                  :props="props.defaultProps" 
                  v-slot="{ node, data }" 
                  accordion 
                  :check-strictly="true"
                  @check="onCheckRight" 
                  default-expand-all>
                  <div>
                      {{ data.label }}
                  </div>
              </el-tree>
          </div>
      </div>
    </div>
  </template>
  
  <script setup lang="ts">
  import { ref, onMounted, watch, nextTick } from 'vue';
  import lodash from 'lodash-es'
  const props = defineProps(['fromData', 'toData', 'defaultProps', 'title', 'visible']);
   
  const checkedLeft = ref(false)
  const checkedRight = ref(false)
  
  const leftOperation = ref<any[]>([])
  const rightOperation = ref<any[]>([])
   
  // 定义emit
  const emits = defineEmits(['addStaffchange']);
  const leftTreeRef = ref();
  const rightTreeRef = ref();
   
  // 左侧数据
  const leftTreeData = ref([] as any);
  // 右侧数据
  const rightTreeData = ref([] as any);
  
  // 左侧可以选中id集合
  const leftAllselectIdarry = ref([] as any)
  
  // 右侧可以选中id集合
  const rightAllselectIdarry = ref([] as any)
   
  watch(
      props,
      newVal => {
          leftTreeData.value = lodash.cloneDeep(newVal.fromData)
          rightTreeData.value = lodash.cloneDeep(newVal.toData)
          // 获取左侧的全选中的id
          leftAllselectIdarry.value = getAllIds(leftTreeData.value, [])
          if (newVal.visible) {
              checkedLeft.value = false
              checkedRight.value = false
              leftOperation.value = []
              rightOperation.value = []
              nextTick(() => {
                  leftTreeRef?.value.setCheckedKeys([])
              })
          }
      },
      { immediate: true }
  )
   
  defineExpose({
      leftTreeData,
      rightTreeData
  })
   
  onMounted(() => {
  })
  
  // 去右边
  const toRight = async () => {
      const leftTree = leftTreeRef.value;
      if (!leftTree) {
          return
      }
      const leftNodes = leftTree.getCheckedNodes(false, false)
      const checkedKeys = leftTree.getCheckedKeys(false)
      const rightTree = rightTreeRef.value
      const newArr = rightTreeData.value.concat(leftNodes)
      
      let obj = {};
      let peon = newArr.reduce((cur,next) => {
          obj[next['id']] ? "" : obj[next['id']] = true && cur.push(next);
          return cur;
      },[]) 
      //设置cur默认类型为数组,并且初始值为空的数组
      const getnewleftArry = peon.map(item => {
         return {
          id: item.id,
          label: item.label,
          pid: item.pid,
          children: [],
         }
      })
      rightTreeData.value = getnewleftArry
      leftOperation.value = leftTreeRef.value?.getCheckedKeys(false)
      emits('addStaffchange', checkedKeys)
      setTimeout(() => {
          rightTree?.setCheckedNodes(leftNodes);
          rightOperation.value = rightTreeRef.value?.getCheckedKeys(false)
          rightAllcheckChange()
      }, 500)
  };
  
  // 去左边
  const toLeft = async () => {
      const rightTree = rightTreeRef.value
      if (!rightTree) {
          return
      }
      const checkedKeys = rightTree.getCheckedKeys(false)
      for(var i=0; i<rightTreeData.value.length;i++){
        if(checkedKeys.includes(rightTreeData.value[i].id)){
          rightTreeData.value.splice(i,1)
          i-=1
        }
      }
      rightOperation.value = rightTree?.getCheckedKeys(false)
      if (rightTreeData.value && rightTreeData.value.length === 0) {
          checkedRight.value = false
      }
      emits('addStaffchange', getAllIds(rightTreeData.value, []))
  };
  
   
  //左侧选中
  const onCheckLeft = () => {
      leftOperation.value = leftTreeRef.value?.getCheckedKeys(false)
      if (leftOperation.value.length === leftAllselectIdarry.value.length) {
          checkedLeft.value = true
      } else {
          checkedLeft.value = false
      }
  }
  
  // 右侧选中
  const onCheckRight = () => {
      rightOperation.value = rightTreeRef.value?.getCheckedKeys(false)
      rightAllselectIdarry.value.length = getAllIds(rightTreeData.value, []).length
      rightAllcheckChange()
  }
  
  // 右侧是否全选获取
  function rightAllcheckChange () {
      rightAllselectIdarry.value.length = getAllIds(rightTreeData.value, []).length
      if (rightOperation.value.length === rightAllselectIdarry.value.length) {
          checkedRight.value = true
      } else {
          checkedRight.value = false
      }
      return checkedRight.value
  }
  
   
  // 左侧全选中值 
  const leftAllCheck = () => {
      if (checkedLeft.value) {
          leftTreeRef.value.setCheckedKeys(getAllIds(leftTreeData.value, []))
          checkedLeft.value = true;
      } else {
          leftTreeRef.value.setCheckedKeys([])
          checkedLeft.value = false
      }
      leftOperation.value = leftTreeRef.value?.getCheckedKeys(false)
  }
   
  // 右侧全选中值 
  const rightAllCheck = () => {
      if (checkedRight.value) {
          rightTreeRef.value.setCheckedKeys(getAllIds(rightTreeData.value, []))
          checkedRight.value = true
      } else {
          rightTreeRef.value.setCheckedKeys([])
          checkedRight.value = false
      }
      rightOperation.value = rightTreeRef.value?.getCheckedKeys(false)
  }
  
  
  // 递归获取所有id数据
  function getAllIds(tree, result) {
    //遍历树获取id数组
    for (const i in tree) {
      if (!tree[i].disabled) {
          result.push(tree[i].id); // 遍历项目满足条件后的操作
      }
      if (tree[i].children) {
        // 存在子节点就递归
        getAllIds(tree[i].children, result);
      }
    }
    return result;
  }
  </script>
  
  <style scoped lang="scss">
  .treeTransfer {
      display: flex;
      justify-content: center;
      .el-tree {
          overflow: auto;
          max-height: 360px;
      }
      .leftTree {
          border: 1px solid #ebeef5;
          width: 40%;
          height: calc(100% - 60px);
          overflow: auto;
      }
  
      .left_lowline {
          display: flex;
          align-items: center;
          justify-content: space-between;
          background: #f5f7fa;
          padding: 0 23px 0 10px;
          .leftcheck_con {
              display: flex;
              align-items: center;
          }
      }
      .btnDiv {
          width: 20%;
          height: calc(100% - 60px);
          text-align: center;
          margin: auto 0;
          line-height: 40px;
          position: relative;
      }
      .rightTree {
          width: 40%;
          height: calc(100% - 60px);
      }
  }
  </style>
2,具体使用如下
<treeTransfetr 
          ref="treeTransferRef" 
          :fromData="fromData"
          :toData="toData"
          :visible="visible"
          :defaultProps="transferProps" 
          @addStaffchange="addStaffchange" 
          :title="['筛选结果', '添加人员']"
        >
        </treeTransfetr>

let treeTransferRef = ref(); // 树形穿梭框
let fromData = ref([
  {
    id: "1",
    pid: 0,    //自定义pid的参数名,默认为"pid" 必填:false
    label: "张三-D1-DM",
    disabled: false,
    children: [
      {
        id: "1-1",
        pid: "1",
        label: "李四-D1-TL",
        disabled: false,
        children: []
      },
      {
        id: "1-2",
        pid: "1",
        label: "王五-D2-TL",
        disabled: false,
        children: [
          {
            id: "1-2-1",
            pid: "1-2",
            children: [],
            label: "赵六-D3-TL",
            disabled: true,
          },
          {
            id: "1-2-2",
            pid: "1-2",
            children: [],
            label: "李明-D4-TL",
            disabled: false,
          },
          {
            id: "1-2-3",
            pid: "1-2",
            children: [],
            label: "王三明-D5-TL",
            disabled: false,
          }
        ]
      }
    ]
  }
]); // 树形数据
let toData = ref([]); // 选中的ids数据
const transferProps = ref({
  label: 'label',
  children: 'children',
  disabled: 'disabled',
});

如果我们想要用插件实现,推荐使用el-tree-transfer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1480358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

three.js 叉乘判断物体在人前左,前右,后左、后右

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs"></div><div style"padding: 10px;text-align: left;">叉乘判断物体…

Flutter修改了函数申明后hotload会失效

文章目录 问题描述问题原因解决方法 问题描述 在 Flutter 开发中&#xff0c;如果修改了函数的声明&#xff0c;Hot Reload 功能通常不能正常使用&#xff0c;需要重新编译。 问题原因 Hot Reload 功能的工作原理是通过注入新的 Dart 代码来更新运行中的应用程序。当您修改函…

从零学算法289

289.根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 …

全面评估2023年最受欢迎的 3 类型原型设计工具

与其他设计工具相比&#xff0c;原型设计工具往往强调快速设计&#xff0c;界面建设和交互设计可以通过预设的组件、模板等内容快速完成。这可以在短时间内呈现和表达这个想法。 在原型设计工具市场上&#xff0c;有遥遥领先的基准产品、积累的新兴产品、没有创新的模仿产品、…

Tomcat布署及优化-----JDK和Tomcat

1.Tomcat简介 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;Tomcat 属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试 JSP 程序的首选。一般来说&…

Springboot项目集成短信验证码(超简单)

操作流程 注册验证码平台创建验证码模版开始集成&#xff08;无需引入第三方库&#xff09; 注册并登陆中昱维信验证码平台 获取AppID和AppKey。 创建验证码模版 创建验证码模版&#xff0c;获取验证码模版id 开始集成 创建controller import org.springframework.web.bi…

YOLOv8涨点技巧,添加SE注意力机制提升目标检测效果

目录 论文地址 摘要 SE结构图 代码实现 Squeeze Excitation SE-Inception and SE-ResNet yaml文件编写 完整代码分享 总结 论文地址 http://openaccess.thecvf.com/content_cvpr_2018/papers/Hu_Squeeze-and-Excitation_Networks_CVPR_2018_paper.pdf 摘要 卷积神…

day06_菜单管理(查询菜单,添加菜单,添加子菜单,修改菜单,删除菜单,角色分配菜单,查询菜单,保存菜单,动态菜单)

文章目录 1 菜单管理1.1 表结构介绍1.2 查询菜单1.2.1 需求说明1.2.2 页面制作1.2.3 后端接口SysMenuSysMenuControllerSysMenuServiceMenuHelperSysMenuMapperSysMenuMapper.xml 1.2.4 前端对接sysMenu.jssysMenu.vue 1.3 添加菜单1.3.1 需求说明1.3.3 页面制作1.3.3 后端接口…

续上一篇---Jetson Xavier NX 开发板Yolov4 环境搭建和摄像头实时检测教程

1.已成功安装完CUDA&#xff0c;OpenCV&#xff0c;cuDNN&#xff0c;配置如下&#xff1a; 2.下载yolov4 自行去GitHub下载&#xff0c;然后再导入NX中&#xff0c;这样比较快且不容易出错。 https://github.com/AlexeyAB/darknet.git 3.配置 下载好包后解压缩&#xff0c;进…

一个完整的Flutter项目的基本构成

目录 1.页面跳转2.本地数据库和读取2.1 在pubspec.yaml中添加数据库框架依赖2.2 创建db.dart 初始化数据库并创建表2.3 安装JsonToDart插件2.4 创建实体类 user_bean.dart2.5 增删改查&#xff1a; 3.网络请求数据解析UI渲染 本篇主要总结下一个完整的Flutter项目有哪些基本构成…

C语言第三十三弹---动态内存管理(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 动态内存管理 1、为什么要有动态内存分配 2、malloc和free 2.1、malloc 2.2、free 3、calloc和realloc 3.1、calloc 3.2、realloc 4、常见的动态内存的错…

【单片机学习的准备】

文章目录 前言一、找一个视频是二、画图软件三、装keil5 仿真protues总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 项目需要&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、找一个视频是 https://www.b…

mariadb数据库——安装,创建数据库

MariaDB是一个流行的开源关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是MySQL的一个分支。 安装 apt -y install mariadb-servervi /etc/mysql/mariadb.conf.d/50-server.cnf character-set-server utf8mb4 collation-server utf8mb4_general_c…

让AI给你写代码,初体验(三)- AI加上格式化对话,实现对单个文件的修改/保存

上一篇中&#xff0c;AI生成web应用初步解决了简单web应用生成&#xff0c;但是存在两方面的问题 1&#xff09; 无法向AI提供增量需求&#xff0c;特别是一个对话结束之后&#xff0c;只能把全部需求&#xff08;包括新需求&#xff09;再描述一遍 2&#xff09; 无法用AI对存…

深入学习NumPy库在数据分析中的应用场景

在数据科学与机器学习领域&#xff0c;NumPy&#xff08;Numerical Python&#xff09;是一个经常被提及的重要工具。它是Python语言中一个非常强大的库&#xff0c;提供了高性能的多维数组对象以及用于处理这些数组的工具。NumPy不仅仅是一个用于数值计算的库&#xff0c;它还…

Redis第6讲——主从复制模式详解

Redis的读写性能很高&#xff0c;但在面对大规模数据和高发访问的挑战时&#xff0c;单节点的Redis可能无法满足需求&#xff0c;这就引出了Redis集群的概念。本节先介绍一下Redis高可用方案之一的主从复制模式&#xff0c;虽说现在基本不会用这种模式&#xff0c;但是无论是哨…

Web自动化测试平台开发---Automated_platform

一、项目简介 历时一个假期&#xff0c;Automated_platform 第一版完工&#xff0c;是一款基于po模式的自动化测试平台,采用后端技术为DjangoceleryRabbitMQmysql 配置mysql数据库&#xff0c;进行数据迁移后&#xff0c;运行项目后&#xff0c;即可成功访问http://127.0.0.1:8…

单调栈的理解

单调栈的理解 核心代码场景思考 完整代码 单调栈&#xff1a; 单调递增或 单调递减的栈 核心代码 while (!s.empty()&&s.peek()<nums[i]){s.pop(); } s.push(nums[i]);将要放入的元素&#xff0c;与栈内元素依个比较&#xff0c;小于的都出栈&#xff0c;最后将要…

List 集合遍历过程中删除元素避坑指南。

文章目录 1. 遍历2. 遍历过程中删除元素2.1 for 简单循环正向遍历方式2.2 for 简单循环反向遍历方式2.3 foreach 方式遍历删除2.4 Iterator的remove()方法2.5 <font color green> removeIf() &#xff08;推荐&#xff09;<green>2.6 Strem 方式 作为一名后端开发…

Executable and Linkable Format(ELF)

File layout ELF文件有两种视图。程序头表&#xff08;Program Header&#xff09;显示在运行时使用的段&#xff08;Segments&#xff09;&#xff0c;而节头表&#xff08;Section Header&#xff09;则列出了二进制文件的所有节&#xff08;Sections&#xff09;的集合。程…