Vue实现的表格多选方案支持跨页选择和回显功能

发布于:2025-03-31 ⋅ 阅读:(19) ⋅ 点赞:(0)

以下是纯Vue实现的表格多选方案(不依赖UI库),支持跨页选择和回显功能:

<template>
  <div class="custom-table">
    <!-- 表格主体 -->
    <table>
      <thead>
        <tr>
          <th>
            <input 
              type="checkbox" 
              :checked="isAllChecked"
              @change="toggleAllSelection"
            >
          </th>
          <th>ID</th>
          <th>Name</th>
          <th>Age</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in currentPageData" :key="item.id">
          <td>
            <input
              type="checkbox"
              :checked="isSelected(item.id)"
              @change="toggleSelection(item)"
            >
          </td>
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.age }}</td>
        </tr>
      </tbody>
    </table>

    <!-- 分页控件 -->
    <div class="pagination">
      <button 
        v-for="page in totalPages" 
        :key="page"
        :class="{ active: currentPage === page }"
        @click="currentPage = page"
      >
        {{ page }}
      </button>
    </div>

    <!-- 已选展示 -->
    <div class="selected-info">
      已选ID: {{ selectedIds.join(', ') }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 原始数据
      allData: [],
      
      // 分页相关
      currentPage: 1,
      pageSize: 5,
      
      // 选中数据
      selectedItems: new Map(), // 使用Map存储保证顺序
    }
  },
  
  computed: {
    // 当前页数据
    currentPageData() {
      const start = (this.currentPage - 1) * this.pageSize
      const end = start + this.pageSize
      return this.allData.slice(start, end)
    },
    
    // 总页数
    totalPages() {
      return Math.ceil(this.allData.length / this.pageSize)
    },
    
    // 当前页是否全选
    isAllChecked() {
      return this.currentPageData.every(item => 
        this.selectedItems.has(item.id)
      )
    },
    
    // 已选ID数组
    selectedIds() {
      return Array.from(this.selectedItems.keys())
    }
  },
  
  created() {
    // 生成模拟数据
    this.generateMockData(23)
    
    // 模拟回显数据(实际从API获取)
    this.$nextTick(() => {
      this.setInitialSelection([2, 5, 9]) // 回显ID为2、5、9的数据
    })
  },
  
  methods: {
    // 生成模拟数据
    generateMockData(count) {
      for(let i = 1; i <= count; i++) {
        this.allData.push({
          id: i,
          name: `User ${i}`,
          age: 20 + (i % 10)
        })
      }
    },
    
    // 设置初始选中(回显)
    setInitialSelection(ids) {
      ids.forEach(id => {
        const item = this.allData.find(d => d.id === id)
        if(item) this.selectedItems.set(id, item)
      })
    },
    
    // 切换单个选择
    toggleSelection(item) {
      if(this.selectedItems.has(item.id)) {
        this.selectedItems.delete(item.id)
      } else {
        this.selectedItems.set(item.id, item)
      }
    },
    
    // 切换全选
    toggleAllSelection(e) {
      const checked = e.target.checked
      this.currentPageData.forEach(item => {
        if(checked) {
          this.selectedItems.set(item.id, item)
        } else {
          this.selectedItems.delete(item.id)
        }
      })
    },
    
    // 检查是否选中
    isSelected(id) {
      return this.selectedItems.has(id)
    }
  }
}
</script>

<style scoped>
.custom-table {
  max-width: 800px;
  margin: 20px auto;
  font-family: Arial, sans-serif;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 20px;
}

th, td {
  border: 1px solid #ddd;
  padding: 12px;
  text-align: left;
}

th {
  background-color: #f5f5f5;
}

tr:hover {
  background-color: #f9f9f9;
}

.pagination {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
}

.pagination button {
  padding: 6px 12px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}

.pagination button.active {
  background: #409eff;
  color: white;
  border-color: #409eff;
}

.selected-info {
  padding: 12px;
  background: #f5f5f5;
  border-radius: 4px;
}
</style>

核心实现逻辑:

  1. 数据结构设计:

    • allData:存储全部数据(模拟数据)
    • selectedItems:使用Map存储选中项,方便快速查找
    • currentPageData:计算属性获取当前页数据
  2. 跨页选择实现:

    • 使用Map存储选中项,不受分页影响
    • 切换页面时自动保留选中状态
    • 通过isSelected方法检查当前行是否选中
  3. 回显功能:

    • 提供setInitialSelection方法初始化选中项
    • 支持传入ID数组自动匹配数据项
  4. 全选功能:

    • 当前页全选/取消全选
    • 自动同步到全局选中状态
    • 智能显示全选复选框状态
  5. 性能优化:

    • 使用Map数据结构提升查找效率
    • 计算属性缓存计算结果
    • 虚拟滚动友好(可扩展)

功能扩展建议:

  1. 分页优化:

    // 在methods中添加
    goToPage(page) {
      if(page < 1 || page > this.totalPages) return
      this.currentPage = page
    }
    
    // 在模板中添加
    <button @click="goToPage(currentPage - 1)">上一页</button>
    <button @click="goToPage(currentPage + 1)">下一页</button>
    
  2. 多选操作增强:

    // 清空选择
    clearSelection() {
      this.selectedItems.clear()
    }
    
    // 批量删除
    deleteSelected() {
      this.allData = this.allData.filter(
        item => !this.selectedItems.has(item.id)
      this.clearSelection()
    }
    
  3. 数据持久化:

    // 本地存储示例
    watch: {
      selectedItems: {
        handler(newVal) {
          localStorage.setItem('selectedItems', JSON.stringify([...newVal]))
        },
        deep: true
      }
    }
    
  4. 性能增强(大数据量):

    // 使用虚拟滚动
    // 安装vue-virtual-scroller
    import { RecycleScroller } from 'vue-virtual-scroller'
    
    // 替换tbody部分
    <RecycleScroller
      class="scroller"
      :items="currentPageData"
      :item-size="48"
      key-field="id"
    >
      <template #default="{ item }">
        <tr>
          <!-- 原有td内容 -->
        </tr>
      </template>
    </RecycleScroller>
    

使用说明:

  1. 通过setInitialSelection方法初始化回显数据
  2. 获取选中数据:Array.from(this.selectedItems.values())
  3. 修改pageSize可调整每页显示数量
  4. 样式可根据需求自定义修改

这个实现方案具有以下特点:

  • 零依赖,纯Vue实现
  • 完整的分页选择逻辑
  • 高性能的选中状态管理
  • 灵活的回显机制
  • 易于扩展的自定义样式
  • 支持大数据量的虚拟滚动(需额外配置)