Ant Design Vue 表格复杂数据合并单元格
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 cellif (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>
实际项目中实现效果

实现原理
分层说明
-
数据预处理
- 使用prepareData方法按markId字段分组
- 组内数据按mergeIs字段排序(值为"是"的排在前)
-
双层级合并机制
- 主合并层:相同markId的"名称"列合并
- 次级合并层:在相同markId组内,连续mergeIs === '是’的"数量"列合并
-
合并标识管理
- 通过rowSpan属性控制行合并数
- rowSpan=0表示该单元格被合并
- originalIndex记录原始位置用于合并定位
-
动态计数器机制
- 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>
相关文章:
Ant Design Vue 表格复杂数据合并单元格
Ant Design Vue 表格复杂数据合并单元格 官方合并效果 官方示例 表头只支持列合并,使用 column 里的 colSpan 进行设置。 表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。 <temp…...
Fiddler为什么可以看到一次HTTP请求数据?
1、作为代理服务器 Fiddler作为代理服务器,拦截了设备与互联网服务器之间的所有HTTP和HTTPS流量。当客户端(如浏览器)发送请求时,请求先到达Fiddler,然后由Fiddler转发到目标服务器;服务器的响应也会返回给…...
第十九讲 | XGBoost 与集成学习:精准高效的地学建模新范式
🟨 一、为什么要学习集成学习? 集成学习(Ensemble Learning) 是一种将多个弱学习器(如决策树)组合成一个强学习器的策略。它在地理学、生态学、遥感分类等领域表现尤为突出。 📌 应用优势&#…...
基于 GoFrame 框架的电子邮件发送实践:优势、特色与经验分享
1. 引言 如果你是一位有1-2年Go开发经验的后端开发者,可能已经熟悉了Go语言在性能和并发上的天然优势,也曾在项目中遇到过邮件发送的需求——无论是用户注册时的激活邮件、系统异常时的通知,还是营销活动中的批量促销邮件,邮件功…...
ECharts 如何实现柱状图悬停时,整个背景区域均可触发点击事件
1. 前言 ECharts 柱状图的点击事件默认仅响应柱子本身的点击,本文旨在实现整个背景区域均可触发点击事件 2. 实现思路 核心:全局监听 坐标判断 数据转换 通过 getZr() 监听整个画布点击,结合像素坐标判断是否在图表区域内通过 containPi…...
金融简单介绍及金融诈骗防范
在当今社会,金融学如同一股无形却强大的力量,深刻影响着我们生活的方方面面。无论是个人的日常收支、投资理财,还是国家的宏观经济调控,都与金融学紧密相连。 一、金融学的概念 金融学,简单来说,是研…...
cursor+高德MCP:制作一份旅游攻略
高德开放平台 | 高德地图API (amap.com) 1.注册成为开发者 2.进入控制台选择应用管理----->我的应用 3.新建应用 4.点击添加Key 5.在高德开发平台找到MCP的文档 6.按照快速接入的步骤,进行操作 一定要按照最新版的cursor, 如果之前已经安装旧的版本卸载掉重新安…...
软件版本命名规范Semantic Versioning
语义化版本控制(Semantic Versioning,简称 SemVer)是一种广泛采用的版本号管理规范,旨在通过版本号传达软件更新的性质和影响,帮助开发者和用户理解每次发布的变更内容 🔢 版本号结构 语义化版本号通常采…...
Uniapp: 大纲
目录 一、基础巩固1.1、Uniapp:下拉选择框ba-tree-picker1.2、Uniapp:确认框1.3、Uniapp:消息提示1.4、Uniapp:获取当前定位坐标 二、项目配置2.1、Uniapp:修改端口号2.2、Uniapp:本地存储 一、基础巩固 1.1、Uniapp:…...
Kubernetes控制平面组件:API Server Webhook 授权机制 详解
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...
【KWDB 创作者计划】_产品技术解读_1
【KWDB 创作者计划】_产品技术解读_1 一、存储引擎:高性能混合存储架构1. 存储模型设计2. 存储压缩与编码3. 持久化策略二、KWDB 组件源码解析1. 核心模块分层架构2. 关键组件源码剖析三、KWDB 特性代码通读1. 实时分析能力(Real-Time OLAP)2. 混合负载隔离(HTAP)3. 智能索…...
Python Cookbook-6.2 定义常量
任务 你需要定义一些模块级别的变量(比如命名的常量),而且客户代码无法将其重新绑定。 解决方案 你可以把任何对象当做模块一样安装。将下列代码存为一个模块const.py,并放入你的Python的sys.path 指定的目录中: class _const(object):class ConstEr…...
【Python爬虫】简单案例介绍2
本文继续接着我的上一篇博客【Python爬虫】简单案例介绍1-CSDN博客 目录 跨页 3.2 环境准备 跨页 当对单个页面的结构有了清晰的认识并成功提取数据后,接下来就需要考虑页面之间的跨页问题。此时我们便迎来了下一个关键任务:如何实现跨页爬取…...
【神经网络】python实现神经网络(四)——误差反向传播的基础理论
一.反向传播 本章将介绍能够高效计算权重参数的梯度的方法——误差反向传播法,这里简单介绍一下什么是反向传播,加入有个函数y = f(x),那么它的反向传播为图下这个样子: 反向传播的计算顺序是,将输入信号E乘以节点的局部导数,然后将结果传递给下一个节点。这里所…...
【SQL】COUNT... FILTER 的适用场景
【SQL】COUNT... FILTER 的适用场景 一、引言二、FILTER 基础学习2.1 语法解析2.2 适用范围2.2.1 主流数据库支持情况 2.3 技术优势2.3.1 性能优化2.3.2 等阶写法对比 2.4 适用场景2.4.1 多维统计报表2.4.2 动态数据过滤2.4.3 复杂条件处理 三、总结 一、引言 今天参加业务评审…...
I/O进程(全)
I/O 一、标准IO 1.概念 在C库中定义的一组用于输入输出的函数 2.特点 (1).通过缓冲机制减少系统调用,提高效率 (2.)围绕流进行操作,流用FILE *来描述(3).标准IO默认打开了三个流,stdin(标准输入)、stdout(标…...
vue2使用ezuikit-js播放萤石视频
需求:需要在大屏上播放萤石视频,用到官方的ezuikit-js插件实现,并实现视频播放切换功能。有个问题至今没有解决,就是萤石视频的宽高是固定的,不会根据大屏缩放进行自适应。我这边做了简单的刷新自适应。 1.下载ezuikit…...
什么是Lodash
一、什么是lodash 在 JavaScript 开发中,Lodash 是一个非常受欢迎的实用工具库,旨在提供高效、模块化的实用函数,帮助开发者更轻松地处理数组、对象、字符串等常见数据结构。它是对 JavaScript 原生方法的增强和优化,它在开发中提…...
【笔试强训day19】
目录 第一题:小易的升级之路 描述 输入描述: 输出描述: 输入: 输出: 第二题:礼物的最大价值 描述 输入: 返回值: 备注: 第三题:对称之美 题目描述…...
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设备驱动与应用程序开发
本文章相关专栏往期内容,SPI子系统专栏: SPI通信协议与Linux设备驱动框架解析SPI传输与驱动框架的实现spidev.c:SPI设备驱动的核心实现逻辑 PCI/PCIe子系统专栏: 专栏地址:PCI/PCIe子系统PCIe设备MSI/MSI-X中断源码分析…...
第十六届蓝桥杯大赛软件赛省赛 Python 大学 B 组 满分题解
题面链接Htlang/2025lqb_python_b 个人觉得今年这套题整体比往年要简单许多,但是G题想简单了出大问题,预估50101015120860,道阻且长,再接再厉 代码仅供学习参考,满分为赛后洛谷中的测评,蓝桥杯官方测评待…...
数据库的种类及常见类型
一,数据库的种类 最常见的数据库类型分为两种,关系型数据库和非关系型数据库。 二,关系型数据库介绍 生产环境主流的关系型数据库有 Oracle、SQL Server、MySQL/MariaDB等。 关系型数据库在存储数据时实际就是采用的一张二维表࿰…...
vue文件预览docx-preview
1、在项目中引入插件docx-preview npm i docx-preview 此插件依赖jszip,所以还要下载jszip:npm i jszip 2、点击在线预览按钮请求接口获取文件流 const blob new Blob([resp.data]) const url URL.createObjectURL(blob);//浏览器本地存储不能直接…...
旧版 VMware 虚拟机迁移至 KVM 平台-案例2
项目背景 需将一台旧版 VMware 虚拟机(VMDK 格式)迁移至 KVM 虚拟化平台,具体要求如下: 格式转换:将 VMDK 转换为 QCOW2 格式。磁盘扩容:将原 40GB 磁盘扩展至 60GB。密码重置:修改 aiden 用户…...
若依代码生成器原理velocity模板引擎(自用)
1.源码分析 代码生成器:导入表结构(预览、编辑、删除、同步)、生成前后端代码 代码生成器表结构说明: 若依提供了两张核心表来存储导入的业务表信息: gen_table:存储业务表的基本信息 ,它对应于配置代码基本信息和生成信息的页…...
OpenCV直方图均衡化全面解析:从灰度到彩色图像的增强技术
目录 一、直方图均衡化基础:原理与核心思想 二、彩色图像的直方图均衡化:挑战与解决方案 三、进阶技巧与注意事项 四、应用场景与典型案 一、直方图均衡化基础:原理与核心思想 1. 直方图的本质与作用 直方图是图像像素强度分布的统计图表…...
【Pandas】pandas DataFrame keys
Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签(行标签和列标签&#…...
Web前端之Vue+Element实现表格动态复杂的合并行功能、localeCompare、forEach、table、push、sort、Map
MENU 效果图公共数据数据未排序时(需要合并的行数据未处于相邻位置)固定合并行方法(写死)动态合并行,行数计算方法当太合并行,合并方法方法(函数)执行 效果图 公共数据 Html <e…...
