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