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>