欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue 實(shí)現(xiàn)高級(jí)穿梭框 Transfer 封裝過(guò)程

 更新時(shí)間:2024年09月20日 10:40:22   作者:小晗同學(xué)  
本文介紹了基于Vue2和Element-UI實(shí)現(xiàn)的高級(jí)穿梭框組件Transfer的設(shè)計(jì)與技術(shù)方案,組件支持多項(xiàng)選擇,并能實(shí)時(shí)同步已選擇項(xiàng),包含豎版和橫版設(shè)計(jì)稿,并提供了組件的使用方法和源碼,此組件具備本地分頁(yè)和搜索功能,適用于需要在兩個(gè)列表間進(jìn)行數(shù)據(jù)選擇和同步的場(chǎng)景

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)效果圖:

實(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)的示例代碼支持模糊查詢

    這篇文章主要介紹了vue 監(jiān)聽(tīng)input輸入事件(oninput)支持模糊查詢,比如說(shuō)表格模糊查詢,實(shí)現(xiàn)一邊輸入,一邊過(guò)濾數(shù)據(jù),本文通過(guò)示例代碼給大家詳細(xì)講解,需要的朋友可以參考下
    2023-02-02
  • 關(guān)于Vue v-on指令的使用

    關(guān)于Vue v-on指令的使用

    這篇文章主要介紹了關(guān)于Vue v-on指令的一些使用場(chǎng)景,比如監(jiān)聽(tīng)事件、傳入event參數(shù)、事件修飾符的一些場(chǎng)景,下面就來(lái)看看具體使用的方法吧,需要的朋友可以參考一下
    2021-10-10
  • vue?el-pagination分頁(yè)查詢封裝的示例代碼

    vue?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ù)

    這篇文章主要介紹了從0搭建Vue3組件庫(kù)之如何使用Vite打包組件庫(kù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • 淺談VUE uni-app 生命周期

    淺談VUE uni-app 生命周期

    這篇文章主要介紹了uni-app 的生命周期,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • vue請(qǐng)求數(shù)據(jù)的三種方式

    vue請(qǐng)求數(shù)據(jù)的三種方式

    這篇文章主要介紹了vue請(qǐng)求數(shù)據(jù)的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù)

    van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù)

    前端開(kāi)發(fā)想省時(shí)間就是要找框架呀,下面這篇文章主要給大家介紹了關(guān)于van-uploader保存文件到后端回顯后端接口返回的數(shù)據(jù),文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • Vue render函數(shù)使用詳細(xì)講解

    Vue render函數(shù)使用詳細(xì)講解

    vue中的render函數(shù),它返回的是一個(gè)虛擬節(jié)點(diǎn)vnode,也就是我們要渲染的節(jié)點(diǎn),下面這篇文章主要給大家介紹了關(guān)于Vue中render函數(shù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • vue表格顯示字符串過(guò)長(zhǎng)的問(wèn)題及解決

    vue表格顯示字符串過(guò)長(zhǎng)的問(wèn)題及解決

    這篇文章主要介紹了vue表格顯示字符串過(guò)長(zhǎng)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • uni-app在線預(yù)覽pdf文件的方法教程

    uni-app在線預(yù)覽pdf文件的方法教程

    這篇文章主要介紹了uni-app在線預(yù)覽pdf文件的相關(guān)資料,本文主要介紹了如何在Vue項(xiàng)目中使用PDF.js插件進(jìn)行PDF文件的預(yù)覽,包括插件的下載、版本兼容問(wèn)題的處理,以及在static文件夾下新建pdf文件夾存放解壓文件,需要的朋友可以參考下
    2024-10-10

最新評(píng)論