Ant Design Vue 表格复杂数据合并单元格

news2025/4/16 13:21:49

Ant Design Vue 表格复杂数据合并单元格

官方合并效果

在这里插入图片描述

官方示例

表头只支持列合并,使用 column 里的 colSpan 进行设置。
表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。

<template>
  <a-table :columns="columns" :data-source="data" bordered>
    <template slot="name" slot-scope="text">
      <a>{{ text }}</a>
    </template>
  </a-table>
</template>
<script>
// In the fifth row, other columns are merged into first column
// by setting it's colSpan to be 0
const renderContent = (value, row, index) => {
  const obj = {
    children: value,
    attrs: {},
  };
  if (index === 4) {
    obj.attrs.colSpan = 0;
  }
  return obj;
};

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

export default {
  data() {
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        customRender: (text, row, index) => {
          if (index < 4) {
            return <a href="javascript:;">{text}</a>;
          }
          return {
            children: <a href="javascript:;">{text}</a>,
            attrs: {
              colSpan: 5,
            },
          };
        },
      },
      {
        title: 'Age',
        dataIndex: 'age',
        customRender: renderContent,
      },
      {
        title: 'Home phone',
        colSpan: 2,
        dataIndex: 'tel',
        customRender: (value, row, index) => {
          const obj = {
            children: value,
            attrs: {},
          };
          if (index === 2) {
            obj.attrs.rowSpan = 2;
          }
          // These two are merged into above cell
          if (index === 3) {
            obj.attrs.rowSpan = 0;
          }
          if (index === 4) {
            obj.attrs.colSpan = 0;
          }
          return obj;
        },
      },
      {
        title: 'Phone',
        colSpan: 0,
        dataIndex: 'phone',
        customRender: renderContent,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        customRender: renderContent,
      },
    ];
    return {
      data,
      columns,
    };
  },
};
</script>

实际项目中实现效果

在这里插入图片描述

实现原理

分层说明

  1. 数据预处理

    • 使用prepareData方法按markId字段分组
    • 组内数据按mergeIs字段排序(值为"是"的排在前)
  2. 双层级合并机制

    • 主合并层:相同markId的"名称"列合并
    • 次级合并层:在相同markId组内,连续mergeIs === '是’的"数量"列合并
  3. 合并标识管理

    • 通过rowSpan属性控制行合并数
    • rowSpan=0表示该单元格被合并
    • originalIndex记录原始位置用于合并定位
  4. 动态计数器机制

    • primarySpan跟踪名称列合并跨度
    • secondarySpan跟踪数量列合并跨度
    • 遇到分组边界或状态变化时重置计数器
{
  markId: "分组标识",  // 用于主合并层级
  mergeIs: "是/否",   // 用于次级合并层级
  name: "显示内容",    // 名称列数据
  num: "数值"         // 数量列数据
}

数据流向示意图

分组排序
原始数据
预处理模块
结构优化数据
合并处理器
可合并数据集
表格渲染

表格组件配置

<template>
  <section class="console-section-box">
    <div class="con">
      <a-table
        :columns="columns"
        :data-source="tableData"
        :showHeader="true"
        :loading="tableLoading"
        :pagination="pagination"
        :bordered="true"
        :rowKey="
          (record, index) => {
            return index;
          }
        "
        :scroll="{ x: true }"
      >
      </a-table>
    </div>
    <a-back-top />
  </section>
</template>

合并逻辑

