Vue 實(shí)現(xiàn)高級(jí)穿梭框 Transfer 封裝過程
01 基礎(chǔ)信息
1.1. 技術(shù)棧
Element-UI、Vue2、lodash
1.2. 組件設(shè)計(jì)
需求描述:
【待選擇列表】 接收業(yè)務(wù)的表格數(shù)據(jù),支持選擇多項(xiàng)并將其添加到【已添加列表】 (勾選或刪除操作,兩邊的列表是同步的);
【已添加列表】支持本地分頁(yè)和本地簡(jiǎn)易搜索功能(已添加的列表數(shù)據(jù)需要實(shí)時(shí)同步給業(yè)務(wù));
a. 豎版設(shè)計(jì)稿

b. 橫版設(shè)計(jì)稿

02 技術(shù)方案
(1)初定義數(shù)據(jù)
// 【待選擇列表】外部傳輸源數(shù)據(jù)
// 【已添加列表】組件內(nèi)部控制數(shù)據(jù)的分頁(yè)、搜索和展示
const props = {
sourceList: [], // 源數(shù)據(jù)
columnList: [], // 表格列配置(注:字段類型均為字符串)
searchList: [], // 【已添加列表】搜索項(xiàng)(注:與表頭對(duì)應(yīng))
refreshTableData: (param)=>{}, // 回調(diào)函數(shù)
total: 0, // 用于控制分頁(yè)器
}
const state = {
targetList: [], // 目標(biāo)數(shù)據(jù)
searchList: [], // 【已添加列表】搜索項(xiàng)
}(2)注意事項(xiàng)
- 【待選擇列表】翻頁(yè)選擇時(shí)需要記錄并回顯已選擇的行
- 【已添加列表】刪除后需要繼續(xù)留在當(dāng)前頁(yè),即要判斷刪除的是否是最后一頁(yè)中只有一條的數(shù)據(jù)
- 【待選擇列表】更改選擇后,【已添加列表】的篩選項(xiàng)或是狀態(tài)項(xiàng)是否重置?或是維持不變?
(3)邏輯草圖

03 代碼示例
3.1. 組件使用
外部可通過 ref 調(diào)用的方法:
- clearSelection():清空所有選擇項(xiàng);
- setPaginationParam({pageNum,pageSize},isFetch):設(shè)置源表格分頁(yè)器參數(shù),若 isFetch 為 true 則會(huì)自動(dòng)調(diào)用 fetchSourceList( isFetch 默認(rèn)為 false );
- initializeComponent(isFetch):初始化組件,若 isFetch 為 true 則初始化后自動(dòng)請(qǐng)求源表格數(shù)據(jù)( isFetch 默認(rèn)為 false );
- this.$refs['transferPlus'].selectList:若要初始化 selectList 可以使用 ref 設(shè)置(記得外面包裹 this.$nextTick);
注意事項(xiàng):
- 使用插槽自定義表格列時(shí),是同時(shí)應(yīng)用到兩個(gè)列表中的;
- 組件會(huì)通過 selectionChange 事件告知您選擇的列表結(jié)果;
- 特別地,組件一開始不會(huì)默認(rèn)請(qǐng)求源表格數(shù)據(jù),所以您需要在使用前自行調(diào)用 fetchSourceList 獲取 sourceList 等來(lái)渲染組件的數(shù)據(jù),組件只會(huì)在內(nèi)部的分頁(yè)狀態(tài)等有更改的情況下自動(dòng)調(diào)用 fetchSourceList 為您刷新渲染數(shù)據(jù);
- 若 usePagination 為 true,則組件自動(dòng)為您控制分頁(yè)器,但您必須設(shè)置好變量(sourceTotal)和源表格數(shù)據(jù)請(qǐng)求方法(fetchSourceList),并且為了防止您初始請(qǐng)求的分頁(yè)參數(shù)和組件內(nèi)部定義的默認(rèn)初始分頁(yè)參數(shù)不同,您可以設(shè)置 initSourcePageNum 和 initSourcePageSize 來(lái)同步內(nèi)外初始化參數(shù);
<template>
<TransferPlus
ref="transferPlusRef"
:sourceList="sourceList"
:tableColumnList="tableColumnList"
usePagination
tableHeight="240"
:sourceTotal="sourceTotal"
:tableLoading="tableLoading"
@fetchSourceList="fetchSourceList"
>
<!-- "table_"后拼接的是你定義該列的prop -->
<template #table_tag="{ row, rowIndex }">{{ rowIndex + 1 }}{{row.tag}}</template>
<!-- 自定義源表格的搜索區(qū)域 -->
<template #source_search>
<el-input placeholder="請(qǐng)輸入課程名稱" v-model="queryInfo.title" class="search-input" clearable>
<el-button slot="append" icon="el-icon-search" @click="searchSourceList"></el-button>
</el-input>
</template>
</TransferPlus>
</template>
<script>
import TransferPlus from '@/components/TransferPlus'
export default {
components: { TransferPlus },
data() {
sourceList: [],
tableColumnList: [
{ label: '課程id', prop: 'id' },
{ label: '課程名稱', prop: 'title' },
{ label: '課程類型', prop: 'tag' },
],
tableLoading: false,
sourceTotal: 0,
queryInfo: {
pageNum: 1,
pageSize: 10,
title: '',
tag: '',
},
}
method:{
async fetchSourceList (params={pageNum,pageSize}) {
this.tableLoading = true
const { pageNum, pageSize } = this.queryInfo
this.queryInfo = {
...this.queryInfo,
pageNum: params?.pageNum || pageNum,
pageSize: params?.pageSize || pageSize,
}
const res = await getList(this.queryInfo)
this.sourceList = res.data.list || []
this.sourceTotal = res.data.total || 0
this.tableLoading = false
},
searchSourceList() {
// 每次查詢時(shí)只需要重置穿梭框的頁(yè)碼到 1,并配置自動(dòng)調(diào)用搜索函數(shù)
this.$refs['transferPlusRef'].setPaginationParam({ pageNum: 1 }, true)
},
}
}
</script>
<style scoped>
.search-input {
margin-bottom: 12px;
width: 100%;
height: 32px;
}
</style>實(shí)現(xiàn)效果圖:

