Vue 實(shí)現(xiàn)高級(jí)穿梭框 Transfer 封裝過(guò)程
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. 組件使用
外部可通過(guò) 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ì)通過(guò) selectionChange 事件告知您選擇的列表結(jié)果;
- 特別地,組件一開(kāi)始不會(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 } } 外部可通過(guò) 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ì)通過(guò) selectionChange 事件告知您選擇的列表結(jié)果; 3. 特別地,組件一開(kāi)始不會(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ù)超過(guò)該值時(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ù)超過(guò)該值時(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ò)位問(wèn)題 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)聽(tīng)input輸入事件(oninput)的示例代碼支持模糊查詢
這篇文章主要介紹了vue 監(jiān)聽(tīng)input輸入事件(oninput)支持模糊查詢,比如說(shuō)表格模糊查詢,實(shí)現(xiàn)一邊輸入,一邊過(guò)濾數(shù)據(jù),本文通過(guò)示例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-02-02vue?el-pagination分頁(yè)查詢封裝的示例代碼
本文主要介紹了vue?el-pagination分頁(yè)查詢封裝的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(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ù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù)
前端開(kāi)發(fā)想省時(shí)間就是要找框架呀,下面這篇文章主要給大家介紹了關(guān)于van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù),文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06vue表格顯示字符串過(guò)長(zhǎng)的問(wèn)題及解決
這篇文章主要介紹了vue表格顯示字符串過(guò)長(zhǎng)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10