封装穿梭框
利用element组件封装了一个包含前端分页,查询,全选所有,全选当页,反选功能的穿梭框。
纯手动封装,如有建议欢迎评论探讨。话不多说,直接上代码!
组件文件
<template>
<div class="transfer">
<div class="left-box card-box">
<div class="box-top">
<el-dropdown placement="bottom-start">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down icon-margin"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="chooseAll('left')">全选所有</el-dropdown-item>
<el-dropdown-item @click.native="chooseCurrentAll('left')">全选当页</el-dropdown-item>
<el-dropdown-item @click.native="chooseReverse('left')">反选当页</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span class="total-tip">{{`${leftCheckList.length}/${leftTotal}台`}}</span>
<span class="title-tip">{{titles[0]}}</span>
</div>
<div class="box-content">
<div class="search-margin">
<el-input
:placeholder="filterPlaceholder"
prefix-icon="el-icon-search"
size="small"
v-model="leftSearch"
clearable
@input="searchClick($event,'left')"
></el-input>
</div>
<div class="checkbox-group">
<el-checkbox-group v-model="leftCheckKeys">
<el-checkbox
class="wrap-text"
v-for="item in leftTransferDataList"
@change="handleCheck($event, item, 'left')"
:key="item.value"
:label="item.label"
></el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="box-bottom">
<el-pagination
small
slot="left-footer"
@current-change="handleCurrentChange($event,'left')"
:current-page="leftPageOption.currentPage"
:page-size="leftPageOption.pageSize"
layout="prev, pager, next, jumper"
:total="leftPageOption.total"
></el-pagination>
</div>
</div>
<div class="buttons-box">
<el-button
type="primary"
icon="el-icon-arrow-right"
:disabled="leftCheckList.length<1"
@click="transferData('toRight')"
></el-button>
<el-button
type="primary"
icon="el-icon-arrow-left"
:disabled="rightCheckList.length<1"
@click="transferData('toLeft')"
></el-button>
</div>
<div class="right-box card-box">
<div class="box-top">
<el-dropdown placement="bottom-start">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down icon-margin"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="chooseAll('right')">全选所有</el-dropdown-item>
<el-dropdown-item @click.native="chooseCurrentAll('right')">全选当页</el-dropdown-item>
<el-dropdown-item @click.native="chooseReverse('right')">反选当页</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span class="total-tip">{{`${rightCheckList.length}/${rightTotal}台`}}</span>
<span class="title-tip">{{titles[1]}}</span>
</div>
<div class="box-content">
<div class="search-margin">
<el-input
:placeholder="filterPlaceholder"
prefix-icon="el-icon-search"
size="small"
v-model="rightSearch"
clearable
@input="searchClick($event,'right')"
></el-input>
</div>
<div class="checkbox-group">
<el-checkbox-group v-model="rightCheckKeys">
<el-checkbox
class="wrap-text"
v-for="item in rightTransferDataList"
:key="item.value"
:label="item.label"
@change="handleCheck($event, item, 'right')"
></el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="box-bottom">
<el-pagination
small
slot="right-footer"
@current-change="handleCurrentChange($event,'right')"
:current-page="rightPageOption.currentPage"
:page-size="rightPageOption.pageSize"
layout="prev, pager, next, jumper"
:total="rightPageOption.total"
></el-pagination>
</div>
</div>
</div>
</template>
<script>
export default {
name: "transferPage",
props: {
titles: {
type: Array,
default: () => ["待选列表", "已选列表"],
},
filterPlaceholder: {
type: String,
default: '请输入内容',
},
// 可筛选
filterable: {
type: Boolean,
default: false,
},
// 左侧数据
leftTransferData: {
type: Array,
default: () => []
},
// 右侧数据
rightTransferData: {
type: Array,
default: () => [],
},
},
data() {
return {
leftOriginDataList: [], // 源数据(未经过搜索过滤)
leftAllDataList: [], // 分页前的数据
leftTransferDataList: [], // 左侧展示列表(当前页)
leftCheckKeys: [], // 左侧被选中数据的label
leftCheckList: [], // 左侧被选中的数据列表
leftSearch: '', // 左侧搜索数据
leftPageOption: {
currentPage: 1, //当前页
pageSize: 10, //默认的每页显示条数
total: 0 //总条目数
},
rightOriginDataList: [], // 源数据
rightAllDataList: [], // 分页前的数据
rightTransferDataList: [],
rightCheckKeys: [],
rightCheckList: [],
rightSearch: '',
rightPageOption: {
currentPage: 1, //当前页
pageSize: 10, //默认的每页显示条数
total: 0 //总条目数
},
search: {
code: "",
name: "",
}, //存储搜索框中填入的内
stashSearch: {
code: "",
name: "",
}, //暂存的搜索列表
}
},
computed: {
leftTotal() {
return this.leftOriginDataList.length
},
rightTotal() {
return this.rightOriginDataList.length
},
},
watch: {
// 源数据改变
leftTransferData: {
handler() {
this.leftOriginDataList = JSON.parse(JSON.stringify(this.leftTransferData))
this.rightOriginDataList = JSON.parse(JSON.stringify(this.rightTransferData))
this.leftAllDataList = JSON.parse(JSON.stringify(this.leftTransferData))
this.rightAllDataList = JSON.parse(JSON.stringify(this.rightTransferData))
this.init()
},
deep: true,
immediate: true,
},
// 源数据改变,重新分页
rightOriginDataList: {
handler() {
this.$emit('transferChange', this.rightOriginDataList)
},
deep: true,
},
},
methods: {
init() {
this.leftOriginDataList = JSON.parse(JSON.stringify(this.leftTransferData))
this.rightOriginDataList = JSON.parse(JSON.stringify(this.rightTransferData))
this.leftAllDataList = JSON.parse(JSON.stringify(this.leftTransferData))
this.rightAllDataList = JSON.parse(JSON.stringify(this.rightTransferData))
this.handleDataChange()
},
// 全选所有
chooseAll(type) {
this[`${type}CheckList`] = this[`${type}AllDataList`]
this[`${type}CheckKeys`] = this[`${type}AllDataList`].map(item => item.label)
},
// 全选当页
chooseCurrentAll(type) {
this[`${type}CheckList`] = this[`${type}TransferDataList`]
this[`${type}CheckKeys`] = this[`${type}TransferDataList`].map(item => item.label)
this.$forceUpdate()
},
// 反选当页
chooseReverse(type) {
let currentCheck = this[`${type}CheckKeys`]
this[`${type}CheckList`] = this[`${type}TransferDataList`].filter(item => !currentCheck.includes(item.label)
)
this[`${type}CheckKeys`] = this[`${type}CheckList`].map(item => item.label)
},
// 选中回调
handleCheck(event, data, type) {
if (event) {
// 如果是选中
this[`${type}CheckList`].push(data)
} else {
// 如果是取消
this[`${type}CheckList`].splice(this[`${type}CheckList`].map(item => item.label).indexOf(data.label), 1)
}
},
// 数据穿梭
transferData(type) {
if (type == 'toLeft') {
// 1. 将右侧选中内容放入左侧数组
this.leftOriginDataList.unshift(...this.rightCheckList)
// 2. 清除右侧数组中的被选中数据
this.rightOriginDataList = JSON.parse(JSON.stringify(this.rightOriginDataList.filter(item =>
!this.rightCheckKeys.includes(item.label))
))
// 3. 清空右侧选中数组,右侧选中标签数组
this.rightCheckList = []
this.rightCheckKeys = []
} else if (type == 'toRight') {
// 1. 将左侧选中内容放入右侧数组
this.rightOriginDataList.unshift(...this.leftCheckList)
// 2. 清除左侧数组中的被选中数据
this.leftOriginDataList = JSON.parse(JSON.stringify(this.leftOriginDataList.filter(item =>
!this.leftCheckKeys.includes(item.label))
))
// 3. 清空左侧选中数组,左侧选中标签数组
this.leftCheckList = []
this.leftCheckKeys = []
}
this.handleDataChange()
},
// 数据穿梭后及数据初始化的数据处理
handleDataChange() {
// 1. origin源数据发生变化,根据搜索条件,让allDataList随之变化
// 搜索过滤会做的事: 根据搜索框内容过滤源数据, 分页(此处多了一次无用的分页)
this.searchFilter('left')
this.searchFilter('right')
// 2. 拿到正确的allDataList,处理分页数据:total,currentPage
this.leftPageOption.total = this.leftAllDataList.length
this.rightPageOption.total = this.rightAllDataList.length
// 穿梭后,如果当前框内数据不足currentPage页,则后退一页
if (this.leftPageOption.total !== 0 && this.leftPageOption.total <= ((this.leftPageOption.currentPage - 1) * this.leftPageOption.pageSize)) {
this.leftPageOption.currentPage--
}
// 穿梭后,如果当前框内数据不足currentPage页,则后退一页
if (this.rightPageOption.total !== 0 && this.rightPageOption.total <= ((this.rightPageOption.currentPage - 1) * this.rightPageOption.pageSize)) {
this.rightPageOption.currentPage--
}
// 3. 分页,也就是拿到正确的TransferDataList
this.leftTransferDataList = this.handleData(this.leftAllDataList, this.leftPageOption)
this.rightTransferDataList = this.handleData(this.rightAllDataList, this.rightPageOption)
// this.$forceUpdate()
},
//搜索功能
searchFilter(type) {
this[`${type}Search`] = this[`${type}Search`] ? this[`${type}Search`].trim() : ""
let filterList = []
// 没有搜索内容时,让未分页的经过筛选数据等于源数据
if (!this[`${type}Search`]) {
filterList = JSON.parse(JSON.stringify(this[`${type}OriginDataList`]))
} else {
filterList = this[`${type}OriginDataList`].filter((item) =>
item.label?.includes(this[`${type}Search`])
)
}
this.$set(this[`${type}PageOption`], 'total', filterList.length)
this[`${type}AllDataList`] = filterList
// 对过滤后的数据,分页
this[`${type}TransferDataList`] = this.handleData(this[`${type}AllDataList`], this[`${type}PageOption`])
this.$forceUpdate()
},
//搜索点击
searchClick(value, type) {
this.$set(this[`${type}PageOption`], 'currentPage', 1)
this[`${type}Search`] = value
this.searchFilter(type)
},
// 处理数据分页 allDataList-经过筛选的数据列表,pageOption-分页数据
handleData(allDataList, pageOption) {
let start = (pageOption.currentPage - 1) * pageOption.pageSize
let end = (start + pageOption.pageSize)
// >pageOption.total?pageOption.total:(start + pageOption.pageSize)
let currentPageList = []
currentPageList = allDataList.slice(start, end)
return currentPageList
},
//跳页回调
handleCurrentChange(val, type) {
this.$set(this[`${type}PageOption`], 'currentPage', val)
this[`${type}TransferDataList`] = this.handleData(this[`${type}AllDataList`], this[`${type}PageOption`])
},
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-dropdown-menu__item {
span{
width: 100%;
height: 100%;
}
}
::v-deep .el-pager li.active {
color: #004BAA;
}
::v-deep .el-pager li:hover, ::v-deep .el-pagination button:hover {
color: #407ecf;
}
::v-deep .el-dropdown-menu__item:focus, .el-dropdown-menu__item:not(.is-disabled):hover {
background-color: #F5F7FA;
color: #303133;
}
// 按钮禁用样式
::v-deep button.el-button.el-button--primary.el-button--small.is-disabled {
background-color: #EFEFEF;
border: 1px solid #CCC;
color: #303133;
}
::v-deep button.el-button.el-button--primary.el-button--small.is-disabled:hover {
background-color: #EFEFEF !important;
border: 1px solid #CCC !important;
color: #303133 !important;
}
.transfer {
width: 100%;
min-width: 680px;
display: flex;
align-items: center;
justify-content: space-between;
.card-box {
width: 40%;
min-width: 300px;
border: 1px solid #EBEEF5;
border-radius: 4px;
overflow: hidden;
background: #FFF;
}
.box-top {
display: flex;
align-items: center;
height: 40px;
line-height: 40px;
padding: 8px 12px 9px;
background: #F5F7FA;
border-bottom: 1px solid #EBEEF5;
.icon-margin {
font-size: 16px;
}
.total-tip {
padding: 0 5px;
}
.title-tip {
font-size: 16px;
color: #303133;
font-weight: 400;
}
}
.box-content {
width: 100%;
border-bottom: 1px solid #EBEEF5;
.search-margin {
margin: 15px;
}
.checkbox-group {
padding: 0 15px;
height: 300px;
overflow-x: hidden;
overflow-y: auto;
::v-deep .el-checkbox{
width: 100%;
height: 30px;
line-height: 30px;
display: flex;
align-items: center;
}
::v-deep .el-checkbox__label {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.box-bottom {
display: flex;
justify-content: flex-end;
align-items: center;
height: 40px;
padding-right: 15px;
}
.buttons-box {
width: 18%;
min-width: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
button {
margin: 0 0 15px 0;
}
}
}
</style>
使用实例:
当前设置穿梭框最小宽度为680px,最大宽度由box-width定义的宽度决定
<template>
<div>
<my-transfer
class="box-width"
:titles="['待选列表', '已选列表']"
:filterable="true"
filterPlaceholder="请输入"
:leftTransferData="deviceData"
@transferChange="transferChange"
></my-transfer>
</div>
</template>
<script>
//引入组件
import MyTransfer from '@/components/Transfer/index.vue'
export default {
components: {
MyTransfer,
},
data() {
return {
deviceData: [
{label:'待选内容1',value:0},
{label:'待选内容2',value:1},
{label:'待选内容3',value:2},
{label:'待选内容4',value:3},
{label:'待选内容5',value:4},
{label:'待选内容6',value:5},
{label:'待选内容7',value:6},
{label:'待选内容8',value:7},
{label:'待选内容9',value:8},
{label:'待选内容10',value:9},
{label:'待选内容11',value:10},
{label:'待选内容12',value:11},
{label:'待选内容13',value:12},
{label:'待选内容14',value:13},
{label:'待选内容15',value:14},
{label:'待选内容16',value:15},
{label:'待选内容17',value:16},
{label:'待选内容18',value:17},
{label:'待选内容19',value:18},
],
chooseDeviceList: [],
}
},
methods: {
// 获取最新的右侧数据
transferChange(data) {
this.chooseDeviceList = data
},
}
}
</script>
<style scoped>
/* 盒子宽度 */
.box-width {
width: 1000px;
}
</style>