Vue学习笔记-分页表格最佳实践(封装 Table + 分页组件) - 详解

作者:一名 Vue 的学习者
记录时间:2025年12月
目标:熟练使用分页组件,实现如下效果:


写到这个阶段,我已经能做页面、布局、菜单、登录、Echarts、Axios 封装等基础能力。
但实际开发中最常见的场景之一——表格 + 分页,才是真正决定开发效率的地方。

一个“好用的分页表格组件”,能让后台管理系统的开发效率翻倍。

这篇我不只使用表格,而是要实现:

封装一个通用的 Table + Pagination 组件
支持自动分页、加载状态
支持外部传入查询参数
支持点击分页自动触发重新请求
让页面逻辑只保留 5 行代码


一、为什么要封装分页表格?

实际开发中,如果不封装,几乎每个列表页面都长这样:

const list = ref([]);
const page = ref(1);
const size = ref(10);
const total = ref(0);
const fetchList = async () => {
  const res = await api.getList({ page: page.value, size: size.value });
  list.value = res.records;
  total.value = res.total;
};

并且:

  • 分页切换要重新请求
  • 重置搜索要重置到第一页
  • 页面加载要自动请求
  • loading 控制又写一遍

一个系统几十个列表页面,都是重复代码。
封装组件后,我们的列表页可以变成:

只写一行,舒服!


二、基础组件结构设计

这里打算把组件拆成两个文件:

✔ BaseTable.vue(组合表格 + 分页 + loading)

✔ BasePagination.vue(分页组件)

目录如下:

src/components/base/
    BaseTable.vue
    BasePagination.vue

三、先做分页组件 BasePagination.vue

分页组件功能:

  • 切换页码触发事件
  • 切换条数触发事件

用 Element Plus 会简单很多,下文以 Element Plus 为例。


BasePagination.vue


<script setup>
const props = defineProps({
  total: Number,
  currentPage: Number,
  pageSize: Number
});
const emits = defineEmits(["update:currentPage", "update:pageSize", "change"]);
const onPageChange = (val) => {
  emits("update:currentPage", val);
  emits("change");
};
const onSizeChange = (val) => {
  emits("update:pageSize", val);
  emits("change");
};
</script>

支持:

  • v-model:currentPage
  • v-model:pageSize
  • change(统一触发外部刷新)

四、封装 BaseTable(重点)

目标:

接收 columns
接收 request(统一分页请求函数)
自动处理 loading
自动请求数据
内置分页组件

列内容脱敏

自定义操作列


BaseTable.vue


<script setup>
import { ref, watch, onMounted } from "vue";
import BasePagination from "./BasePagination.vue";
/* ========= props ========= */
const props = defineProps({
  columns: {
    type: Array,
    default: () => []
  },
  request: {
    type: Function,
    required: true
  },
  params: {
    type: Object,
    default: () => ({})
  }
});
/* ========= 列类型预设 ========= */
const COLUMN_PRESET = {
  index: {
    width: 70,
    align: "center"
  },
  action: {
    width: 160,
    align: "center"
  },
  short: {
    width: 100,
    align: "left"
  },
  normal: {
    minWidth: 140,
    align: "left"
  },
  long: {
    minWidth: 220,
    align: "left",
    tooltip: true
  },
  time: {
    width: 180,
    align: "center"
  },
  status: {
    width: 90,
    align: "center"
  }
};
/* ========= 工具方法 ========= */
const resolveColumn = (col) => {
  const preset = COLUMN_PRESET[col.type] || {};
  return {
    ...preset,
    ...col
  };
};
/* ========= 表格状态 ========= */
const tableData = ref([]);
const loading = ref(false);
const total = ref(0);
const page = ref(1);
const pageSize = ref(10);
/* ========= 数据请求 ========= */
const fetchList = async () => {
  if (!props.request) return;
  loading.value = true;
  try {
    const res = await props.request({
      current: page.value,
      size: pageSize.value,
      ...props.params
    });
    tableData.value = res?.records || [];
    total.value = res?.total || 0;
  } finally {
    loading.value = false;
  }
};
onMounted(fetchList);
watch(
    () => props.params,
    () => {
      page.value = 1;
      fetchList();
    },
    { deep: true }
);
defineExpose({
  fetchList
});
</script>

五、如何在页面中使用?

假设我们有个用户接口:

// api/user.js
export function getUserList(params) {
  return request({
    url: "/user/list",
    method: "get",
    params
  });
}

页面中使用


<script setup>
import BaseTable from "@/components/base/BaseTable.vue";
import * as api from "@/api/user";
import {ref} from "vue";
import {maskMobile, maskName} from '@/utils/mask';
const tableRef = ref();
const columns = [
  { type: 'index' },
  { label: "姓名", prop: "name" ,formatter: maskName, type: 'short' },
  { label: "手机号", prop: "phone" ,formatter: maskMobile , type: 'normal'},
  // { label: "邮箱", prop: "email", type: 'normal' },
  { label: "创建时间", prop: "createTime" , type: 'time'},
  { prop: 'status', label: '状态', type: 'status', slot: 'status' },
  { type: 'action' }
];
const searchForm = ref({
  phone: '',
  username: '',
});
function handleReset() {
  searchForm.value = {};
  tableRef.value?.fetchList();
}
function edit(row) {
//todo
}
function remove(row) {
//todo
}
</script>

现在一个分页列表页面几乎不需要逻辑了。


六、扩展:表格插槽支持更多内容

为了支持“操作列、按钮、格式化”等,BaseTable 增加了插槽:


  

页面即可:


  

七、小结

本篇实现了一个真正可复用的分页表格组件,让我在后台系统开发过程中的效率提升非常明显。

本篇成果:


✔ BasePagination

  • 封装 Element Plus 分页
  • 简化分页切换逻辑
  • 支持 v-model 双向绑定

✔ BaseTable

  • 自动触发分页请求
  • 自动处理 loading、data、total
  • 支持外部搜索参数
  • 支持表格插槽扩展
  • 支持自定义列脱敏
  • 页面逻辑极致简化

✔ 实际效果

现在一个分页列表页面,只需要写:

实现了前后端项目中最常见的一类页面,真正做到“写一次,到处用”。


下一篇预告

下一步计划:树形结构学习

posted @ 2026-01-14 20:04  gccbuaa  阅读(11)  评论(0)    收藏  举报