<script>
import { mockData } from '~/mock/index.js';
const productColumn = [
  {
    title: '名称',
    dataIndex: 'name',
    customRender: (value, row, index) => {
      const { rowSpan, originalIndex } = row.nameCellObj || { rowSpan: 1, originalIndex: index };
      const obj = {
        children: value,
        attrs: {}
      };
      if (index === originalIndex) {
        obj.attrs.rowSpan = rowSpan;
        obj.attrs.colSpan = 1;
      }
      return obj;
    },
    align: 'center',
    width: 90
  },
  {
    title: '类型',
    dataIndex: 'type',
    align: 'center',
    width: 100
  },
  {
    title: '数量',
    dataIndex: 'num',
    key: 'num',
    customRender: (value, row, index) => {
      const { rowSpan, originalIndex } = row.numCellObj || { rowSpan: 1, originalIndex: index };
      const obj = {
        children: value,
        attrs: {}
      };
      if (index === originalIndex) {
        obj.attrs.rowSpan = rowSpan;
        obj.attrs.colSpan = 1;
      }
      return obj;
    },
    align: 'center',
    width: 90
  }
];
export default {
  name: '',
  data() {
    return {
      tableLoading: false,
      tableData: [],
      pagination: {
        current: 1, // 当前页码
        pageSize: 10000, // 每页显示条数
        total: 0,
        showTotal: total => `共有 ${total} 条数据` //分页中显示总的数据
      },
      columns: productColumn,
    };
  },
  async mounted() {
    await this.fetchData();
  },
  methods: {
    async fetchData() {
      this.tableLoading = true;
      try {
        const res = await this.XXXX();
        if (res.code === 0) {
          this.tableData = mockData;
          this.pagination.total = res.data.length;
          this.handleCellMerge(this.tableData);
        }
      } catch (error) {
        console.error('Error fetching data:', error);
      }
      this.tableLoading = false;
    },
   // 根据数据合并单元格
    handleCellMerge(arr) {
      if (!arr?.length) return;

      const processor = {
        currentMarkId: null,
        currentMergeIs: null,
        primarySpan: 1,
        secondarySpan: 1,

        // 初始化单元格状态
        initialize(row, index) {
          row.nameCellObj = { rowSpan: 1, originalIndex: index };
          row.numCellObj = { rowSpan: 1, originalIndex: index };
        },

        // 主合并逻辑
        processPrimary(index, rows) {
          if (rows[index].markId === this.currentMarkId) {
            this.primarySpan++;
            rows[index - this.primarySpan + 1].nameCellObj.rowSpan = this.primarySpan;
            rows[index].nameCellObj.rowSpan = 0;
            return true;
          }
          this.currentMarkId = rows[index].markId;
          this.primarySpan = 1;
          return false;
        },

        // 次级合并逻辑
        processSecondary(index, rows) {
          if (rows[index].mergeIs === this.currentMergeIs && this.currentMergeIs === '是') {
            this.secondarySpan++;
            rows[index - this.secondarySpan + 1].numCellObj.rowSpan = this.secondarySpan;
            rows[index].numCellObj.rowSpan = 0;
            return true;
          }
          this.currentMergeIs = rows[index].mergeIs;
          this.secondarySpan = 1;
          return false;
        }
      };

      const sortedData = this.prepareData(arr);
      processor.currentMarkId = sortedData[0].markId;
      processor.currentMergeIs = sortedData[0].mergeIs;

      // 单次遍历处理所有合并逻辑
      sortedData.forEach((item, index) => {
        processor.initialize(item, index);
        if (index === 0) return;

        if (processor.processPrimary(index, sortedData)) {
          processor.processSecondary(index, sortedData);
        } else {
          processor.currentMergeIs = item.mergeIs;
        }
      });

      arr.splice(0, arr.length, ...sortedData);
    },
       // 分组排序方法
    prepareData(originData) {
      // 使用Map提高分组性能
      const groups = new Map();
      for (const item of originData) {
        const group = groups.get(item.markId) || [];
        group.push(item);
        groups.set(item.markId, group);
      }

      // 预计算排序权重避免重复计算
      return Array.from(groups.values()).flatMap(group => group.sort((a, b) => (b.mergeIs === '是') - (a.mergeIs === '是')));
    }
  }
};
</script>

mock数据

mock/index.js

export const mockData = [
  {
    name: '数据A',
    num: '9999999',
    type: 'AAA',
    mergeIs: '是',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'BBB',
    mergeIs: '是',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'CCC',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据B',
    num: '600',
    type: 'AAA',
    mergeIs: '是',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'BBB',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '600',
    type: 'CCC',
    mergeIs: '是',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'AAA',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'BBB',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'CCC',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_003'
  }
];

5. 样式

