1.实现效果
2.整体思路
将左侧选中的节点移动到右侧,还要保持树结构,意味着移动子节点,需要把该子节点对应的父节点甚至父节点的父节点一并移到右侧形成一个新的树结构,树结构的层级和原来的树保持一致,只是右侧展示的子节点为选中的子节点,
思路一:一开始准备左侧删除节点,右侧构建新的节点,生成一棵新的树,但是在节点组装时需要依次找到外层节点进行树结构组装,非常麻烦以及性能也不是很好;
思路二:利用elementUI的filter API对选中节点进行筛选,左侧筛选出未选中的,右侧筛选出选中的,用的还是同一棵树,用一个属性来区分是否选择,好处是子节点选中,父节点会跟随保存,不用重新构建树结构
左侧
<el-tree
ref="treeLeft"
:data="treeDataArr"
:props="props"
node-key="id"
show-checkbox
:filter-node-method="filterNodeLeft"
@check-change="handleCheckChange('left')"
/>
右侧
<el-tree
ref="treeRight"
:data="treeDataArr"
:props="props"
node-key="id"
show-checkbox
:filter-node-method="filterNodeRight"
@check-change="handleCheckChange('right')"
/>
筛选函数
this.$refs.treeLeft.filter();
this.$refs.treeRight.filter();
filterNodeLeft(value, data) {
return !data.selected;
},
filterNodeRight(value, data) {
return data.selected;
},
filter API用法
3.完整代码
<template>
<el-dialog
title="编辑"
:visible="isVisible"
width="50%"
custom-class="pw-edit"
:close-on-press-escape="false"
:close-on-click-modal="false"
:before-close="closeEditDialog"
>
<div class="per_container">
<div class="per_con_left">
<div class="per_con_title">未选</div>
<div class="check_all">
<el-checkbox
:indeterminate="config.left.isIndeterminate"
v-model="config.left.checkAll"
@change="handleCheckAll($event, 'left')"
>全选/全不选</el-checkbox
>
</div>
<div class="tree">
<el-tree
ref="treeLeft"
:data="treeDataArr"
:props="props"
node-key="id"
show-checkbox
:filter-node-method="filterNodeLeft"
@check-change="handleCheckChange('left')"
/>
</div>
</div>
<div class="operation">
<el-button type="primary" @click="toRight()"
>移入
<i class="el-icon-d-arrow-right"></i>
</el-button>
<el-button type="primary" icon="el-icon-d-arrow-left" @click="toLeft()"
>移除</el-button
>
</div>
<div class="per_con_right">
<div class="per_con_title">已选</div>
<div class="check_all">
<el-checkbox
:indeterminate="config.right.isIndeterminate"
v-model="config.right.checkAll"
@change="handleCheckAll($event, 'right')"
>全选/全不选</el-checkbox
>
</div>
<div class="tree">
<el-tree
ref="treeRight"
:data="treeDataArr"
:props="props"
node-key="id"
show-checkbox
:filter-node-method="filterNodeRight"
@check-change="handleCheckChange('right')"
/>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="closeEditDialog" size="small">取 消</el-button>
<el-button type="primary" size="small" @click="submitEdit"
>确 定</el-button
>
</span>
</el-dialog>
</template>
<script>
export default {
props: {
isVisible: {
type: Boolean,
default: false,
},
treeData: [],
},
watch: {
isVisible(val) {
if (val) {
this.$nextTick(() => {
this.setTreeFilter();
this.$refs.treeLeft.setCheckedKeys([]);
this.$refs.treeRight.setCheckedKeys([]);
});
}
},
treeData: {
handler(val) {
this.treeDataArr = val;
this.allParentKeys = this.treeDataArr.map((item) => {
return item.id;
});
if (this.$refs.treeLeft && this.$refs.treeRight) {
this.$nextTick(() => {
this.setTreeFilter();
});
}
},
deep: true,
},
},
data() {
return {
props: {
label: "name",
},
isIndeterminateL: false,
isIndeterminateR: false,
checkAllLeft: false,
checkAllRight: false,
treeDataArr: [],
checkedKeys: [],
halfCheckedKeys: [],
checkedNodes: [],
config: {
left: {
isIndeterminate: false,
checkAll: false,
ref: "treeLeft",
},
right: {
isIndeterminate: false,
checkAll: false,
ref: "treeRight",
},
},
};
},
methods: {
closeEditDialog() {
this.$emit("closePerEdit");
},
setTreeFilter() {
this.$refs.treeLeft.filter();
this.$refs.treeRight.filter();
},
toLeft() {
this.checkedKeys = this.$refs.treeRight.getCheckedKeys();
this.halfCheckedKeys = this.$refs.treeRight.getHalfCheckedKeys();
this.settreeDataArr(this.treeDataArr, false);
this.setTreeFilter();
this.$refs.treeLeft.setCheckedKeys(this.checkedKeys);
this.$refs.treeRight.setCheckedKeys([]);
},
toRight() {
this.checkedKeys = this.$refs.treeLeft.getCheckedKeys();
this.halfCheckedKeys = this.$refs.treeLeft.getHalfCheckedKeys();
this.settreeDataArr(this.treeDataArr, true);
this.setTreeFilter();
this.$refs.treeRight.setCheckedKeys(this.checkedKeys);
this.$refs.treeLeft.setCheckedKeys([]);
},
filterNodeLeft(value, data) {
return !data.selected;
},
filterNodeRight(value, data) {
return data.selected;
},
// 递归设置数据选中状态
settreeDataArr(tree, type) {
const setTree = (treeDataArr) => {
treeDataArr.forEach((item, index) => {
if (
this.checkedKeys.includes(item.id) ||
this.halfCheckedKeys.includes(item.id)
) {
treeDataArr[index].selected = type;
}
if (item.children && item.children.length) {
setTree(item.children);
}
});
};
setTree(tree);
},
submitEdit() {
this.$emit("permissionData", this.treeDataArr);
},
// 勾选树结构时判断是否勾选上面的全选
handleCheckChange(type) {
this.checkedNodes = this.$refs[this.config[type].ref].getCheckedNodes();
const pIds = this.checkedNodes.filter((item) => {
return !item.pId;
});
if (!pIds.length) {
this.config[type].checkAll = false;
this.config[type].isIndeterminate = false;
return;
}
if (pIds.length === this.allParentKeys.length) {
this.config[type].checkAll = true;
this.config[type].isIndeterminate = false;
} else {
this.config[type].isIndeterminate = true;
this.config[type].checkAll = false;
}
},
// 全选
handleCheckAll(value, type) {
const keys = value
? this.treeDataArr.map((item) => {
return item.id;
})
: [];
this.$refs[this.config[type].ref].setCheckedKeys(keys, false);
},
},
};
</script>
<style lang="less" scoped>
/deep/.per_container {
display: flex;
height: 500px;
justify-content: space-between;
align-items: center;
.per_con_left,
.per_con_right {
width: 45%;
height: 100%;
}
.operation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
.el-button {
margin-left: 0;
margin-bottom: 10px;
}
}
.per_con_title {
height: 42px;
line-height: 26px;
border-radius: 8px 8px 0 0;
padding: 8px;
align-self: stretch;
background: #f2f6f9;
font-size: 16px;
box-sizing: border-box;
border: 1px solid #d8d8d8;
font-weight: 700;
text-align: left;
}
.check_all {
height: 42px;
line-height: 42px;
padding: 0 5px;
border: 1px solid #d8d8d8;
border-top: none;
text-align: left;
}
.tree {
height: calc(100% - 82px);
border: 1px solid #d8d8d8;
border-top: none;
overflow: auto;
}
}
</style>