需求:
我们在开发过程中,会遇到需要将一个数据选择做成穿梭框,但是要求穿梭框左侧为树形结构、右侧为无层级结构的数据展示,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