1、效果图
2、需求
首先接口获取到已添加人员列表,然后通过异步树+穿梭框进行人员的增加和删除。使用树结构展示组织信息,点击父节点展开按钮获取该组织下的所属组织,点击树节点传递获取该组织的所属人员,将人员展示在待添加列表,人员可以左移右移,并且左移时会进行回显(只回显属于该组织下的人员),最后点击保存就将右侧已添加列表进行保存。
3、源码
<template>
<div>
<el-button @click="handleAdd">添加</el-button>
<el-dialog :area="[940, 628]" title="添加推送用户" :visible.sync="isShow" :show-close="false">
<div class="tree-box">
<el-tree node-key="deptIndexCode" parent-key="parentIndexCode" :props="props"
:data="treeData" simple-data lazy :default-expanded-keys="expandNode"
:current-node-key="currentNode" :load="setTreeData" @node-click="handleClickNode"
></el-tree>
</div>
<div class="table-box">
<div class="left-box">
<h3>待添加({{ leftData.length }}/{{ leftData.length }})</h3>
<el-table :data="leftData" force-scroll max-height="426" @selection-change="handleLeftSelect">
<el-table-column type="selection"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
</el-table>
</div>
<div class="btn-box">
<el-button @click="moveRight" :disabled="!leftSelect.length!=0">
<i class="h-icon-angle_right_sm"></i>
</el-button>
<el-button @click="moveLeft" :disabled="!rightSelect.length!=0">
<i class="h-icon-angle_left_sm"></i>
</el-button>
</div>
<div class="right-box">
<h3>已添加({{ rightData.length }}/{{ rightData.length }})</h3>
<el-table :data="rightData" force-scroll max-height="426" @selection-change="handleRightSelect">
<el-table-column type="selection"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
</el-table>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :disabled="rightData.length == 0" @click="handleSubmit">保 存</el-button>
<el-button @click="handleCancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getDepartment, getUsers } from '@/service/importantPersonArrange'
export default {
name: 'LastForm',
data() {
return {
list: [], // 接口获取到的右侧数据列表
props: {
label: 'name',
children: 'zones'
},
treeData: [],
expandNode: [],
currentNode: "",
isShow: false, // 当前选中树节点
treeSelect: {},
leftData: [], // 待添加数据
rightData: [], // 已添加数据
leftSelect: [], // 勾选的待添加数据
rightSelect: [], // 勾选的已添加数据
copyRightData: [], // 拷贝已添加数据
}
},
methods: {
// 打开添加推送用户弹窗
handleAdd() {
this.isShow = true;
// 深拷贝右侧数据,如果用户点击取消进行还原
this.copyRightData = JSON.parse(JSON.stringify(this.rightData));
// 这一步操作是过滤左侧数据,因为可能出现用户在弹窗页面移动数据到左侧之后直接点击取消,leftData数据未还原
this.getPersonList();
},
// 获取部门
async setTreeData(node, resolve) {
const params = {
parentId: node.data.deptIndexCode || "-1",
}
const { data } = await getDepartment(params);
// 初始化时默认查展开根节点,并选中根节点
if (params.parentId == -1) {
this.expandNode = [data[0].deptIndexCode];
this.currentNode = data[0].deptIndexCode;
this.getPersonList(data[0]);
}
resolve(data);
},
// 点击树节点
handleClickNode(data) {
this.treeSelect = data;
this.getPersonList();
},
// 获取部门用户
getPersonList() {
if (this.treeSelect.deptIndexCode) {
getUsers({
deptIndexCode: this.treeSelect.deptIndexCode,
}).then(res => {
this.leftData = res.data.map(item => {
// 这里将左侧的数据格式进行过滤,跟右侧接口读取到的数据格式一致
return {
indexCode: item.userIndexCode,
username: item.realName,
// 后端返回的右侧数据格式必须增加一个deptIndexCode(或其他字段)标识,标明所属的树节点indexCode
deptIndexCode: item.deptIndexCode
}
})
// 将每次点击异步树节点后获取到的用户数据过滤掉跟右侧数据重复的数据
this.leftData = this.leftData.filter(x =>
!this.rightData.some(y => x.indexCode === y.indexCode)
)
})
}
},
// 待添加数据选中
handleLeftSelect(data) {
this.leftSelect = data;
},
// 已添加数据选中
handleRightSelect(data) {
this.rightSelect = data;
},
// 数据右移
moveRight() {
// 右侧数据添加左侧勾选的数据
this.rightData = this.rightData.concat(this.leftSelect);
// 左侧数据删除勾选的数据
this.leftData = this.leftData.filter(x => this.leftSelect.every(y => x.indexCode != y.indexCode));
},
// 数据左移
moveLeft() {
// 左侧数据添加左移列表中跟当前选择树节点deptIndexCode相同的数据
this.rightSelect.forEach(item => {
if (item.deptIndexCode === this.treeSelect.deptIndexCode) {
this.leftData.push(item);
}
});
// 右侧数据删除勾选的数据
this.rightData = this.rightData.filter(x => this.rightSelect.every(y => x.indexCode != y.indexCode));
},
// 保存
handleSubmit() {
// 更新接口获取到的右侧数据列表,以进行保存操作
this.list = this.rightData;
this.isShow = false;
},
// 取消
handleCancel() {
// 点击取消按钮还原右侧数据
this.rightData = JSON.parse(JSON.stringify(this.copyRightData));
this.isShow = false;
}
},
mounted() {
// todo 接口获取 list 列表
this.rightData = JSON.parse(JSON.stringify(this.list));
}
}
</script>
<style lang="less" scoped>
/deep/ .el-dialog__body {
padding: 0;
}
// /deep/ .el-table::before {
// height: 0;
// }
// /deep/ .el-table .el-table__body-wrapper::before {
// height: 0;
// }
/deep/ .el-dialog__body-wrapper {
padding: 30px 40px;
}
.tree-box {
height: 476px;
border: 1px solid rgb(229, 229, 229);
border-right: 0px;
width: 240px;
display: inline-block;
vertical-align: top;
padding: 12px 8px;
}
/deep/ .table-box {
height: 476px;
width: 620px;
display: inline-block;
vertical-align: top;
.left-box,
.right-box {
width: 274px;
height: 476px;
padding: 0 16px;
border: 1px solid #e5e5e5;
display: inline-block;
vertical-align: top;
h3 {
height: 48px;
padding: 12px 0;
margin: 0;
color: #4c4c4c;
font-size: 14px;
font-weight: normal;
line-height: 20px;
}
.el-table {
border: 0px;
}
.el-table:after {
background-color: transparent;
}
.el-table__empty-block {
min-height: 389px;
}
}
.btn-box {
width: 72px;
height: 476px;
display: inline-block;
position: relative;
/deep/ .el-button {
width: 40px;
min-width: 40px;
padding: 0;
position: absolute;
left: 50%;
margin-left: -20px;
top: 150px;
&:last-of-type {
top: 206px;
}
&.is-disabled {
background-color: transparent;
}
}
}
}
</style>