3.2. 組件源碼
./TransferPlus/index.vue
<!--
組件使用方式如下:
<TransferPlus :sourceList="sourceList" :tableColumnList="tableColumnList" usePagination tableHeight="240" :sourceTotal="sourceTotal" :tableLoading="tableLoading" @fetchSourceList="fetchSourceList" >
<template #table_你定義該列的prop="{ columnProps }">{{ columnProps.$index + 1 }}{{columnProps.row.xxx}}</template>
</TransferPlus>
method:{
async fetchSourceList (params={pageNum,pageSize}) {
this.tableLoading = true
const res = await getList({ ...this.queryInfo, ...params })
this.sourceList = res.data.list
this.sourceTotal = res.data.total
this.tableLoading = false
}
}
外部可通過 ref 調(diào)用的方法:
1. clearSelection():清空所有選擇項(xiàng);
2. setPaginationParam({pageNum,pageSize},isFetch):設(shè)置源表格分頁(yè)器參數(shù),若 isFetch 為 true 則會(huì)自動(dòng)調(diào)用 fetchSourceList( isFetch 默認(rèn)為 false );
3. initializeComponent(isFetch):初始化組件,若 isFetch 為 true 則初始化后自動(dòng)請(qǐng)求源表格數(shù)據(jù)( isFetch 默認(rèn)為 false );
4. this.$refs['transferPlusRef'].selectList:若要初始化 selectList 可以使用 ref 設(shè)置(記得外面包裹 this.$nextTick);
注意事項(xiàng):
1. 使用插槽自定義表格列時(shí),是同時(shí)應(yīng)用到兩個(gè)列表中的;
2. 組件會(huì)通過 selectionChange 事件告知您選擇的列表結(jié)果;
3. 特別地,組件一開始不會(huì)默認(rèn)請(qǐng)求源表格數(shù)據(jù),所以您需要在使用前自行調(diào)用 fetchSourceList 獲取 sourceList 等來(lái)渲染組件的數(shù)據(jù),組件只會(huì)在內(nèi)部的分頁(yè)狀態(tài)等有更改的情況下自動(dòng)調(diào)用 fetchSourceList 為您刷新渲染數(shù)據(jù);
4. 若 usePagination 為 true,則組件自動(dòng)為您控制分頁(yè)器,但您必須設(shè)置好變量(sourceTotal)和源表格數(shù)據(jù)請(qǐng)求方法(fetchSourceList),并且為了防止您初始請(qǐng)求的分頁(yè)參數(shù)和組件內(nèi)部定義的默認(rèn)初始分頁(yè)參數(shù)不同,您可以設(shè)置 initSourcePageNum 和 initSourcePageSize 來(lái)同步內(nèi)外初始化參數(shù);
-->
<template>
<div :class="direction === 'horizontal' ? 'transfer-horizontal' : ''">
<!-- 【待選擇列表】 -->
<div :class="['list-wrapping', { horizontal: direction === 'horizontal' }]">
<div class="wrapping-header">
<span>待選擇列表</span>
<span>{{ selectLength }}/{{ sourceTotal || sourceList.length }}</span>
</div>
<div class="wrapping-content">
<!-- 自定義搜索 -->
<slot name="source_search" />
<TransferTable
ref="sourceTransferTableRef"
v-model="selectList"
:tableList="sourceList"
:tableColumnList="tableColumnList"
:tableHeight="tableHeight"
:total="sourceTotal"
:initPageNum="initSourcePageNum"
:initPageSize="initSourcePageSize"
:usePagination="usePagination"
:tableLoading="tableLoading"
:uniqueKey="uniqueKey"
:selectable="selectable"
:pagerCount="pagerCount"
@fetchTableList="handleFetchTableList"
>
<!-- 使用穿梭表格的自定義列插槽 -->
<template v-for="(item, index) in tableColumnList" :slot="`inner_table_${item.prop}`" slot-scope="slotData">
<span :key="index">
<!-- 設(shè)置新的插槽提供給消費(fèi)端自定義列 -->
<slot :name="`table_${item.prop}`" :columnProps="slotData.columnProps" :row="slotData.columnProps.row" :rowIndex="slotData.columnProps.$index">
{{ slotData.columnProps.row[item.prop] || '-' }}
</slot>
</span>
</template>
</TransferTable>
</div>
</div>
<!-- 【已添加列表】 -->
<div :class="['list-wrapping', { horizontal: direction === 'horizontal' }]">
<div class="wrapping-header">
<span>已添加列表</span>
<span>{{ selectLength }}</span>
</div>
<div class="wrapping-content">
<template v-if="selectLength">
<el-input placeholder="請(qǐng)輸入內(nèi)容" v-model="searchStr" class="search-input" clearable>
<el-select slot="prepend" v-model="searchKey" placeholder="請(qǐng)選擇" class="search-select" @change="handleSearchKeyChange" value-key="prop">
<el-option v-for="item in targetSearchList" :key="item.prop" :label="item.label" :value="item.prop"></el-option>
</el-select>
<el-button slot="append" icon="el-icon-search" @click="handleSearchStrChange"></el-button>
</el-input>
<TransferTable
ref="targetTransferTableRef"
:tableList="targetList"
:tableColumnList="tableColumnList"
:tableHeight="tableHeight"
tableType="target"
:uniqueKey="uniqueKey"
:total="targetTotal"
:usePagination="usePagination"
:pagerCount="pagerCount"
@removeSelectRow="handleRemoveSelectRow"
@fetchTableList="getTargetTableList"
>
<!-- 使用穿梭表格的自定義列插槽 -->
<template v-for="(item, index) in tableColumnList" :slot="`inner_table_${item.prop}`" slot-scope="slotData">
<span :key="index">
<!-- 設(shè)置新的插槽提供給消費(fèi)端自定義列 -->
<slot :name="`table_${item.prop}`" :columnProps="slotData.columnProps" :row="slotData.columnProps.row" :rowIndex="slotData.columnProps.$index">
{{ slotData.columnProps.row[item.prop] || '-' }}
</slot>
</span>
</template>
</TransferTable>
</template>
<div class="empty-box" v-else>
<el-image class="empty-image" :src="require('@/assets/empty_images/data_empty.png')" />
</div>
</div>
</div>
</div>
</template>
<script>
import TransferTable from './TransferTable.vue'
import { throttle, differenceBy, filter, isNil, noop } from 'lodash'
export default {
components: { TransferTable },
props: {
// 源數(shù)據(jù)
sourceList: {
type: Array,
default: () => [],
},
// 表格列配置列表
tableColumnList: {
type: Array,
default: () => [], // {label,prop,align}[]
},
// 表格數(shù)據(jù)是否加載中
tableLoading: {
type: Boolean,
default: false,
},
// 表格高度
tableHeight: {
type: String | Number,
default: 240,
},
// 【已添加列表】搜索項(xiàng)(注:與表格列配置對(duì)應(yīng),且僅能搜索字段類型為 String)
searchList: {
type: Array,
default: () => [], // {label,prop}[]
},
// 源表格總數(shù)據(jù)的條數(shù)
sourceTotal: {
type: Number,
default: 0,
},
// 源表格初始 pageNum(用于同步消費(fèi)端初始化請(qǐng)求時(shí)的分頁(yè)參數(shù),進(jìn)而幫助控制分頁(yè)器)
initSourcePageNum: {
type: Number,
default: 1,
},
// 源表格初始 pageSize(用于同步消費(fèi)端初始化請(qǐng)求時(shí)的分頁(yè)參數(shù),進(jìn)而幫助控制分頁(yè)器)
initSourcePageSize: {
type: Number,
default: 10,
},
// 使用分頁(yè)器
usePagination: {
type: Boolean,
default: false,
},
// 唯一標(biāo)識(shí)符(便于定位到某條數(shù)據(jù)進(jìn)行添加和移除操作)
uniqueKey: {
type: String,
default: 'id',
},
// 穿梭框展示方式
direction: {
type: String,
default: 'vertical', // horizontal 左右布局, vertical 上下布局
},
selectable: {
type: Function,
default: noop(),
},
// 頁(yè)碼按鈕的數(shù)量,當(dāng)總頁(yè)數(shù)超過該值時(shí)會(huì)折疊(element規(guī)定:大于等于 5 且小于等于 21 的奇數(shù))
pagerCount: {
type: Number,
default: 7,
},
},
data() {
return {
selectList: [], // 已選擇的列表
targetList: [], // 已添加列表的回顯數(shù)據(jù)
searchKey: '',
searchStr: '',
targetPageNum: 1,
targetPageSize: 10,
targetTotal: 10,
}
},
computed: {
targetSearchList() {
return this.searchList.length ? this.searchList : this.tableColumnList
},
selectLength() {
return this.selectList?.length || 0
},
},
watch: {
selectList(newVal) {
this.getTargetTableList()
this.$emit('selectionChange', newVal)
},
},
mounted() {
this.searchKey = this.targetSearchList[0].prop
this.targetPageNum = 1
this.targetPageSize = 10
},
methods: {
handleFetchTableList(params) {
this.$emit('fetchSourceList', params)
},
handleRemoveSelectRow(rowItem) {
this.selectList = differenceBy(this.selectList, [rowItem], this.uniqueKey)
},
handleSearchStrChange() {
// 每次查詢時(shí)只需要重置穿梭框的頁(yè)碼到 1,并配置自動(dòng)調(diào)用搜索函數(shù)
this.$refs['targetTransferTableRef'].setPaginationParam({ pageNum: 1 }, true)
},
handleSearchKeyChange() {
// 更新搜索 Key 之后,需要清空搜索字符串
this.searchStr = ''
this.$refs['targetTransferTableRef'].setPaginationParam({ pageNum: 1 }, true)
},
getTargetTableList(params = null) {
const targetTableList = filter(this.selectList, (item) => {
if (this.searchStr) {
const itemValueToString = isNil(item[this.searchKey]) ? '' : JSON.stringify(item[this.searchKey])
return itemValueToString.includes(this.searchStr)
} else {
return true
}
})
this.targetTotal = targetTableList.length
if (params) {
this.targetPageNum = params.pageNum
this.targetPageSize = params.pageSize
}
// 前端分頁(yè)
const startIndex = (this.targetPageNum - 1) * this.targetPageSize
const endIndex = this.targetPageNum * this.targetPageSize
this.targetList = targetTableList.slice(startIndex, endIndex)
},
clearSelection() {
// 清空所有選擇項(xiàng)(用于消費(fèi)端設(shè)置的 ref 調(diào)用)
this.selectList = []
this.targetPageNum = 1
this.targetPageSize = 10
this.searchKey = this.targetSearchList[0].prop
},
setPaginationParam({ pageNum, pageSize }, isFetch) {
// 設(shè)置源表格分頁(yè)器參數(shù)(用于消費(fèi)端設(shè)置的 ref 調(diào)用)
// 若 isFetch 為 true,則自動(dòng)調(diào)用消費(fèi)端傳進(jìn)來(lái)的回調(diào)搜索方法
this.$refs['sourceTransferTableRef'].setPaginationParam({ pageNum, pageSize }, isFetch)
},
initializeComponent(isFetch) {
// 初始化組件(用于消費(fèi)端設(shè)置的 ref 調(diào)用)
// 若 isFetch 為 true,則自動(dòng)調(diào)用消費(fèi)端傳進(jìn)來(lái)的回調(diào)搜索方法
this.clearSelection()
this.setPaginationParam({ pageNum: this.initSourcePageNum || 1, pageSize: this.initSourcePageSize || 10 }, isFetch)
},
},
}
</script>
<style lang="scss" scoped>
.transfer-horizontal {
display: flex;
}
.list-wrapping {
margin-bottom: 12px;
border-radius: 2px;
border: 1px solid #d9d9d9;
background: #fff;
overflow: hidden;
}
.horizontal {
flex: 1;
margin-right: 20px;
margin-bottom: 0px;
&:last-child {
margin: 0px;
}
}
.wrapping-header {
width: 100%;
padding: 10px 20px;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #d9d9d9;
background: #f5f5f5;
color: #333;
font-size: 14px;
line-height: 20px;
}
.wrapping-content {
padding: 12px;
width: 100%;
}
.search-input {
margin-bottom: 12px;
max-width: 500px;
height: 32px;
}
.search-select {
width: 120px;
}
.empty-box {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
margin: 40px 0px;
}
.empty-image {
width: 150px;
height: 150px;
}
:deep(.search-input .el-input-group__prepend) {
background-color: #fff;
}
:deep(.el-select .el-input .el-select__caret) {
color: #3564ff;
}
</style>./TransferPlus/TransferTable.vue
<template>
<div>
<!-- 表格區(qū)域 -->
<el-table
ref="transferTable"
v-loading="tableLoading"
:border="true"
:data="tableList"
size="mini"
:stripe="true"
:height="tableHeight || 'auto'"
:row-class-name="getTableRowClassName"
:header-cell-style="{
background: '#F1F1F1',
}"
@select="handleSelect"
@select-all="handleSelectAll"
>
<el-table-column type="index" align="center"></el-table-column>
<el-table-column type="selection" width="50" v-if="tableType === 'source'" :selectable="selectable"></el-table-column>
<el-table-column v-for="(item, index) in tableColumnList" :key="item.prop || index" :label="item.label" :prop="item.prop" :align="item.align || 'left'" :width="item.width || 'auto'" show-overflow-tooltip>
<template #default="columnProps">
<slot :name="`inner_table_${item.prop}`" :columnProps="columnProps">
<span>{{ columnProps.row[item.prop] }}</span>
</slot>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="70" align="center" v-if="tableType === 'target'">
<template slot-scope="scope">
<el-button @click="handleRemoveRowItem(scope.row, scope.$index)" type="text" icon="el-icon-delete" size="medium"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分頁(yè)器區(qū)域 -->
<div v-if="usePagination" class="pagination-box">
<!-- 實(shí)現(xiàn)兩側(cè)分布的分頁(yè)器布局:使用兩個(gè)分頁(yè)器組件 + 不同 layout 組成 -->
<el-pagination background :current-page="pageNum" :layout="layoutLeft" :page-size="pageSize" :pager-count="pagerCount" :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" />
<el-pagination background :current-page="pageNum" :layout="layoutRight" :page-size="pageSize" :pager-count="pagerCount" :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" />
</div>
</div>
</template>
<script>
import { differenceBy, uniqBy, noop } from 'lodash'
export default {
props: {
// 已勾選的數(shù)組
value: {
type: Array,
default: () => [],
require: true,
},
// 表格數(shù)據(jù)
tableList: {
type: Array,
default: () => [],
},
// 表格列配置列表
tableColumnList: {
type: Array,
default: () => [], // {label,prop,align}[]
},
// 表格數(shù)據(jù)是否加載中
tableLoading: {
type: Boolean,
default: false,
},
// 表格高度
tableHeight: {
type: String | Number,
default: 240,
},
// 表格數(shù)據(jù)類型
tableType: {
type: String,
default: 'source', // source 源列表,target 目標(biāo)列表
},
// 【已添加列表】搜索項(xiàng)(注:與表格列配置對(duì)應(yīng),且僅能字段類型為 String)
searchList: {
type: Array,
default: () => [], // {label,prop,align}[]
},
// 分頁(yè)后表格總數(shù)據(jù)的條數(shù)
total: {
type: Number,
default: 0,
},
// 初始 pageNum
initPageNum: {
type: Number,
default: 1,
},
// 初始 pageSize
initPageSize: {
type: Number,
default: 10,
},
// 使用分頁(yè)器
usePagination: {
type: Boolean,
default: false,
},
// 唯一標(biāo)識(shí)符(便于定位到某條數(shù)據(jù)進(jìn)行添加和移除操作)
uniqueKey: {
type: String,
default: 'id',
},
// Function 的返回值用來(lái)決定這一行的 CheckBox 是否可以勾選
selectable: {
type: Function,
default: noop(),
},
// 頁(yè)碼按鈕的數(shù)量,當(dāng)總頁(yè)數(shù)超過該值時(shí)會(huì)折疊(element規(guī)定:大于等于 5 且小于等于 21 的奇數(shù))
pagerCount: {
type: Number,
default: 7,
},
},
data() {
return {
layoutLeft: 'total',
layoutRight: 'sizes, prev, pager, next',
pageNum: 1,
pageSize: 10,
preSelectList: [], // 上一次選擇的數(shù)據(jù)(點(diǎn)擊分頁(yè)器就清空)
stashSelectList: [], // 暫存數(shù)據(jù),便于點(diǎn)擊頁(yè)碼后,還能保存前一頁(yè)的數(shù)據(jù)
isNeedToggle: true, // 是否需要勾選該頁(yè)已選擇項(xiàng)(用于換頁(yè)后的回顯選擇項(xiàng))
isTableChangeData: false, // 是否是當(dāng)前表格造成選擇項(xiàng)的變化(用于同步【待選擇列表】的勾選項(xiàng))
}
},
computed: {
currentPageSelectList() {
const currentSelectList = []
this.stashSelectList?.forEach((item) => {
const currentRow = this.tableList?.find((row) => row[this.uniqueKey] === item[this.uniqueKey])
if (currentRow) {
currentSelectList.push(currentRow)
}
})
return currentSelectList
},
},
watch: {
value(newVal) {
this.stashSelectList = newVal || []
// 只有在其他地方修改了選擇表格數(shù)據(jù)后,才刷新覆蓋勾選項(xiàng)(當(dāng)前表格修改選擇項(xiàng)是雙向綁定的,所以不需要刷新覆蓋勾選項(xiàng)),實(shí)現(xiàn)精準(zhǔn)回顯和兩表格的聯(lián)動(dòng)
if (!this.isTableChangeData) {
this.handleToggleSelection()
}
// 當(dāng)暫存的選擇列表為空時(shí),需要同步更新 preSelect 為空數(shù)組,以便下次選擇時(shí)進(jìn)行判斷是增加選擇項(xiàng)還是減少選擇項(xiàng)
if (!this.stashSelectList.length) {
this.preSelectList = []
}
this.isTableChangeData = false
},
tableList() {
if (this.isNeedToggle) {
this.preSelectList = this.currentPageSelectList
this.handleToggleSelection()
this.isNeedToggle = false
}
},
},
mounted() {
this.pageNum = this.initPageNum || 1
this.pageSize = this.initPageSize || 110
// 解決右側(cè)固定操作欄錯(cuò)位問題
this.$nextTick(() => {
this.$refs.transferTable.doLayout()
})
this.$emit('selectionChange', [])
},
methods: {
getTableRowClassName({ rowIndex }) {
if (rowIndex % 2 == 0) {
return ''
} else {
return 'stripe-row'
}
},
fetchTableList(pageNum = 1) {
if (this.usePagination) {
// 若不是頁(yè)碼更改觸發(fā),則默認(rèn)將 pageNum 重置為 1
this.pageNum = pageNum
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
}
this.$emit('fetchTableList', params)
} else {
this.$emit('fetchTableList')
}
},
setPaginationParam({ pageNum, pageSize }, isFetch = false) {
// 設(shè)置分頁(yè)器參數(shù)(用于消費(fèi)端設(shè)置的 ref 調(diào)用)
this.pageNum = pageNum || this.pageNum
this.pageSize = pageSize || this.pageSize
this.isNeedToggle = true
if (isFetch) {
this.fetchTableList()
}
},
handleSizeChange(val) {
this.pageSize = val
this.isNeedToggle = true
this.fetchTableList()
},
handleCurrentChange(val) {
this.isNeedToggle = true
this.fetchTableList(val)
},
handleStashSelectList(isAdd = true, list = []) {
if (isAdd) {
// 暫存數(shù)組中增加,并兜底去重
this.stashSelectList = uniqBy([...this.stashSelectList, ...list], this.uniqueKey)
} else {
// 暫存數(shù)組中移除
this.stashSelectList = differenceBy(this.stashSelectList, list, this.uniqueKey)
}
this.isTableChangeData = true
this.$emit('input', this.stashSelectList)
this.$emit('selectionChange', this.stashSelectList)
},
handleSelect(selectList, row) {
// 判斷是否是增加選擇項(xiàng)
const isAddRow = this.preSelectList.length < selectList.length
this.handleStashSelectList(isAddRow, [row])
// 更新當(dāng)前頁(yè)記錄的上次數(shù)據(jù)
this.preSelectList = [...selectList]
},
handleSelectAll(selectList) {
// 判斷是否是全選(需要考慮兩個(gè)數(shù)組長(zhǎng)度相等的情況)
const isAddAll = this.preSelectList.length <= selectList.length
// 更新當(dāng)前頁(yè)記錄的上次數(shù)據(jù)
this.handleStashSelectList(isAddAll, isAddAll ? selectList : this.preSelectList)
this.preSelectList = [...selectList]
},
handleRemoveRowItem(rowItem, rowIndex) {
const remainderPage = this.total % this.pageSize ? 1 : 0
const pageNumTotal = parseInt(this.total / this.pageSize) + remainderPage
const isLastPageOnlyOne = rowIndex === 0 && this.pageNum === pageNumTotal
// 判斷刪除的是否是最后一頁(yè)中只有一條的數(shù)據(jù)
if (isLastPageOnlyOne && this.pageNum > 1) {
// 若是,則 pageNum 需要往前調(diào)整一頁(yè),因?yàn)閯h除后最后一頁(yè)不存在
this.handleCurrentChange(this.pageNum - 1)
}
this.$emit('removeSelectRow', rowItem)
},
handleToggleSelection() {
this.$nextTick(() => {
// 先清除所有勾選狀態(tài)
this.$refs.transferTable.clearSelection()
if (this.currentPageSelectList.length) {
// 再依次勾選當(dāng)前頁(yè)存在的行
this.currentPageSelectList.forEach((item) => {
this.$refs.transferTable.toggleRowSelection(item, true)
})
}
})
},
},
}
</script>
<style scoped>
/* 表格斑馬自定義顏色 */
:deep(.el-table__row.stripe-row) {
background: #f9f9f9;
}
/* 表格操作欄按鈕取消間距 */
:deep(.el-button) {
padding: 0px;
}
/* 表格操作欄按鈕固定大小 */
:deep(.el-icon-delete::before) {
font-size: 14px !important;
}
.pagination-box {
display: flex;
justify-content: space-between;
}
</style>到此這篇關(guān)于Vue 實(shí)現(xiàn)高級(jí)穿梭框 Transfer 封裝的文章就介紹到這了,更多相關(guān)Vue 穿梭框 Transfer 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 監(jiān)聽input輸入事件(oninput)的示例代碼支持模糊查詢
這篇文章主要介紹了vue 監(jiān)聽input輸入事件(oninput)支持模糊查詢,比如說表格模糊查詢,實(shí)現(xiàn)一邊輸入,一邊過濾數(shù)據(jù),本文通過示例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-02-02
vue?el-pagination分頁(yè)查詢封裝的示例代碼
本文主要介紹了vue?el-pagination分頁(yè)查詢封裝的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
從0搭建Vue3組件庫(kù)之如何使用Vite打包組件庫(kù)
這篇文章主要介紹了從0搭建Vue3組件庫(kù)之如何使用Vite打包組件庫(kù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù)
前端開發(fā)想省時(shí)間就是要找框架呀,下面這篇文章主要給大家介紹了關(guān)于van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06

