0


基于Element-ui 封装穿梭框(左侧树 右侧列表,可全选,列表可拖拽)

Element-ui提供的穿梭框只支持列表,根据实际需求自己写了一个左边是树结构,右边是列表结构的穿梭框,(如果需要两边都是树结构的话,需要把右侧的逻辑参考左侧改一改)拖拽使用了

vuedraggable

插件

效果图

左侧树右侧列表的穿梭框

组件代码

<template><div class="transfer-tree"><div class="transfer-panel"><div class="transfer-panel-header"><el-checkbox
          v-model="leftAllChecked":disabled="!(leftDataList && leftDataList.length)":indeterminate="isIndeterminateLeft"
          @change="handleCheckAllChangeLeft">{{ leftTitle }}</el-checkbox></div><div class="transfer-panel-body"><el-tree
          ref="leftTree"
          show-checkbox
          check-on-click-node
          default-expand-all
          :node-key="defaultProps.key":data="leftDataList":props="defaultProps"
          @check="handleCheckLeft"></el-tree></div></div><div class="transfer-buttons"><el-button
        class="mb8"
        size="mini"
        icon="el-icon-arrow-left":disabled="!(rightCheckedList && rightCheckedList.length)"
        @click="handleLeftChange"></el-button><el-button
        type="primary"
        size="mini"
        icon="el-icon-arrow-right":disabled="!(leftCheckedList && leftCheckedList.length)"
        @click="handleRightChange"></el-button></div><div class="transfer-panel"><div class="transfer-panel-header"><el-checkbox
          v-model="rightAllChecked":disabled="!(rightDataList && rightDataList.length)":indeterminate="isIndeterminateRight"
          @change="handleCheckAllChangeRight">{{ rightTitle }}</el-checkbox><!-- 右侧数据量/限制最大可保存数据量 --><span class="transfer-panel-ratio">{{ rightDataList.length }}/{{ maxLimitCount }}</span></div><div class="transfer-panel-body"><el-checkbox-group
          v-if="rightDataList && rightDataList.length"
          v-model="rightCheckedKeyList"
          @change="handleCheckRight"><draggable
            v-model="rightDataList"
            chosenClass="chosen"
            forceFallback="true"
            animation="200"
            @start="drag = true"
            @end="drag = false"
            @update="handleOrder"><transition-group><el-checkbox
                v-for="(item, index) in rightDataList":key="`right_${item[defaultProps.key]}_${index}`":label="item[defaultProps.key]">{{ item[defaultProps.label]}}<img
                  src="@/assets/drag_icon.svg"
                  alt="拖拽排序"
                  width="40"
                  height="15"/></el-checkbox></transition-group></draggable></el-checkbox-group><el-empty description="暂无数据" v-else></el-empty></div></div></div></template><script>import{ number }from'echarts';import draggable from'vuedraggable';exportdefault{name:'',components:{
    draggable,},props:{// tree的默认结构defaultProps:{type: Object,required:true,default:()=>({children:'children',label:'label',key:'key',parentKey:'parent',// 这个属性不是 tree组件需要的,是子节点数据中记录父节点标识的属性}),},// left 原始数据leftOriginalList:{type: Array,default:()=>[],},// right 原始数据rightOriginalList:{type: Array,default:()=>[],},// 最大可保存数据量maxLimitCount:{type: Number,default:0,},// left 标题leftTitle:{type: String,default:'可选项',},// right 标题rightTitle:{type: String,default:'已选项',},},data(){return{leftAllChecked:false,// left 全选checkboxleftDataList:[],// left 所有数据leftCheckedList:[],// left 选中的数据isIndeterminateLeft:false,rightAllChecked:false,// right 全选checkboxrightDataList:[],// right 所有数据rightCheckedList:[],// right 选中的数据 =>rightCheckedKeyList对应的 对象数组rightCheckedKeyList:[],// right 选中的 key list => 绑定在 el-checkbox-group上的 listisIndeterminateRight:false,drag:false,};},// 初始化watch:{leftOriginalList:{immediate:true,deep:true,handler(newVal){this.leftDataList =JSON.parse(JSON.stringify(newVal));this.leftCheckedList =[];this.leftAllChecked =false;this.isIndeterminateLeft =false;},},rightOriginalList:{immediate:true,deep:true,handler(newVal){this.rightDataList =JSON.parse(JSON.stringify(newVal));this.rightCheckedList =[];this.rightCheckedKeyList =[];this.rightAllChecked =false;this.isIndeterminateRight =false;},},},computed:{// left 所有子节点数据的数量leftDataTotal(){let count =0;this.leftDataList.forEach((v)=>{if(v[this.defaultProps.children]){
          count += v[this.defaultProps.children].length;}});return count;},},methods:{// 选择——lefthandleCheckLeft(val,{ checkedNodes }){// 包含了父节点const checkedCount = checkedNodes.length;const totalNodeCount =this.leftDataTotal +this.leftDataList.length;this.leftAllChecked = checkedCount === totalNodeCount;this.isIndeterminateLeft = checkedCount >0&& checkedCount < totalNodeCount;// 手动剔除父节点this.leftCheckedList = checkedNodes.filter((v)=>(!v[this.defaultProps.children]));},// 选择——righthandleCheckRight(val){const checkedCount = val.length;this.rightAllChecked = checkedCount ===this.rightDataList.length;this.isIndeterminateRight = checkedCount >0&& checkedCount <this.rightDataList.length;// 手动组织对象数组this.rightCheckedList =this.rightDataList.filter((v)=>(val.includes(v[this.defaultProps.key])));},// 全选——lefthandleCheckAllChangeLeft(val){this.isIndeterminateLeft =false;const checkedNodes =[];if(val){this.leftDataList.forEach((v)=>{
          checkedNodes.push(v);if(v[this.defaultProps.children]){
            v[this.defaultProps.children].forEach((child)=>{ checkedNodes.push(child);});}});}// 手动赋checkedlist值this.leftCheckedList = checkedNodes.filter((v)=>(!v[this.defaultProps.children]));this.$refs.leftTree.setCheckedNodes(checkedNodes);},// 全选——righthandleCheckAllChangeRight(val){this.isIndeterminateRight =false;this.rightCheckedKeyList = val ?this.rightDataList.map((v)=>(v[this.defaultProps.key])):[];// 手动赋checkedlist值this.rightCheckedList = val ?this.rightDataList.map((v)=>(v)):[];},// 传递 right => lefthandleLeftChange(){// left +const leftDataMap ={};this.leftDataList.forEach((v)=>{
        leftDataMap[v[this.defaultProps.key]]= v[this.defaultProps.children]||[];});this.rightCheckedList.forEach((v)=>{
        leftDataMap[v[this.defaultProps.parentKey]].push(v);});// right -this.rightDataList =this.rightDataList.filter((v)=>!(this.rightCheckedKeyList.includes(v[this.defaultProps.key])));// 清空选中数组this.rightCheckedList =[];this.rightCheckedKeyList =[];// right 全选 => 直接取消this.rightAllChecked =false;this.isIndeterminateRight =false;// left 全选 => 原先没有选中/半选中=>不动,原先全选=>半选中 => 重新渲染一次 tree组件选中if(this.leftAllChecked &&!this.isIndeterminateLeft){this.leftAllChecked =false;this.isIndeterminateLeft =true;}// 先清空再重置,直接重置的话,父节点的状态会有问题this.$refs.leftTree.setCheckedNodes([]);this.$nextTick(()=>{this.$refs.leftTree.setCheckedNodes(this.leftCheckedList);});// 传递当前数据分布this.$emit('change',{left:this.leftDataList,right:this.rightDataList,});},// 传递 left => righthandleRightChange(){// right +this.rightDataList.push(...this.leftCheckedList);// left -const{ key, children }=this.defaultProps;const checkedKeys =this.leftCheckedList.map((v)=>(v[key]));this.leftDataList.forEach((v)=>{if(v[children]){
          v[children]= v[children].filter((child)=>!checkedKeys.includes(child[key]));}});// 清空选中数组this.leftCheckedList =[];// 清空 tree组件选中this.$refs.leftTree.setCheckedNodes([]);// left 全选 => 直接取消this.leftAllChecked =false;this.isIndeterminateLeft =false;// right 全选 => 原先没有选中/半选中=>不动,原先全选=>半选中if(this.rightAllChecked &&!this.isIndeterminateRight){this.rightAllChecked =false;this.isIndeterminateRight =true;}// 传递当前数据分布this.$emit('change',{left:this.leftDataList,right:this.rightDataList,});},handleOrder(){// 传递当前数据分布this.$emit('change',{left:this.leftDataList,right:this.rightDataList,});},},};</script><style lang="scss" scoped>.transfer-tree {display: flex;width:100%;.transfer-panel {width:100%;height:100%;
    border-radius: 4px;border: 1px solid $color-border;.transfer-panel-header {display: flex;
      justify-content: space-between;height: 30px;
      line-height: 30px;
      border-radius: 3px 3px 0px 0px;padding:0 12px;::v-deep .el-checkbox {.el-checkbox__label {color: $color-text;
          font-size: 14px;
          padding-left: 8px;}}.transfer-panel-ratio {
        font-size: 12px;color: $color-text;}}.transfer-panel-body {height: 200px;padding: 12px 12px 0 12px;
      border-top: 1px solid $color-border;overflow: auto;.transfer-panel-filter {float: right;width: 170px;.el-checkbox__label {color: $color-text;
          font-size: 12px;
          padding-left: 8px;}.el-input__inner {height: 26px;border: none;}}::v-deep .el-tree {color: $color-text;
        margin-bottom: 4px;.el-tree-node__content {height: 22px;
          margin-bottom: 8px;.el-tree-node__label {
            font-size: 12px;}}.el-tree-node__children {.el-tree-node__content {
            padding-left: 12px!important;}}.el-tree-node__expand-icon {
          margin-left:-6px;}}::v-deep .el-checkbox-group {
        margin-bottom: 4px;.el-checkbox {display: block;
          line-height: 22px;color: $color-text;
          margin-bottom: 8px;width:100%;.el-checkbox__label {width:calc(100%- 5px);position: relative;
            font-size: 12px;
            padding-left: 8px;
            img{position: absolute;right:0;top: 2px;}}}}}}.transfer-buttons {display: flex;
    justify-content: center;
    flex-flow: column;margin:0 12px;.el-button {display: flex;
      justify-content: center;
      align-items: center;width: 32px;height: 24px;padding:0;
      margin-left:0;}}}::v-deep .el-empty {height: 60px;padding:0;.el-empty__image {display: none;}.el-empty__description {margin:0;}}</style>

