1. 功能概述
本教程将介绍如何使用 Vue 3 和 arco 组件库实现表格合并功能。具体来说,我们会根据表格数据中的某个字段(如 type
)对表格的某一列(如入库类型列)进行合并,同时将质检说明列合并为一列。
2. 数据准备
首先,我们需要准备一份数据示例:
import { ref, computed } from 'vue';
const storageGoodsVosdata = ref([
{
id: 1,
workOrderId: 'WO001',
goodsNo: 'G001',
goodsName: 'Test Product 1',
skuCode: 'SKU001',
skuInfo: 'Color: White; Style: Simple',
quantity: 1,
type: 0,
typeName: 'Defective Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
],
outerPackagingUrls: [
'https://example.com/image3.jpg',
'https://example.com/image4.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image5.jpg',
'https://example.com/image6.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 2,
workOrderId: 'WO001',
goodsNo: 'G001',
goodsName: 'Test Product 1',
skuCode: 'SKU001',
skuInfo: 'Color: White; Style: Simple',
quantity: 1,
type: 0,
typeName: 'Defective Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
],
outerPackagingUrls: [
'https://example.com/image3.jpg',
'https://example.com/image4.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image5.jpg',
'https://example.com/image6.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 3,
workOrderId: 'WO001',
goodsNo: 'G002',
goodsName: 'Test Product 2',
skuCode: 'SKU002',
skuInfo: 'Color: Black; Style: Modern',
quantity: 1,
type: 1,
typeName: 'Goods Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image7.jpg',
'https://example.com/image8.jpg',
],
outerPackagingUrls: [
'https://example.com/image9.jpg',
'https://example.com/image10.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image11.jpg',
'https://example.com/image12.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 4,
workOrderId: 'WO001',
goodsNo: 'G002',
goodsName: 'Test Product 2',
skuCode: 'SKU002',
skuInfo: 'Color: Black; Style: Modern',
quantity: 1,
type: 1,
typeName: 'Goods Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image7.jpg',
'https://example.com/image8.jpg',
],
outerPackagingUrls: [
'https://example.com/image9.jpg',
'https://example.com/image10.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image11.jpg',
'https://example.com/image12.jpg',
],
createTime: '2025-02-12 09:43:31',
},
]);
const sortedStorageData = computed(() => {
return [...storageGoodsVosdata.value].sort((a, b) => a.type - b.type);
});
3. 表格合并方法
接下来,我们定义一个 spanMethod
函数来处理表格合并逻辑:
function spanMethod({ rowIndex, columnIndex }) {
if (columnIndex === 0) { // 只处理第一列(入库类型列)
const arr = sortedStorageData.value;
const item = arr[rowIndex];
// 如果是第一行,或者当前行的类型与前一行不同
if (rowIndex === 0 || arr[rowIndex - 1].type !== item.type) {
// 计算当前类型的连续行数
const count = arr.slice(rowIndex).findIndex(row => row.type !== item.type);
return {
rowspan: count === -1 ? arr.length - rowIndex : count,
colspan: 1,
};
}
// 其他行不显示
return {
rowspan: 0,
colspan: 0,
};
}
// 处理质检说明列(最后一列)
if (columnIndex === 8) { // 质检说明列的索引
if (rowIndex === 0) {
return {
rowspan: sortedStorageData.value.length,
colspan: 1,
};
}
return {
rowspan: 0,
colspan: 0,
};
}
}
模板部分
最后,我们在模板中使用 a-table
组件来渲染表格,并绑定数据和合并方法:
<template>
<a-table
:data="sortedStorageData"
:bordered="{ cell: true }"
:pagination="false"
:span-method="spanMethod"
>
<template #columns>
<a-table-column title="Incoming Type" data-index="typeName" align="center" :min-width="180">
<template #cell="{ record }">
{{ record.type === 1 ? 'Goods Incoming' : 'Defective Incoming' }}
</template>
</a-table-column>
<a-table-column
title="Product SKU Number"
data-index="salary"
align="center"
:min-width="180"
>
<template #cell="{ record }">
{{ record.goodsNo }}
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Product Name"
data-index="goodsNo"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
{{ record.goodsName }}
</div>
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Quantity"
data-index="goodsName"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
×{{ record.quantity }}
</div>
</template>
</a-table-column>
<a-table-column cell-class="custom-col" title="Defective Reason" data-index="spec" align="center" :min-width="180">
<template #cell="{ record }">
<div class="good-item-wrapper">
{{ record.type === 1 ? '--' : record.reasonCodeStr }}
</div>
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Logistic Receipts"
data-index="quantity"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
<div v-show="record.type === 1">
--
</div>
<div style="display: flex; gap: 5px;">
<a-image
v-for="item in record.logisticReceiptsUrls"
v-show="record.type === 0"
:key="item"
width="30"
:src="item"
/>
</div>
</div>
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Outer Packaging"
data-index="quantity"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
<div v-show="record.type === 1">
--
</div>
<div style="display: flex; gap: 5px;">
<a-image
v-for="item in record.outerPackagingUrls"
v-show="record.type === 0"
:key="item"
width="30"
:src="item"
/>
</div>
</div>
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Housekeeping Diagram"
data-index="quantity"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
<div v-show="record.type === 1">
--
</div>
<div style="display: flex; gap: 5px;">
<a-image
v-for="item in record.housekeepingDiagramUrls"
v-show="record.type === 0"
:key="item"
width="30"
:src="item"
/>
</div>
</div>
</template>
</a-table-column>
<a-table-column
cell-class="custom-col"
title="Quality Inspection Note"
data-index="quantity"
align="center"
:min-width="180"
>
<template #cell="{ record }">
<div class="good-item-wrapper">
<!-- 这里可以替换为实际的质检说明数据 -->
质检说明
</div>
</template>
</a-table-column>
</template>
</a-table>
</template>
<script setup>
import { ref, computed } from 'vue';
import { ATable, ATableColumn, AImage } from 'ant-design-vue';
const storageGoodsVosdata = ref([
{
id: 1,
workOrderId: 'WO001',
goodsNo: 'G001',
goodsName: 'Test Product 1',
skuCode: 'SKU001',
skuInfo: 'Color: White; Style: Simple',
quantity: 1,
type: 0,
typeName: 'Defective Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
],
outerPackagingUrls: [
'https://example.com/image3.jpg',
'https://example.com/image4.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image5.jpg',
'https://example.com/image6.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 2,
workOrderId: 'WO001',
goodsNo: 'G001',
goodsName: 'Test Product 1',
skuCode: 'SKU001',
skuInfo: 'Color: White; Style: Simple',
quantity: 1,
type: 0,
typeName: 'Defective Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
],
outerPackagingUrls: [
'https://example.com/image3.jpg',
'https://example.com/image4.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image5.jpg',
'https://example.com/image6.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 3,
workOrderId: 'WO001',
goodsNo: 'G002',
goodsName: 'Test Product 2',
skuCode: 'SKU002',
skuInfo: 'Color: Black; Style: Modern',
quantity: 1,
type: 1,
typeName: 'Goods Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image7.jpg',
'https://example.com/image8.jpg',
],
outerPackagingUrls: [
'https://example.com/image9.jpg',
'https://example.com/image10.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image11.jpg',
'https://example.com/image12.jpg',
],
createTime: '2025-02-12 09:43:31',
},
{
id: 4,
workOrderId: 'WO001',
goodsNo: 'G002',
goodsName: 'Test Product 2',
skuCode: 'SKU002',
skuInfo: 'Color: Black; Style: Modern',
quantity: 1,
type: 1,
typeName: 'Goods Incoming',
reasonCode: 'R01',
reasonCodeStr: 'Label/Certificate/Manual/Missing or Damaged',
logisticReceiptsUrls: [
'https://example.com/image7.jpg',
'https://example.com/image8.jpg',
],
outerPackagingUrls: [
'https://example.com/image9.jpg',
'https://example.com/image10.jpg',
],
housekeepingDiagramUrls: [
'https://example.com/image11.jpg',
'https://example.com/image12.jpg',
],
createTime: '2025-02-12 09:43:31',
},
]);
const sortedStorageData = computed(() => {
return [...storageGoodsVosdata.value].sort((a, b) => a.type - b.type);
});
function spanMethod({ rowIndex, columnIndex }) {
if (columnIndex === 0) { // 只处理第一列(入库类型列)
const arr = sortedStorageData.value;
const item = arr[rowIndex];
// 如果是第一行,或者当前行的类型与前一行不同
if (rowIndex === 0 || arr[rowIndex - 1].type !== item.type) {
// 计算当前类型的连续行数
const count = arr.slice(rowIndex).findIndex(row => row.type !== item.type);
return {
rowspan: count === -1 ? arr.length - rowIndex : count,
colspan: 1,
};
}
// 其他行不显示
return {
rowspan: 0,
colspan: 0,
};
}
// 处理质检说明列(最后一列)
if (columnIndex === 8) { // 质检说明列的索引
if (rowIndex === 0) {
return {
rowspan: sortedStorageData.value.length,
colspan: 1,
};
}
return {
rowspan: 0,
colspan: 0,
};
}
}
</script>
<style scoped>
.custom-col {
/* 可以添加自定义样式 */
}
.good-item-wrapper {
/* 可以添加自定义样式 */
}
</style>
5. 解释
- 数据排序:使用
computed
计算属性对数据进行排序,确保相同类型的数据相邻。 - 表格合并逻辑:
spanMethod
函数根据列索引和行索引来决定哪些单元格需要合并。对于入库类型列,根据type
字段合并相同类型的行;对于质检说明列,将所有行合并为一列。 - 模板渲染:使用
a-table
组件渲染表格,并通过#columns
插槽定义表格列。每个列可以通过#cell
插槽自定义单元格内容。
通过以上步骤,你就可以实现一个带有合并单元格功能的表格。