vue2 element el-transfer穿梭框组件支持拖拽及排序 已封装,随取随用

news2024/11/15 20:09:47

项目场景:

项目中有个功能用到穿梭框组件,新版本需要支持穿梭框组件排序,由于element2版本中的穿梭框组件本身不支持排序功能

在此不仅需要支持随意更换顺序,还支持从一侧拖拽至另一侧,具体功能效果图如下:
在这里插入图片描述
啊啊啊对不住哈哈,GIF过于模糊,自行脑补吧,临时没找到免费的视频转GIF


穿梭框组件封装代码如下

提示:关于拖拽部分代码是基于其他大佬的基础上稍作修改,原博文地址:[https://blog.csdn.net/juvenile_Li/article/details/126886010?spm=1001.2014.3001.5506]

Tips1: 此功能使用到了sortablejs插件,请务必先下载本插件再引用下面的代码,sortablejs安装命令为:npm install sortablejs --save

Tips2: 下面的代码中引入的其他方法请自行根据自己的项目进行修改,比如:错误提示Messagebox

//先看封装好的组件,此处我们是加了弹框的,如果不需要自行修改去掉即可
<template>
    <div class="delete-info-dialog">
        <el-dialog
            :title="dialogTitle"
            :visible.sync="tableItemDialogVisible"
            :close-on-click-modal="false"
            :close-on-press-escape="false"
            width="700px"
            :before-close="handleClose">
            <div class="info-body">
                <el-transfer ref="transfer"
                             v-model="visibleValue"
                             :data="transferData"
                             :titles="transferTitle"
                             target-order="push"
                             :props="transferProps"
                             @left-check-change="leftCheckChange"
                             @right-check-change="rightCheckChange"
                             >
                      <span slot-scope="{ option }"
                            draggable="!option.disabled"
                            @dragstart="drag($event, option)"
                      >
                        {{ option.label }}
                      </span>
                </el-transfer>
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button type="primary" @click="handleConfirm">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
import Sortable from 'sortablejs' //记得安装拖拽组件哦
import cloneDeep from "lodash.clonedeep"; //这个是深拷贝方法,也可以换成自己的,也可以下载lodash,此处为了避免修改父组件传过来的数据
export default {
    name: "visible-item-table-dialog",
    props: {
      //此处是可以随意定义弹框的文字,且有默认值
      dialogTitle: {
        type: String,
        default: '显示列设置'
      },
      //此处是可以定义transfer组件的props
      transferProps: {
        type: Object,
        default: {
            key: 'key',
            label: 'label',
        }
      },
      //此处是可以定义transfer组件的title
      transferTitle: {
        type: Array,
        default: ()=> ['隐藏列', '显示列']
      },
      // 穿梭框总数据
      transferDataList: {
        type: Array,
        default: ()=> []
      },
      // 右侧显示列,用户已经选好保存过的数据,从父组件传过来回显
      visibleTableItemList: {
        type: Array,
        default: ()=> []
      },
      // 默认显示列,如果用户还没设置过则取这个
      defaultTableItemList: {
        type: Array,
        default: ()=> []
      },
      // 控制弹框显隐
      tableItemDialogVisible: {
        type: Boolean,
        default: false
      },
    },
    data() {
        return {
            visibleValue: [],//显示列表头数据
            transferData: [], // 穿梭框数据
            transferLeftCheckData: [], // 左侧选中数据
            transferRightCheckData: [], // 右侧选中数据
            draggingKey: '',  // 当前拖拽项
        }
    },
    mounted() {
      this.transferData = this.transferDataList;
      this.visibleValue = this.visibleTableItemList.length>0 ?cloneDeep(this.visibleTableItemList) : cloneDeep(this.defaultTableItemList);
      // 此处加nextTick 为了保证顺利取到refs
      this.$nextTick(()=>{
        const transfer = this.$refs.transfer.$el;
        const leftPanel = transfer.getElementsByClassName('el-transfer-panel')[0].getElementsByClassName('el-transfer-panel__body')[0]
        const rightPanel = transfer.getElementsByClassName('el-transfer-panel')[1].getElementsByClassName('el-transfer-panel__body')[0]
        const rightEl = rightPanel.getElementsByClassName('el-transfer-panel__list')[0]
        Sortable.create(rightEl, {
          onEnd: evt => {
            const { oldIndex, newIndex } = evt
            const temp = this.visibleValue[oldIndex]
            if (!temp || temp === 'undefined') {
              return
            } // 解决右边最后一项从右边拖左边,有undefined的问题
            // this.$set(this.visibleValue, oldIndex, this.visibleValue[newIndex]) //这种赋值方法会导致数据更新视图未更新,排序后顺序展示错乱,强制更新也无效,原博主是这么赋值的,仅供参考
            // this.$set(this.visibleValue, newIndex, temp)
              let _arr = this.visibleValue.splice(oldIndex, 1)
              this.visibleValue.splice(newIndex, 0, _arr[0])
          }
        })
        // 目前只让右侧支持拖拽顺序即可,左侧暂时注释
        const leftEl = leftPanel.getElementsByClassName('el-transfer-panel__list')[0]
        Sortable.create(leftEl, {
          onEnd: evt => {
            const { oldIndex, newIndex } = evt
            const temp = this.transferData[oldIndex]
            if (!temp || temp === 'undefined') {
              return
            } // 解决右边最后一项从左边拖右边,有undefined的问题            
              let _arr = this.transferData.splice(oldIndex, 1)
              this.transferData.splice(newIndex, 0, _arr[0])
          }
        })

        // 关于左侧拖拽至右侧的功能,在本项目中暂时无法实现,经检测drag事件未触发(除了本项目外都可),后续再研究^_^
        leftPanel.ondragover = ev => ev.preventDefault()
        leftPanel.ondrop = ev => {
          ev.preventDefault()
          // 往左拉
          const index = this.visibleValue.indexOf(this.draggingKey)
          if (index !== -1) {
            // 如果当前拉取的是选中数据就将所有选中的数据拉到左边选中框内
            if (this.transferRightCheckData.indexOf(this.draggingKey) !== -1) {
              // 此处为多选执行
              this.transferRightCheckData.reduce((arr, item) => {
                if (arr.indexOf(item) !== -1) {
                  // 每次计算将相同的删掉
                  arr.splice(arr.indexOf(item), 1)
                }
                return arr
              }, this.visibleValue)
              this.transferRightCheckData = []// 清除右侧选中的 不然下次向左拉取时会有缓存
              // 否则就只拉取当前一个
            } else {
              this.visibleValue.splice(index, 1)
            }
          }
        }
        rightPanel.ondragover = ev => ev.preventDefault()
        rightPanel.ondrop = ev => {
          ev.preventDefault()
          if (!this.draggingKey || this.draggingKey === 'undefined') {
            return
          } // 解决右边最后一项从左边拖右边,有undefined的问题
          // 右边框里没有当前key值的时候 向右拉
          if (this.visibleValue.indexOf(this.draggingKey) === -1) {
            // 此处为多选执行
            // 如果当前拉取的是选中数据就将所有选中的数据拉到右边选中框内
            if (this.transferLeftCheckData.indexOf(this.draggingKey) !== -1) {
              this.visibleValue =
                this.visibleValue.concat(this.transferLeftCheckData)
              this.transferLeftCheckData = [] // 清除左侧选中的  不然下次向右拉取时会有缓存
            } else {
              // 否则就只拉取当前一个
              this.visibleValue.push(this.draggingKey)
            }
          }
        }
      })
    },
    methods: {
        //关闭弹框
        handleClose(){
            this.$emit("handleCloseTableItem");
        },
        //点击确定按钮
        handleConfirm(){
            if(this.visibleValue.length<=0){
                this.$message({
		          message: '未选中任何需要显示的数据',
		          type: 'warning'
		        });
                return;
            }
            this.$emit("handleConfirmVisible",this.visibleValue)
        },
      drag(ev, option) {
        // 赋值当前拖拽的唯一标识
        this.draggingKey = option[this.transferProps.key]
      },
      leftCheckChange(val) {
        // 穿梭框左侧多选选中
        this.transferLeftCheckData = [...val]
      },
      rightCheckChange(val) {
        // 穿梭框右侧多选选中
        this.transferRightCheckData = [...val]
      },
    },
}
</script>

<style lang="scss" scoped>
.delete-info-dialog{
    /deep/ .el-dialog__body{
        max-height: 400px;
        overflow-y: auto;
    }
    .info-body{
        color: #909399;
        padding-top: 20px;
        padding-bottom: 20px;
        .success-info{
            height: 20px;
            line-height: 20px;
        }
        .info-list{
            .info{
                padding: 0 10px;
                line-height: 20px;
                word-break: break-all;
            }
        }

    }
}
</style>



封装组件的应用:

提示:上面的代码是封装好的支持拖拽的穿梭框,且在弹框内哦,不在弹框内的自己修改下吧O(∩_∩)O哈哈~:

第一步:在需要的页面引入封装好的组件
//此处用Button触发弹框打开,不需要弹框的省略
<el-button @click="tableItemDialogVisible= true">打开封装好的穿梭框组件弹框</el-button>

<visible-dialog
            :default-table-item-list="defaultTableItemList"
            :table-item-dialog-visible="tableItemDialogVisible"
            v-if="tableItemDialogVisible"
            :visible-table-item-list="visibleTableItemList"
            :transfer-data-list="transferDataList"
            :transfer-props="{key: 'property',label: 'label',}"
            @handleCloseTableItem="handleCloseTableItem"
            @handleConfirmVisible="handleConfirmVisible"
        ></visible-dialog>

<script>
import VisibleDialog from "../../../components/visible-dialog/index.vue"; //记得修改地址哦,这里引入的就是上面那个封装好的组件,地址务必改为自己的哈,文件名也记得改为自己的哈
export default {   
	components: {
		VisibleDialog 
	},
	data(){
            return {               
                tableItemDialogVisible: true,//显隐弹框
                defaultTableItemList: ['date', 'invoiceClass', 'invoiceCode', 'invoiceNum', 'isTravel'],//如果visibleTableItemList为空,直接取defaultTableItemList的值,这个逻辑可根据自己需要自行修改
                transferDataList: [
               		 {label: '销售方名称', property: 'salename', align:'left', minWidth: 180},
					 {label: '销售方税号', property: 'saleaxpayerid', align:'left', minWidth: 180},
					 {label: '购买方税号', property: 'buytaxnumber', align:'left', minWidth: 180},
               		{label: '发票日期', property: 'date', minWidth: 120,},
				    {label: '发票类型', property: 'invoiceClass', minWidth: 180,},
				    {label: '发票代码', property: 'invoiceCode', minWidth: 180,},
				    {label: '发票号码', property: 'invoiceNum', minWidth: 180,},
				    {label: '是否差旅', property: 'isTravel', minWidth: 120},
                ],
                visibleTableItemList: [],//这个数组的数据是从后来请求回来的,用户自己设置好顺序的数据,如果没有设置就会去取defaultTableItemList里面的数据,这个demo中咱们就默认为空,假装用户还没设置过哈
            }
        },
        methods: {
			//  关闭显示列弹框
            handleCloseTableItem(){
                this.tableItemDialogVisible = false;
            },
            
            // 点击弹框的确认按钮事件,array就是用户自己放在右侧排好序的数组,
            handleConfirmVisible(array){
            	console.log("用户选择好的右侧的数据",array)
              // 这里就可以向后端发送请求,保存用户排好序的数据了
            },
        }
        
}
</script>

结语:

提示:按照上述代码复制到自己项目中即可实现顶部GIF中的功能了,如有问题,欢迎留言交流,欢迎大佬指点。

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

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

相关文章

Y-MODEM协议定制上位机

最近在使用N32G031和STM32F10X系列单片机进行IAP&#xff0c;使用的是Ymodem协议。单片机上的软件已经完成了&#xff0c;一般是使用secureCRT这样的工具作为上位机来进行测试&#xff0c;后来想做一个定制化的简单的上位机。在网上找了下资料&#xff0c;以下这篇文章写的使用…

能量不够,AI帮助

问CHAT&#xff1a;有关工程管理的中文参考文献 CHAT回复&#xff1a; 1. 余庆麟. (2009). 工程项目管理[M]. 北京&#xff1a; 机械工业出版社. 2. 刘宁. (2013). 工程建设管理[M]. 北京&#xff1a;清华大学出版社. 3. 谈刚&#xff0c;陈嘉春&#xff0c;尚雅凡. (2014).…

最详细的软件测试面试题整理与分析

前言 时光荏苒&#xff0c;一转眼到了2023年末尾&#xff0c;2024年也快要来了&#xff0c;人员就业市场以往的寒冬也貌似有了转暖的迹象&#xff0c;身边大批的就业人员也开始了紧张的备战之中。 近几周也和多家合作公司的HR进行了沟通&#xff0c;发现虽然岗位就业情况较去年…

2021年06月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 下图中的程序执行一次之后,“我的变量”最终的值是? A:0或者1 B:true或者false C:包含或者不包含 D:成立或者不成立 答案:B 积木“()包含()”是一个条件判断语句,它判…

Unity RenderFeature架构分析

自定义RenderFeature接口流程 URP内部ScriptableRenderPass分析 public、protected属性 renderPassEvent &#xff1a;渲染事件发生的时刻colorAttachments &#xff1a;渲染的颜色纹理列表 m_ColorAttachmentscolorAttachment &#xff1a;m_ColorAttachments[0];depthAttac…

【转载】如何在Macbook上把Ubuntu安装到移动硬盘里

我的设备系统版本、遇到的问题和解决&#xff1a; Mac&#xff1a;macOS Ventura 13.3 Ubuntu&#xff1a;22.04.3 问题&#xff1a; 按照这个教程在【3.3.10】修改完启动项后&#xff0c;Mac系统无法启动&#xff0c;Ubuntu可以正常启动。 原因&#xff1a; Mac找不到启动引导…

自己实名绑定了几个微信号?赶紧来看看

我们都明白&#xff0c;微信的重要性无处不在&#xff0c;它与我们生活的方方面面紧密相连。如今&#xff0c;微信支付已成为我们日常生活中不可或缺的一部分。无论是购物、用餐&#xff0c;还是日常消费&#xff0c;微信支付都能轻松解决。如果你担心携带现金会有遗失的风险&a…

服务台需要跟踪的3个重要指标MTBF+MTTF+MTTR

衡量标准是IT服务管理的核心&#xff0c;可提供有关运营的见解&#xff0c;并帮助确定需要持续改进的领域。通常的服务台指标有助于展示内部运营效率。例如&#xff0c;衡量在规定时间内解决问题的工单数量的 SLA 是展示服务台效率的关键因素。另一方面&#xff0c;故障指标可帮…

那些被玩烂了的设计模式

单例模式 单例模式是指一个类在一个进程中只有一个实例对象&#xff08;但也不一定&#xff0c;比如Spring中的Bean的单例是指在一个容器中是单例的&#xff09; 单例模式创建分为饿汉式和懒汉式&#xff0c;总共大概有8种写法。但是在开源项目中使用最多的主要有两种写法&am…

SPSS信度分析

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

HBase之Region Splitting

目录 Region Splitting步骤 Region状态过程 Region Splitting 步骤 RegionServer开始split region&#xff0c;SPLIT事务开启。RegionServer在表上获取共享读锁防止split过程中数据被修改。接着在zk中创建一个znode&#xff0c;标记为SPLITTING。Master 将会观察到该znode的创…

linux如何查看文件的hash数值

在Linux系统中&#xff0c;你可以使用各种工具来查看文件的哈希值。下面是一些常见的方法&#xff1a; md5sum命令&#xff1a; md5sum 文件名例如&#xff1a; md5sum example.txtsha1sum命令&#xff1a; sha1sum 文件名例如&#xff1a; sha1sum example.txtsha256sum命令&a…

Android 打包aar包含第三方aar 解决方案

Android 打包aar包含第三方aar 因项目需要&#xff0c;打包aar包含第三方aar&#xff0c;如果直接对module进行打包会产生一些问题。 * What went wrong: Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken be…

加速你的自动化测试:3种等待方式!

在自动化测试中&#xff0c;等待是一个重要的技术&#xff0c;用于处理页面加载、元素定位、元素状态改变等延迟问题。 等待能够确保在条件满足后再进行后续操作&#xff0c;提高自动化测试的稳定性以及可靠性。 等待方式&#xff1a;显示等待、隐式等待、线程睡眠 1. 显式等…

Python丨让简历脱颖而出的关键,居然是“它”!

进入疫情后时代&#xff0c;各行各业都在力争新的发展&#xff01;财会行业亦是如此&#xff0c;浏览各大招聘网站&#xff0c;不难发现财会相关岗位的招聘要求越来越“卷”&#xff0c;那求职者如何才能让自己获得面试邀请呢&#xff1f; 答案就是&#xff1a;一份亮眼且具有…

设计模式—开闭原则

1.背景 伯特兰迈耶一般被认为是最早提出开闭原则这一术语的人&#xff0c;在他1988年发行的《面向对象软件构造》中给出。这一想法认为一旦完成&#xff0c;一个类的实现只应该因错误而修改&#xff0c;新的或者改变的特性应该通过新建不同的类实现。新建的类可以通过继承的方…

Duplicate 模型中的 ROLLUP(十六)

因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP&#xff0c;已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序&#xff0c;以命中前缀索引的作用。下面详细介绍前缀索引&#xff0c;以及如何使用 ROLLUP 改变前缀索引&#xff0c;以获得更好的查询效率。 前…

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

选择TikTok直播网络方案前的必读指南

真实、稳定、可靠的TikTok直播网络方案挑选需要考虑这三大要素&#xff1a; 原生IP、独享带宽、线路 之前小编也分享了不少关于TikTok直播网络搭建方法和注意事项的文章及用户案例&#xff0c;但还是不少TikTok直播卖家不知道各个方法有何区别&#xff0c;毕竟市面上说可以解决…

Centos 7、Debian、Ubuntu中tree指令的检查与下载

目录 前言 Centos 7中检查tree指令是否安装的两种办法 which指令检查 查看当前版本指令 不同版本下安装tree指令 Centos 7的发行版本 重点 Debian的发行版本 重点 Ubuntu的发行版本 重点 前言 在大多数Linux发行版中&#xff0c;tree命令通常不是默认安装的指令。…