Vue3+Vite+TypeScript项目中跨页多选表格的实现与应用

在企业级管理系统、数据中台等复杂前端项目中,表格作为数据展示与交互的核心组件,常面临数据量庞大需分页展示的场景。例如在管理系统中,需要在多页数据中批量勾选目标条目(如批量导出、批量删除)。

基于此,实现一个支持跨页选择的复选框表格成为提升用户体验的关键需求。该功能需要解决数据分页加载时选中状态的持久化维护、不同页面间勾选逻辑的一致性,以及结合TypeScript类型安全特性确保数据操作的可靠性,同时兼顾组件性能与可维护性,为复杂数据交互场景提供高效的解决方案。

交互演示与核心功能说明:

  1. 空状态展示:页面初始加载时呈现所有未被选中的数据
  2. 跨页选中功能演示:
    在不同分页间勾选数据行,表格自动维护选中状态
    切换页码时保留已选数据,支持连续跨页批量操作
  3. 数据获取与处理
    点击"获取选中数据"按钮,实时提取选中项ID集合
    支持两种数据获取模式:仅ID数组或完整数据对象
    在控制台输出选中结果(实际项目中可用于批量提交、导出等操作)
  4. 编辑场景模拟
    点击"渲染数据"按钮,模拟编辑页回显已选数据
    自动选中已被选择过的数据

第一种:采用原生html实现

代码如下:

javascript 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'

const tableData = ref([
  {
    id: 1,
    name: 'aaa',
    age: 18
  },
  {
    id: 2,
    name: 'bbb',
    age: 17
  },
  {
    id: 3,
    name: 'ccc',
    age: 16
  },
  {
    id: 4,
    name: 'ddd',
    age: 15
  },
  {
    id: 5,
    name: 'eeee',
    age: 14
  },
  {
    id: 6,
    name: 'fff',
    age: 19
  },
  {
    id: 7,
    name: 'ggg',
    age: 20
  },
  {
    id: 8,
    name: 'hhh',
    age: 21
  },
  {
    id: 9,
    name: 'iii',
    age: 22
  },
  {
    id: 10,
    name: 'jjj',
    age: 23
  },
])

const pageSize = 5
const currentPage = ref(1)

const totalPages = computed(() => Math.ceil(tableData.value.length / pageSize))

const paginatedData = computed(() => {
  const start = (currentPage.value - 1) * pageSize
  const end = start + pageSize
  return tableData.value.slice(start, end)
})

const nextPage = () => {
  if (currentPage.value < totalPages.value) {
    currentPage.value++
  }
}

const prevPage = () => {
  if (currentPage.value > 1) {
    currentPage.value--
  }
}

const selectedItems = ref<number[]>([]) // Initialize with IDs 2 and 8

const toggleSelection = (id: number) => {
  const index = selectedItems.value.indexOf(id)
  if (index === -1) {
    selectedItems.value.push(id)
  } else {
    selectedItems.value.splice(index, 1)
  }
}

const getSelectedData = () => {
  // 获取单条数据的id
  console.log('selectData', selectedItems.value)
  // 获取单条数据
  const selectedData = tableData.value.filter(item => selectedItems.value.includes(item.id))
  console.log(JSON.stringify(selectedData, null, 2))
}

const setSelectedData = () =>{
  selectedItems.value = [2,8]
}

  
</script>

<template>
  <div>
    <table class="border">
      <tr>
        <th></th>
        <th>名字</th>
        <th>年龄</th>
      </tr>
      <tr v-for="item in paginatedData" :key="item.id">
        <td>
          <input 
            type="checkbox" 
            :checked="selectedItems.includes(item.id)"
            @change="toggleSelection(item.id)"
          />
        </td>
        <td>{{item.name}}</td>
        <td>{{item.age}}</td>
      </tr>
    </table>
    
    <div class="pagination">
      <button @click="prevPage" :disabled="currentPage === 1">上一页</button>
      <span>{{ currentPage }} / {{ totalPages }}</span>
      <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
    </div>

    <div class="selected-info">
      已选择: {{ selectedItems.length }} 项
      <button class="get-data-btn" @click="getSelectedData">获取选中的数据</button>
      <button class="get-data-btn" @click="setSelectedData">渲染数据</button>
      
    </div>
  </div>
</template>

<style scoped>
.border {
  width: 400px;
  border: 1px solid lightyellow;
  margin-bottom: 1rem;
}

.pagination {
  display: flex;
  gap: 1rem;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}

.selected-info {
  text-align: center;
  color: #646cff;
}

button {
  padding: 0.5rem 1rem;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.get-data-btn {
  margin-left: 1rem;
  background-color: #42b883;
  color: white;
}

.get-data-btn:hover {
  border-color: #42b883;
}
</style>

实现效果如下:

第二种:采用element-plus实现

代码如下:

javascript 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ElTable, ElTableColumn, ElPagination, ElButton } from 'element-plus'

const tableData = ref([
  { id: 1, name: 'aaa', age: 18 },
  { id: 2, name: 'bbb', age: 17 },
  { id: 3, name: 'ccc', age: 16 },
  { id: 4, name: 'ddd', age: 15 },
  { id: 5, name: 'eeee', age: 14 },
  { id: 6, name: 'fff', age: 19 },
  { id: 7, name: 'ggg', age: 20 },
  { id: 8, name: 'hhh', age: 21 },
  { id: 9, name: 'iii', age: 22 },
  { id: 10, name: 'jjj', age: 23 },
])

const currentPage = ref(1)
const pageSize = ref(5)
const multipleSelection = ref<number[]>([])

const paginatedData = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  const end = start + pageSize.value
  return tableData.value.slice(start, end)
})


const getSelectedData = () => {
  console.log('Selected data:', multipleSelection.value)
}

const setSelectedData = () => {
   multipleSelection.value = [2, 8]
}


const handleCheck = (e,id) => {
  if(e) {
    multipleSelection.value.push(id)
  } else {
    const index = multipleSelection.value.findIndex(item => item.id === id)
    multipleSelection.value.splice(index, 1)
  }
}
</script>

<template>
  <div class="table-container">
    <el-table
      :data="paginatedData"
      style="width: 100%"
    >
      <el-table-column header-align="center" align="center" width="40">
        <template #default="scope">
          <el-checkbox :model-value="multipleSelection.includes(scope.row.id)" @change="handleCheck($event, scope.row.id)" />
        </template>
       </el-table-column>
      <el-table-column
        prop="name"
        label="名字"
      />
      <el-table-column
        prop="age"
        label="年龄"
      />
    </el-table>

    <div class="footer">
      <el-pagination
        v-model:current-page="currentPage"
        :page-size="pageSize"
        :total="tableData.length"
        layout="prev, pager, next"
      />

      <div class="buttons">
        <span class="selected-info">已选择: {{ multipleSelection.length }} 项</span>
        <el-button type="primary" @click="getSelectedData">获取选中的数据</el-button>
        <el-button type="primary" @click="setSelectedData">渲染数据</el-button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.table-container {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.footer {
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.buttons {
  display: flex;
  align-items: center;
  gap: 16px;
}

.selected-info {
  margin-right: 16px;
}
</style>

实现效果如下: