返回

Element Plus 表格分页、序号与请求状态整理

用一个用户列表例子整理 Element Plus 的 el-table 和 el-pagination:跨页连续序号、加载状态、分页请求、切换页大小,以及服务端排序的处理边界。

列表页是后台项目最常见的页面之一。表格、分页、筛选、加载状态一起出现后,问题往往不是“表格能不能显示”,而是换页后序号不连续、重复请求、页码没有重置,或者前端排序和服务端分页混在一起。

跨页连续序号怎么算

el-table-columntype="index" 默认会从 1 开始重新编号。要显示跨页连续序号,可以给它传一个函数:

1
<el-table-column type="index" label="序号" :index="getIndex" width="72" />
1
2
3
const getIndex = (index: number) => {
  return (currentPage.value - 1) * pageSize.value + index + 1
}

例如当前是第 3 页、每页 20 条,当前页第一行的 index 是 0,最终序号就是 41。

一个可复用的分页列表

下面的例子把页码、每页数量、总数和加载状态放在一起管理。筛选条件变化后回到第 1 页,再重新请求数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<script setup lang="ts">
import { onMounted, ref } from 'vue'

interface User {
  id: number
  name: string
  status: 'enabled' | 'disabled'
}

const rows = ref<User[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(20)
const keyword = ref('')
const loading = ref(false)

const getIndex = (index: number) => {
  return (currentPage.value - 1) * pageSize.value + index + 1
}

const loadUsers = async () => {
  loading.value = true

  try {
    const params = new URLSearchParams({
      page: String(currentPage.value),
      pageSize: String(pageSize.value),
      keyword: keyword.value,
    })
    const response = await fetch(`/api/users?${params}`)
    const data = await response.json()

    rows.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}

const search = () => {
  currentPage.value = 1
  loadUsers()
}

const changePageSize = () => {
  currentPage.value = 1
  loadUsers()
}

onMounted(loadUsers)
</script>

<template>
  <el-input
    v-model="keyword"
    clearable
    placeholder="按用户名搜索"
    @keyup.enter="search"
  />

  <el-table v-loading="loading" :data="rows" row-key="id">
    <el-table-column type="index" label="序号" :index="getIndex" width="72" />
    <el-table-column prop="name" label="用户名" min-width="160" />
    <el-table-column prop="status" label="状态" width="120" />
  </el-table>

  <el-pagination
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :page-sizes="[10, 20, 50, 100]"
    :total="total"
    background
    layout="total, sizes, prev, pager, next"
    @current-change="loadUsers"
    @size-change="changePageSize"
  />
</template>

loading 要在 finally 中关闭。无论请求成功还是失败,页面都不会一直停在加载状态。

筛选后为什么要重置页码

用户本来在第 8 页,搜索条件一变,新的结果可能只有 1 页。如果继续带着第 8 页请求,列表就会空掉。因此所有会改变结果集的筛选项,都应该先执行:

1
2
currentPage.value = 1
loadUsers()

服务端分页时,排序也交给服务端

如果表格数据来自分页接口,前端只拿到当前页的 20 条数据。此时直接在前端对这 20 条排序,得到的并不是全量排序结果。

可以监听 sort-change,把字段名和顺序传回接口:

1
2
3
<el-table @sort-change="handleSortChange">
  <el-table-column prop="createdAt" label="创建时间" sortable="custom" />
</el-table>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const sortBy = ref('createdAt')
const sortOrder = ref<'ascending' | 'descending' | ''>('descending')

const handleSortChange = ({ prop, order }: {
  prop: string
  order: 'ascending' | 'descending' | null
}) => {
  sortBy.value = prop
  sortOrder.value = order ?? ''
  currentPage.value = 1
  loadUsers()
}

小结

表格页真正需要统一管理的是请求参数和页面状态:页码、每页数量、筛选条件、排序条件、总数与加载状态。把这些状态放在同一处后,分页和表格才不会各自运行、互相打架。

使用 Hugo 构建
主题 StackJimmy 设计