当前位置: 首页 > news >正文

教程:使用 Vue 3 和 arco 实现表格合并

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 插槽自定义单元格内容。

通过以上步骤,你就可以实现一个带有合并单元格功能的表格。

6. 效果

在这里插入图片描述

相关文章:

  • MySQL 数据库定时任务及进阶学习
  • UE_C++ —— Metadata Specifiers
  • Redis——优惠券秒杀问题(分布式id、一人多单超卖、乐悲锁、CAS、分布式锁、Redisson)
  • 【目标检测json2txt】label从COCO格式json文件转YOLO格式txt文件
  • mysql开启gtid并配置主从
  • Windows 11 下 Ollama 安装与 OpenWebUI 调用 DeepSeek-R1 的详细指南
  • 100N03-ASEMI豆浆机专用MOS管100N03
  • qt QOpenGLContext详解
  • 数字电路-基础逻辑门实验
  • 【Elasticsearch】runtime_mappings搜索请求中定义运行时字段
  • 微软AutoGen高级功能——Magentic-One
  • LabVIEW软件需求开发文档参考
  • Win11配置wsl、ubuntu、docker
  • 【哇! C++】内联函数、auto关键字、基于范围的for循环、指针空值nullptr
  • git bash在github的库中上传或更新本地文件
  • 本地部署Deepseek-R1模型指南:从Ollama安装到RAG应用
  • 【Linux】Ubuntu Linux 系统——Node.js 开发环境
  • Spring Security,servlet filter,和白名单之间的关系
  • C#中的GC机制简析
  • AI在电竞比分网中的主要应用场景
  • 美联储官员:美国经济增速可能放缓,现行关税政策仍将导致物价上涨
  • 中央提级巡视后,昆明厅官郭子贞接受审查调查
  • 国防部:中方愿与俄方不断增强两军关系良好发展势头
  • 秦洪看盘|指标股发力,A股渐有突破态势
  • 美凯龙:董事兼总经理车建兴被立案调查并留置
  • 李强会见巴西总统卢拉