先上效果图:
问题描述:
1. 由于数据过多,后端对列表数据分页处理,前端一次性无法拿到所有分页,但是elementui自带的穿梭框无法满足分页的需求;
2. elementui 自带的穿梭框异步数据分页请求,切换页码时,选中的选项会被清掉,没办法记住选中的数据;
3. 涉及到编辑功能时,也需要做到数据的回显,这里起初后端给的数据仅有一个id列表,无法满足穿梭框分页、反显的功能条件,死磕许久,突然发现是后端接口数据格式不满足,需要去和后端同学沟通下。
解决方法:
1. 摒弃elementui框架自带的穿梭框,之前也有遇到改造elementui穿梭框的数据格式,前提是,展示的label就应该作为选中的key,这样如果移到右边就可以通过处理数据,将选中的非当前页的数据拼接到左侧的当前页列表中,这样就可以做到数据的反显了,不过这里也存在bug,规定一页展示10条,这样的话当前页的数据肯定是在10条之上的。所以考虑自己手动实现穿梭框的功能;
2. 由于需要勾选操作,采用elementui框架中的table实现,左右两个table,中间填充两个移动的按钮,通过点击确定添加、移除,更新两个table的数据、选中状态;
3. 这样的好处是,左右两侧数据独立,左边的翻页操作,对右边不会带来任何影响,都只涉及添加、移除等操作,不会有过多的处理数据、逻辑;
4. 为了能更好的呈现穿梭框的功能,这里考虑在对左侧移除、添加时,添加标识,动态控制当前行的显示与隐藏。即在table中定义 :row-class-name="rowClassName",通过动态复制,添加样式类名,控制行的显示隐藏,这样只要左边选择的数据移到右侧后,视觉上会有一种隐藏的效果。
3. 这样的好处是,左右两侧数据独立,左边的翻页操作,对右边不会带来任何影响,都只涉及添加、移除等操作,不会有过多的处理数据、逻辑;
4. 为了能更好的呈现穿梭框的功能,这里考虑在对左侧移除、添加时,添加标识,动态控制当前行的显示与隐藏。即在table中定义 :row-class-name="rowClassName",通过动态复制,添加样式类名,控制行的显示隐藏,这样只要左边选择的数据移到右侧后,视觉上会有一种隐藏的效果。
注意事项:
1. 后端需要保存你每次选中的数据,数据格式为一个对象数组,对象应该包含你列表需要展示的lable、id,如果后端只返回id数组,是没有办法实现分页、回现等功能的。
html代码实现如下:
<template>
<div class="transfer__container">
<div class="search__div">
<el-input
placeholder="请输入用例名称"
size="small"
v-model="caseName"
@keyup.enter.native="getCaseList"
clearable
@change="getCaseList"
@clear="getCaseList"
>
<el-button slot="append" icon="el-icon-search" @click="getCaseList"></el-button
></el-input>
</div>
<div class="table__container">
<div class="left__table">
<el-table
ref="tableRef"
:data="list"
tooltip-effect="dark"
style="width: 100%"
height="330"
:row-style="{ height: '20px' }"
@selection-change="handleSelectionChange"
:row-class-name="rowClassName"
:header-row-class-name="'tableHead'"
>
<el-table-column type="selection" width="55" :selectable="checkSelectable"> </el-table-column>
<el-table-column label="未选用例" width="400" show-overflow-tooltip>
<template slot="header" slot-scope="scope">
<span>未选用例</span>
<!-- <span style="" class="case__select-num"
>{{ bindSelection.length }}/{{ pagination.total }}</span
> -->
</template>
<template slot-scope="scope">{{ scope.row.ruleName }}</template>
</el-table-column>
</el-table>
<div class="view__pagination">
<el-pagination
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="pagination.pageSizes"
small
align="center"
layout="prev, pager, next"
@current-change="currentChangeHandler"
></el-pagination>
</div>
</div>
<div class="center__btn">
<el-button type="primary" @click="comfirmDeleteCase" :disabled="!unbindSelection.length"
><i class="el-icon-arrow-left"></i
></el-button>
<el-button type="primary" @click="comfirmAddCase" :disabled="!bindSelection.length"
><i class="el-icon-arrow-right"></i
></el-button>
</div>
<div class="right__table">
<el-table
ref="bindTableRef"
:data="bindList"
tooltip-effect="dark"
style="width: 100%"
height="330"
@selection-change="handlUnbindeSelectionChange"
>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column width="400" show-overflow-tooltip>
<template slot="header" slot-scope="scope">
<span>已选用例</span>
<!-- <span style="" class="case__select-num"
>{{ unbindSelection.length }}/{{ bindList.length }}</span
> -->
</template>
<template slot-scope="scope">{{ scope.row.ruleName }}</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import { getCaseList } from '@/api/test-case-manage';
import { cloneDeep } from 'lodash';
export default {
name: 'CaseTransfer',
props: {
modelVersionId: {
type: Number,
default: null
},
cases: {
type: Array,
default: () => {
return [];
}
}
},
data() {
return {
caseName: '', //搜索用例
//分页
pagination: {
total: 0,
currentPage: 1,
pageSize: 10,
pageSizes: [5, 10, 100]
},
list: [], // 全部的数据(包括已绑定和未绑定的数据)
bindSelection: [], //选中即将绑定的数据
bindList: [], //已绑定的数据
unbindSelection: [] //选中即将解绑的数据
};
},
methods: {
//编辑时,初始化穿梭框,将已选中的设置在右边
initTransfer() {
if (!this.cases.length) return;
this.bindList = cloneDeep(this.cases);
},
//根据选中的模型,获取用例
getCaseList() {
let params = {
pageNum: this.pagination.currentPage,
pageSize: this.pagination.pageSize
};
let data = {
ruleName: this.caseName,
levels: [],
priorities: [],
modelVersionId: this.modelVersionId
};
getCaseList(params, data)
.then(res => {
if (res.code !== '0') {
this.$message.error(res.message || '获取用例配置列表失败');
return;
}
this.list = [];
res.data.records.forEach((item, index) => {
this.list.push({
id: item.id,
ruleName: item.ruleName
});
});
// this.dealTableData(this.list);
this.pagination.total = res.data.total;
})
.catch(error => {
this.$message.error(error.message || '获取用例配置列表失败');
})
.finally(() => {
this.$nextTick(() => {
this.selectCacheData(this.bindList);
});
});
},
//绑定过的,需要在左边显示勾
selectCacheData(selections) {
const cacheIds = selections.map(item => item.id);
this.list.map((row,index) => {
if (cacheIds.includes(row.id)) {
// this.$refs.tableRef.toggleRowSelection(row);
row.disabled = true;
this.$set(this.list, index, row);
}
return row;
});
},
/**
* 是否可选
*/
checkSelectable(row) {
const state = this.bindList.some(el => {
return el.id === row.id;
});
return !state;
},
//获取即将绑定的数据
handleSelectionChange(val) {
this.bindSelection = val;
},
//获取即将移除的数据
handlUnbindeSelectionChange(val) {
this.unbindSelection = val;
},
//确定添加
comfirmAddCase() {
// this.bindList = this.bindSelection;
if (Array.isArray(this.bindSelection) && this.bindSelection.length > 0) {
const bindIds = this.bindList.map(item => item.id);
this.bindSelection.map(item => {
if (bindIds.includes(item.id) === false) {
this.$refs.tableRef.toggleRowSelection(item, true);
this.bindList = this.bindList.concat(item);
}
});
this.bindSelection = this.bindList.map(item => item.id);
this.list.map(item => {
if (this.bindSelection.includes(item.id)) {
item.disabled = true;
}
return item;
});
this.bindSelection = [];
}
},
//确定移除
comfirmDeleteCase() {
if (Array.isArray(this.unbindSelection) && this.unbindSelection.length > 0) {
let oldBindIds = this.bindList.map(item => item.id);
this.unbindSelection.map(item => {
if (oldBindIds.includes(item.id) === true) {
const index = oldBindIds.indexOf(item.id);
this.bindList.splice(index, 1);
oldBindIds.splice(index, 1);
}
});
// 对比 左右是否有交集
let newBindIds = this.bindList.map(item => item.id);
this.list.map(item => {
if (newBindIds.includes(item.id) === true) {
this.$refs.tableRef.toggleRowSelection(item, true);
item.disabled = true;
} else {
this.$refs.tableRef.toggleRowSelection(item, false);
item.disabled = false;
}
return item;
});
}
},
//根据标识disabled设置el-table列的动态显示,这里记得给table column添加key,这样可以保证列的显示隐藏正常。
rowClassName({ row }) {
if (!row.disabled) {
return;
}
return 'showRow';
},
//分页
currentChangeHandler(data) {
this.pagination.currentPage = data;
this.getCaseList();
},
//获取绑定数据的id
getBindIds() {
if (!this.bindList.length) {
this.$message.error('请选择用例!');
return false;
}
let ids = [];
this.bindList.map(item => {
ids.push(item.id);
});
return ids;
}
},
watch: {
modelVersionId: {
handler(newVal, oldVal) {
this.getCaseList();
if (!oldVal) {
this.initTransfer();
} else {
this.bindList = [];
}
},
deep: true
}
}
};
</script>
<style lang="scss" scoped>
.transfer__container {
display: flex;
flex-direction: column;
margin-left: 15px;
.search__div {
width: 457px;
margin-bottom: 15px;
}
.table__container {
display: flex;
.center__btn {
padding: 0 10px;
height: 330px;
display: flex;
flex-direction: column;
justify-items: center;
justify-content: center;
/deep/ .el-button--primary {
margin-left: 0px;
margin-top: 10px;
}
}
/deep/.el-table td,
.el-table th {
padding: 5px 0;
}
/deep/ .case__select-num {
margin-left: 10px;
color: #909399;
font-weight: 400;
}
}
/deep/.showRow {
display: none;
}
}
</style>