核心:对el-tree进行操作以及对数据的处理
本例子中左侧为单列用户、(原为给某个文档目录下添加可查看文档的用户,现在需要精细化的控制用户对文档的操作权限,**产品),穿梭到右侧后在每个用户下添加权限节点用于勾选权限
自定义树形穿梭框组件(理论上左右均可自定义数据结构)
<template>
<div class="tree-transfer">
<!-- 穿梭框左侧 -->
<div class="tree-transfer-left">
<!-- 左侧采用element-ui的el-tree -->
<el-input placeholder="输入关键字进行过滤" v-model="filterText" style="padding:10px;" />
<div v-show="dataLeft.length > 0" style="width:95%;height:230px;overflow-y:auto;margin:0 auto;">
<el-tree ref="treeLeft" :data="dataLeft" show-checkbox node-key="id" :props="defaultProps"
:filter-node-method="filterNode" @check-change="leftCheckChange" default-expand-all>
</el-tree>
</div>
<div v-show="!dataLeft.length > 0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
暂无数据
</div>
</div>
<!-- 穿梭框中间按钮区域 -->
<div class="tree-transfer-middle">
<!-- -->
<el-button circle type="info" icon="el-icon-arrow-left"
:style="selectRightList.length > 0 ? 'background:#409EFF;border-color:#409EFF' : ''"
@click="removeClick"></el-button>
<el-button circle type="info" icon="el-icon-arrow-right"
:style="selectLeftList.length > 0 ? 'background:#409EFF;border-color:#409EFF' : ''"
@click="addClick"></el-button>
</div>
<!-- 穿梭框右侧 -->
<div class="tree-transfer-right">
<!-- 右侧直接放置结果 -->
<!-- 这里也采用tree结构,默认是对数据进行处理使得没有树形结构,也可以选择有树形结构 -->
<div style="height:50px;width:100%;line-height:50px;margin-bottom:10px;border-bottom: 1px solid #ccc;">
已选择 </div>
<div v-show="dataRight.length > 0">
<el-tree ref="treeRight" :data="dataRight" show-checkbox node-key="id" :props="defaultProps"
@check-change="rightCheckChange">
</el-tree>
</div>
<div v-show="!dataRight.length > 0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
暂无数据
</div>
</div>
</div>
<el-button @click="submit">提交</el-button>
</template>
定义需要用到的变量
let treeLeft = ref(null); //左侧树
let treeRight = ref(null); //右侧树
let defaultProps = {
children: "child",
label: "name",
}
let yuanlaiData = [] //保存一个原始树
let dataLeft = ref([]) //左侧树--数据
let selectLeftList = [] //左侧树-勾选
let filterText = ref('') //左侧的树进行过滤
let dataRight = ref([]) //右侧树--数据
let selectRightList = [] //右侧树-勾选
let setUserstreeData = [] //获取的一个完整树
let currentData = [] //这个树是剔除右侧数据的树
let dataListData = [{
id: "11",
name: "集团公司"
}, {
id: "22",
name: "美丽公司"
}]
let permissionData = [
{
id: "1111",
name: "管理权限",
child: null,
type: 1,
},
{
id: "2222",
name: "阅读权限",
child: null,
type: 2,
}
]
左侧树初始化数据,方法中添加对数据回显时的处理
function getReViewOfficeTree() {
setUserstreeData = dataListData;
if (Object.keys(setUserstreeData).length > 0) {
dataLeft.value = setUserstreeData;
let data = JSON.parse(JSON.stringify(setUserstreeData));
yuanlaiData = data;
}
}
数据向右侧流动方法
function addClick() {
// 定义一个递归过滤的方法,用来删掉父级中给的元素
let list = treeLeft.value.getCheckedNodes(); //勾选的节点
// 选中的数据list,遍历树,找到对应的id,将这条数据进行删除
list.forEach((item, index) => {
getTreeName(dataLeft.value, item.id);
});
setRightList(list)
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
}
function setRightList(list) {
for (let i of list) {
let a = JSON.parse(JSON.stringify(permissionData))
for (let j of a) {
j.parentId = i.id
j.id = generateRandomId()
}
i.child = a
}
// 3.回显设置右侧选中
let arr = []
dataRight.value.push(...list);
dataRight.value.map(item => {
props.data.map(item2 => {
if (item.id == item2.userId) {
item.child.map(item3 => {
if (item2.permiss.includes(item3.type)) {
console.log("item3", item3)
arr.push(item3)
}
})
}
})
})
nextTick(() => {
treeRight.value.setCheckedNodes(arr);
})
}
// 循环遍历树形结构,删除符合条件的叶子节点
function getTreeName(list, id) {
for (let i = 0; i < list.length; i++) {
let a = list[i];
if (a.id === id) {
const index = list.findIndex((item) => item.id === id);
list.splice(index, 1);
return a.name;
} else {
if (a.child && a.child.length > 0) {
let res = getTreeName(a.child, id);
if (res) {
return res;
}
}
}
}
}
数据向左侧流动方法
function removeClick() {
// 从右侧移除时的方法
// 1.从右侧删除选中的数据
let list = treeRight.value.getCheckedNodes();
for (let item of list) {
let index = dataRight.value.findIndex((item2) => {
return item.id == item2.id;
});
if (index >= 0) {
dataRight.value.splice(index, 1);
}
}
// 2.遍历右边的数据,将左边树对应的数据删除---右边存在的数据不能在左边树存在
if (dataRight.value.length > 0) {
currentData = JSON.parse(JSON.stringify(yuanlaiData));
dataRight.value.forEach((item, index) => {
getTreeName(currentData, item.id);
dataLeft.value = currentData;
});
} else {
dataLeft.value = JSON.parse(JSON.stringify(yuanlaiData));
}
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
}
完整代码
<template>
<!-- 自定义树形穿梭框组件、理论上左右均可选择是否为树形结构,目前固定为左侧树形、右侧无层级结构 -->
<div class="tree-transfer">
<!-- 穿梭框左侧 -->
<div class="tree-transfer-left">
<!-- 左侧采用element-ui的el-tree -->
<el-input placeholder="输入关键字进行过滤" v-model="filterText" style="padding:10px;" />
<div v-show="dataLeft.length > 0" style="width:95%;height:230px;overflow-y:auto;margin:0 auto;">
<el-tree ref="treeLeft" :data="dataLeft" show-checkbox node-key="id" :props="defaultProps"
:filter-node-method="filterNode" @check-change="leftCheckChange" default-expand-all>
</el-tree>
</div>
<div v-show="!dataLeft.length > 0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
暂无数据
</div>
</div>
<!-- 穿梭框中间按钮区域 -->
<div class="tree-transfer-middle">
<!-- -->
<el-button circle type="info" icon="el-icon-arrow-left"
:style="selectRightList.length > 0 ? 'background:#409EFF;border-color:#409EFF' : ''"
@click="removeClick"></el-button>
<el-button circle type="info" icon="el-icon-arrow-right"
:style="selectLeftList.length > 0 ? 'background:#409EFF;border-color:#409EFF' : ''"
@click="addClick"></el-button>
</div>
<!-- 穿梭框右侧 -->
<div class="tree-transfer-right">
<!-- 右侧直接放置结果 -->
<!-- 这里也采用tree结构,默认是对数据进行处理使得没有树形结构,也可以选择有树形结构 -->
<div style="height:50px;width:100%;line-height:50px;margin-bottom:10px;border-bottom: 1px solid #ccc;">
已选择 </div>
<div v-show="dataRight.length > 0">
<el-tree ref="treeRight" :data="dataRight" show-checkbox node-key="id" :props="defaultProps"
@check-change="rightCheckChange">
</el-tree>
</div>
<div v-show="!dataRight.length > 0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
暂无数据
</div>
</div>
</div>
<el-button @click="submit">提交</el-button>
</template>
<script setup>
import { nextTick, onMounted, ref, watch } from 'vue'
let props = defineProps({
data: {
type: Array,
default: () => [{ userId: 11, permiss: [1, 2] }, { userId: 22, permiss: [2] }]
}
})
let treeLeft = ref(null); //左侧树
let treeRight = ref(null); //右侧树
let defaultProps = {
children: "child",
label: "name",
}
let yuanlaiData = [] //保存一个原始树
let dataLeft = ref([]) //左侧树--数据
let selectLeftList = [] //左侧树-勾选
let filterText = ref('') //左侧的树进行过滤
let dataRight = ref([]) //右侧树--数据
let selectRightList = [] //右侧树-勾选
let setUserstreeData = [] //获取的一个完整树
let currentData = [] //这个树是剔除右侧数据的树
let dataListData = [{
id: "11",
name: "集团公司"
}, {
id: "22",
name: "美丽公司"
}]
let permissionData = [
{
id: "1111",
name: "管理权限",
child: null,
type: 1,
},
{
id: "2222",
name: "阅读权限",
child: null,
type: 2,
}
]
onMounted(() => {
getReViewOfficeTree();
})
// 监控data中的数据变化
watch(filterText, (newVal, oldVal) => {
treeLeft.value.filter(newVal);
})
// 获取左侧的树
function getReViewOfficeTree() {
setUserstreeData = dataListData;
if (Object.keys(setUserstreeData).length > 0) {
dataLeft.value = setUserstreeData;
let data = JSON.parse(JSON.stringify(setUserstreeData));
yuanlaiData = data;
// 如果要回显数据,则调用这个方法
let arr = []
if (props.data.length > 0) {
props.data.forEach((item, index) => {
dataLeft.value.forEach((item2, index2) => {
if (item.userId == item2.id) {
arr.push(dataLeft.value[index2])
dataLeft.value.splice(index2, 1);
}
})
});
setRightList(arr)
}
}
}
function addClick() {
list.forEach((item, index) => {
getTreeName(dataLeft.value, item.id);
});
setRightList(list)
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
}
function setRightList(list) {
for (let i of list) {
let a = JSON.parse(JSON.stringify(permissionData))
for (let j of a) {
j.parentId = i.id
j.id = generateRandomId()
}
i.child = a
}
// 3.回显设置右侧选中
let arr = []
dataRight.value.push(...list);
dataRight.value.map(item => {
props.data.map(item2 => {
if (item.id == item2.userId) {
item.child.map(item3 => {
if (item2.permiss.includes(item3.type)) {
console.log("item3", item3)
arr.push(item3)
}
})
}
})
})
console.log("arr", arr)
nextTick(() => {
treeRight.value.setCheckedNodes(arr);
// treeRight.value.setCheckedNodes([]);
})
}
function generateRandomId() {
const randomId = Math.random().toString(36).substring(2, 10);
return randomId;
}
function submit() {
let list = treeRight.value.getCheckedNodes();
console.log("提交", dataRight.value);
let params = dataRight.value.map(item => {
let perArry = list.map(item2 => {
if (item2.parentId == item.id) {
console.log("perArry", item2)
return item2.type
}
})
return {
id: item.id,
name: item.name,
permiss: perArry
}
})
for (let i of dataRight.value) {
i.permiss = []
for (let j of list) {
if (i.id == j.parentId) {
console.log("j", j)
i.permiss.push(j.type)
}
}
}
console.log("params", dataRight.value)
}
// 循环遍历树形结构,删除符合条件的叶子节点
function getTreeName(list, id) {
for (let i = 0; i < list.length; i++) {
let a = list[i];
if (a.id === id) {
const index = list.findIndex((item) => item.id === id);
list.splice(index, 1);
return a.name;
} else {
if (a.child && a.child.length > 0) {
let res = getTreeName(a.child, id);
if (res) {
return res;
}
}
}
}
}
// 将所有根节点禁选
function getTreeDisable(list) {
for (let i = 0; i < list.length; i++) {
let a = list[i];
if (a.child && a.child.length > 0) {
a.disabled = true;
getTreeDisable(a.child);
}
}
}
function removeClick() {
// 从右侧移除时的方法
// 1.从右侧删除选中的数据
let list = treeRight.value.getCheckedNodes();
for (let item of list) {
let index = dataRight.value.findIndex((item2) => {
return item.id == item2.id;
});
if (index >= 0) {
dataRight.value.splice(index, 1);
}
}
// 2.遍历右边的数据,将左边树对应的数据删除---右边存在的数据不能在左边树存在
if (dataRight.value.length > 0) {
currentData = JSON.parse(JSON.stringify(yuanlaiData));
dataRight.value.forEach((item, index) => {
getTreeName(currentData, item.id);
dataLeft.value = currentData;
});
} else {
dataLeft.value = JSON.parse(JSON.stringify(yuanlaiData));
}
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
}
function getResult() {
return dataRight.value;
}
function leftCheckChange(data, checked, indeterminate) {
console.log("1212dsageddsa", data, checked, indeterminate);
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
}
function rightCheckChange(data, checked, indeterminate) {
selectRightList = treeRight.value.getCheckedNodes(); //用于控制左侧按钮颜色
selectLeftList = treeLeft.value.getCheckedNodes(); //用于控制右侧按钮颜色
}
// 左侧树 支持过滤
function filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
}
</script>
<style scoped lang="scss">
:deep(.el-tree) {
background-color: rgba(0,0,0,0);
color: #ffff;
.el-checkbox__inner{
background:rgba(0,0,0,0);// checkbox 背景透明
}
.el-tree-node__content:hover {
background: rgba(104,147,215,0.4);// hover 节点背景色,否则是白色
}
.el-tree-node:focus>.el-tree-node__content {
background-color: rgba(0,0,0,0); // focus 节点背景透明,否则是白色
}
}
.tree-transfer {
display: flex;
min-height: 250px;
.tree-transfer-left {
width: 300px;
border: 1px #E5E5E5 solid;
border-radius: 10px;
height: 330px;
overflow-y: auto;
}
.tree-transfer-middle {
display: flex;
justify-content: center;
align-items: center;
width: 14%;
}
.tree-transfer-right {
width: 300px;
border: 1px #E5E5E5 solid;
border-radius: 10px;
height: 330px;
overflow-y: auto;
}
}
</style>