Ant Design Vue 实现表格合并单元格并且可分页

1. 问题背景

在许多管理类系统中,表格是展示数据的常见方式。而在一些具有层级结构的数据展示场景中,表格可能包含多个数据层级,如 "大条数据" 和 "小条数据"(每大条数据包含若干小条,数量不一定)。例如,在企业项目管理系统中,每一条大条数据可能代表一项主要的项目,而该大条数据下可能包含多条相关的小条数据,比如该项目下的子任务。

这种数据展示通常可以采用多种交互形式,比如:

  • 树形表格:通过树形结构展示父子层级关系,支持展开/收起操作,方便查看层级数据。
  • 嵌套表格:每行展示大条数据,并在大条数据下嵌套子表格显示相关的小条数据。
  • 表格单元格合并(行合并) :通过合并单元格的方式,把大条数据的内容合并到多行数据中。

在 Vue 3 + Antd Design 的项目中,Table 组件可以支持上述三种交互形式,但当采用单元格合并 的方式展示这种层级数据,同时还要进行分页展示的话(假设需求为每页显示 10 大条数据),会遇到一个问题:

Antd Design Vue Table 组件默认的分页逻辑是基于数据的总行数来计算的,pageSize 会直接影响表格显示的条数。也就是说,在使用单元格行合并的时候,其分页逻辑是按小条数据计算(如果一条大条数据包含 5 条小条数据,那么该条大条数据实际上会占用分页的 5 行),而不是按大条数据为单位分页显示。

假设当前表格pageSize=10,表格则会在每一页上展示 10 条小条数据,而不是 10 条大条数据。如果每个大条数据包含多个数量不固定的小条数据,就会造成分页显示的混乱:每一页的大条数据数量不固定,且无法控制每页展示的大条数据的数量,每页可能显示的大条数据数量会大大少于 10 条,无法满足每页显示 10 大条数据的需求。

2. 解决方案

不直接使用 Antd Design Vue Table 组件自带的分页逻辑,而是通过自定义分页逻辑来实现。

3. 代码实现 demo

vue 复制代码
<template>
  <a-table
    :columns="columns"
    :data-source="formatedData"
    :pagination="false"
    bordered
  >
    <template #bodyCell="{ column, text }">
      <template v-if="column.dataIndex === 'name'">
        <a href="javascript:;">{{ text }}</a>
      </template>
    </template>
  </a-table>
  <a-pagination class="custom-pagination" size="small" v-bind="pagination" />
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import type { TableColumnType } from "ant-design-vue";

const sharedOnCell = (record) => ({
  rowSpan: record.rowSpan,
});

const data = [
  {
    key: "1",
    name: "John Brown",
    age: 32,
    tel: "0571-22098909",
    phone: 18889898989,
    address: "New York No. 1 Lake Park",
    childList: [
      { name: "John Brown's Child 1", age: 5 },
      { name: "John Brown's Child 2", age: 3 },
    ],
  },
  {
    key: "2",
    name: "Jim Green",
    tel: "0571-22098333",
    phone: 18889898888,
    age: 42,
    address: "London No. 1 Lake Park",
    childList: [
      { name: "Jim Green's Child 1", age: 7 },
      { name: "Jim Green's Child 2", age: 4 },
    ],
  },
  {
    key: "3",
    name: "Joe Black",
    age: 32,
    tel: "0575-22098909",
    phone: 18900010002,
    address: "Sidney No. 1 Lake Park",
    childList: [{ name: "Joe Black's Child 1", age: 6 }],
  },
  {
    key: "4",
    name: "Jim Red",
    age: 18,
    tel: "0575-22098909",
    phone: 18900010002,
    address: "London No. 2 Lake Park",
    childList: [{ name: "Jim Red's Child 1", age: 4 }],
  },
  {
    key: "5",
    name: "Jake White",
    age: 18,
    tel: "0575-22098909",
    phone: 18900010002,
    address: "Dublin No. 2 Lake Park",
    childList: [
      { name: "Jake White's Child 1", age: 2 },
      { name: "Jake White's Child 2", age: 2 },
    ],
  },
];

const columns: TableColumnType[] = [
  {
    title: "No",
    dataIndex: "index",
    customCell: sharedOnCell,
  },
  {
    title: "Name",
    dataIndex: "name",
    customCell: sharedOnCell,
  },
  {
    title: "Age",
    dataIndex: "age",
    customCell: sharedOnCell,
  },
  {
    title: "Home phone",
    colSpan: 2,
    dataIndex: "tel",
    customCell: sharedOnCell,
  },
  {
    title: "Phone",
    colSpan: 0,
    dataIndex: "phone",
    customCell: sharedOnCell,
  },
  {
    title: "Children",
    children: [
      {
        title: "Name",
        dataIndex: "childrenName",
      },
      {
        title: "Age",
        dataIndex: "childrenAge",
      },
    ],
  },
  {
    title: "Address",
    dataIndex: "address",
    customCell: sharedOnCell,
  },
];

const pageSize = ref(3);
const currentPage = ref(1);

const formatedData = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value;
  const end = start + pageSize.value;
  const lastData = data.slice(start, end).flatMap((item, index) => {
    // 每条大数据展开成小数据
    return item.childList.map((subItem, subIndex) => ({
      ...item,
      childrenName: subItem.name,
      childrenAge: subItem.age,
      key: `${item.key}-${subIndex}`,
      rowSpan: subIndex === 0 ? item.childList.length : 0,
      index: start + index + 1,
    }));
  });
  return lastData;
});

const pagination = computed(() => {
  return {
    current: currentPage.value,
    pageSize: pageSize.value,
    total: data.length,
    showTotal: (total) => `共 ${total} 条数据`,
    showSizeChanger: true,
    showQuickJumper: true,
    pageSizeOptions: ["3", "10", "15", "20", "50"],
    onChange: (page, newPageSize) => {
      currentPage.value = page;
      pageSize.value = newPageSize;
    },
  };
});
</script>

<style scoped>
.custom-pagination {
  display: flex;
  padding: 12px 0;
  justify-content: flex-end;
}
</style>

4. 实现效果

预览:在 CodeSandbox 中打开

相关推荐
abigale031 小时前
webpack+vite前端构建工具 -11实战中的配置技巧
前端·webpack·node.js
专注API从业者1 小时前
构建淘宝评论监控系统:API 接口开发与实时数据采集教程
大数据·前端·数据库·oracle
Joker`s smile1 小时前
Chrome安装老版本、不同版本,自制便携版本用于前端调试
前端·chrome
weixin_416639971 小时前
爬虫工程师Chrome开发者工具简单介绍
前端·chrome·爬虫
我是如子啊2 小时前
【解决“此扩展可能损坏”】Edge浏览器(chrome系列通杀))扩展损坏?一招保留数据快速修复
前端·chrome·edge
灵性花火2 小时前
Qt的前端和后端过于耦合(0/7)
开发语言·前端·qt
孤水寒月5 小时前
基于HTML的悬窗可拖动记事本
前端·css·html
祝余呀6 小时前
html初学者第一天
前端·html
耶啵奶膘8 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
视频砖家9 小时前
移动端Html5播放器按钮变小的问题解决方法
前端·javascript·viewport功能