vue实现自定义树形穿梭框功能
<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>