父组件调用

<template><div><TransferTreeList
        :defaultProps="{ children: 'list', label: 'name', key: 'id', parentKey: 'classify' }":leftOriginalList="unselectedList":rightOriginalList="selectedList":maxLimitCount="10"
        @change="handelSelectedChange"/></div></template><script>import TransferTreeList from'@/components/TransferTreeList';exportdefault{name:'',components:{
    TransferTreeList,},data(){return{unselectedList:[// 未被选中的选项{id:'classify1',name:'分类1',list:[{id:'kpi1-1',name:'选项1-1',classify:'classify1'},{id:'kpi1-3',name:'选项1-3',classify:'classify1'},],},{id:'classify2',name:'分类2',list:[{id:'kpi2-1',name:'选项2-1',classify:'classify2'},{id:'kpi2-3',name:'选项2-3',classify:'classify2'},],},],selectedList:[// 被选中的选项(选项内部要有父节点标识){id:'kpi2-2',name:'选项2-2',classify:'classify2'},{id:'kpi1-2',name:'选项1-2',classify:'classify1'},],};},methods:{handelSelectedChange(data){
      console.log('最新数据', data)},},};</script>
标签: vue.js elementui 前端

本文转载自: https://blog.csdn.net/weixin_40615155/article/details/125606140
版权归原作者 Alice_hhu 所有, 如有侵权,请联系我们删除。

“基于Element-ui 封装穿梭框(左侧树 右侧列表,可全选,列表可拖拽)”的评论:

还没有评论