<style lang="scss" scoped>
.con {
  min-height: calc(100vh - 160px);
  padding: 24px;
  border-radius: 8px;
  background-color: #fff;
}
.project-info-box {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  padding-bottom: 20px;
}
.project-info {
  width: 100%;
  height: 60px;
  line-height: 60px;
  display: flex;
  justify-content: space-between;
  p {
    margin: 0;
  }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2335287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ECharts 如何实现柱状图悬停时,整个背景区域均可触发点击事件

1. 前言 ECharts 柱状图的点击事件默认仅响应柱子本身的点击&#xff0c;本文旨在实现整个背景区域均可触发点击事件 2. 实现思路 核心&#xff1a;全局监听 坐标判断 数据转换 通过 getZr() 监听整个画布点击&#xff0c;结合像素坐标判断是否在图表区域内‌通过 containPi…

金融简单介绍及金融诈骗防范

在当今社会&#xff0c;金融学如同一股无形却强大的力量&#xff0c;深刻影响着我们生活的方方面面。无论是个人的日常收支、投资理财&#xff0c;还是国家的宏观经济调控&#xff0c;都与金融学紧密相连。​ 一、金融学的概念​ 金融学&#xff0c;简单来说&#xff0c;是研…

cursor+高德MCP:制作一份旅游攻略

高德开放平台 | 高德地图API (amap.com) 1.注册成为开发者 2.进入控制台选择应用管理----->我的应用 3.新建应用 4.点击添加Key 5.在高德开发平台找到MCP的文档 6.按照快速接入的步骤&#xff0c;进行操作 一定要按照最新版的cursor, 如果之前已经安装旧的版本卸载掉重新安…

Kubernetes控制平面组件:API Server Webhook 授权机制 详解

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes控…

【Python爬虫】简单案例介绍2

本文继续接着我的上一篇博客【Python爬虫】简单案例介绍1-CSDN博客 目录 跨页 3.2 环境准备 跨页 当对单个页面的结构有了清晰的认识并成功提取数据后&#xff0c;接下来就需要考虑页面之间的跨页问题。此时我们便迎来了下一个关键任务&#xff1a;如何实现跨页爬取&#xf…

【神经网络】python实现神经网络(四)——误差反向传播的基础理论

一.反向传播 本章将介绍能够高效计算权重参数的梯度的方法——误差反向传播法,这里简单介绍一下什么是反向传播,加入有个函数y = f(x),那么它的反向传播为图下这个样子: 反向传播的计算顺序是,将输入信号E乘以节点的局部导数,然后将结果传递给下一个节点。这里所…

I/O进程(全)

I/O 一、标准IO 1.概念 在C库中定义的一组用于输入输出的函数 2.特点 (1).通过缓冲机制减少系统调用&#xff0c;提高效率 (2.)围绕流进行操作&#xff0c;流用FILE *来描述(3).标准IO默认打开了三个流&#xff0c;stdin&#xff08;标准输入&#xff09;、stdout&#xff08;标…

vue2使用ezuikit-js播放萤石视频

需求&#xff1a;需要在大屏上播放萤石视频&#xff0c;用到官方的ezuikit-js插件实现&#xff0c;并实现视频播放切换功能。有个问题至今没有解决&#xff0c;就是萤石视频的宽高是固定的&#xff0c;不会根据大屏缩放进行自适应。我这边做了简单的刷新自适应。 1.下载ezuikit…

【笔试强训day19】

目录 第一题&#xff1a;小易的升级之路 描述 输入描述&#xff1a; 输出描述&#xff1a; 输入&#xff1a; 输出&#xff1a; 第二题&#xff1a;礼物的最大价值 描述 输入&#xff1a; 返回值&#xff1a; 备注&#xff1a; 第三题&#xff1a;对称之美 题目描述…

STM32电机库 电机控制特性

ST MC FW库提供FOC和六步法两种电机控制方式。这使得它能够驱动永磁同步电机 (PMSM) 和无刷直流电机 (BLDC)。FOC 更适合 PMSM,而六步法更适合 BLDC 电机。该固件可以驱动内嵌式PMSM 和标贴式PMSM。 ST Motor Control 固件库提供以下功能: FOC SVPWM 生成: 可配置的 PW…

【Linux】42.网络基础(2.4)

文章目录 2.3 TCP协议2.3.10 拥塞控制2.3.11 延迟应答2.3.12 捎带应答2.3.13 面向字节流2.3.14 粘包问题2.3.15 TCP异常情况2.3.16 TCP小结2.3.17 基于TCP应用层协议 2.3 TCP协议 2.3.10 拥塞控制 虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚…

SPI接口DAC设备驱动与应用程序开发

本文章相关专栏往期内容&#xff0c;SPI子系统专栏&#xff1a; SPI通信协议与Linux设备驱动框架解析SPI传输与驱动框架的实现spidev.c&#xff1a;SPI设备驱动的核心实现逻辑 PCI/PCIe子系统专栏&#xff1a; 专栏地址&#xff1a;PCI/PCIe子系统PCIe设备MSI/MSI-X中断源码分析…

第十六届蓝桥杯大赛软件赛省赛 Python 大学 B 组 满分题解

题面链接Htlang/2025lqb_python_b 个人觉得今年这套题整体比往年要简单许多&#xff0c;但是G题想简单了出大问题&#xff0c;预估50101015120860&#xff0c;道阻且长&#xff0c;再接再厉 代码仅供学习参考&#xff0c;满分为赛后洛谷中的测评&#xff0c;蓝桥杯官方测评待…

旧版 VMware 虚拟机迁移至 KVM 平台-案例2

项目背景 需将一台旧版 VMware 虚拟机&#xff08;VMDK 格式&#xff09;迁移至 KVM 虚拟化平台&#xff0c;具体要求如下&#xff1a; 格式转换&#xff1a;将 VMDK 转换为 QCOW2 格式。磁盘扩容&#xff1a;将原 40GB 磁盘扩展至 60GB。密码重置&#xff1a;修改 aiden 用户…

若依代码生成器原理velocity模板引擎(自用)

1.源码分析 代码生成器:导入表结构(预览、编辑、删除、同步)、生成前后端代码 代码生成器表结构说明&#xff1a; 若依提供了两张核心表来存储导入的业务表信息&#xff1a; gen_table&#xff1a;存储业务表的基本信息 &#xff0c;它对应于配置代码基本信息和生成信息的页…

OpenCV直方图均衡化全面解析:从灰度到彩色图像的增强技术

目录 一、直方图均衡化基础&#xff1a;原理与核心思想 二、彩色图像的直方图均衡化&#xff1a;挑战与解决方案 三、进阶技巧与注意事项 四、应用场景与典型案 一、直方图均衡化基础&#xff1a;原理与核心思想 1. 直方图的本质与作用 直方图是图像像素强度分布的统计图表…

Web前端之Vue+Element实现表格动态复杂的合并行功能、localeCompare、forEach、table、push、sort、Map

MENU 效果图公共数据数据未排序时&#xff08;需要合并的行数据未处于相邻位置&#xff09;固定合并行方法&#xff08;写死&#xff09;动态合并行&#xff0c;行数计算方法当太合并行&#xff0c;合并方法方法&#xff08;函数&#xff09;执行 效果图 公共数据 Html <e…

【DDR 内存学习专栏 1.4 -- DDR 的 Bank Group】

文章目录 BankgroupBankgroup 与 Bank 的关系 DDR4 中的 BankgroupDDR4-3200 8Gb芯片为例组织结构访问场景 实际应用示例 Bankgroup Bankgroup是DDR4及后续标准(DDR5)中引入的一个更高层次的组织结构。它将多个Bank组合在一起形成一个Bankgroup&#xff0c;目的是为了进一步提…

嵌入式进阶:如何选择合适的开发平台?

随着现代工业、物联网以及人工智能技术的迅速发展&#xff0c;嵌入式系统已经由简单的控制器向复杂的高性能系统迈进。从传统家电到智能机器人、从自动驾驶汽车到工业自动化&#xff0c;每一项应用都对嵌入式系统的响应速度、运行稳定性和能耗管理提出了更高要求。在这种背景下…

酶动力学参数预测,瓶颈识别……中科院深圳先进技术研究院罗小舟分享AI在酶领域的创新应用

蛋白质&#xff0c;作为生命的基石&#xff0c;在生命活动中发挥着关键作用&#xff0c;其结构和功能的研究&#xff0c;对创新药物研发、合成生物学、酶制剂生产等领域&#xff0c;有着极其重要的意义。但传统蛋白质设计面临诸多难题&#xff0c;蛋白质结构复杂&#xff0c;序…