EasyExcel模板导出(行和列自动合并)
1.需求背景:
①需要从第三方获取数据,第三方接口有两个参数,开始时间和结束时间
②获取回来的数据并没有入库,所以不能通过数据库将数据归类统计,excel合并大概的流程是判断上一行或者左右相邻列是否相同,然后进行合并,所以不能是零散的数据且客户要求每一个自治区和每一个航站要统计总数(后续会出一个数据整合文章),咱们默认数据已经整理好了.效果如下:

③最终效果:

2.初步实现:
①利用easyExcel模板填充,实现效果如下图

代码:
//模板位置InputStream template = new PathMatchingResourcePatternResolver().getResource("templates/飞机扑救火场统计表.xlsx").getInputStream();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码response.setHeader("Content-Disposition","attachment;filename=" + java.net.URLEncoder.encode("飞机扑救火场统计表.xlsx", "UTF-8"));//ExcelWriter该对象用于通过POI将值写入ExcelExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(template).build();//构建excel的sheet WriteSheet writeSheet = EasyExcel.writerSheet().build();Map<String, String> fileData = new HashMap<>();fileData.put("beginDate", beginDate);fileData.put("endDate", endDate);excelWriter.fill(list, writeSheet);excelWriter.fill(fileData, writeSheet);excelWriter.finish();
模板:

3.列合并
列合并工具类,合并代码在afterCellDispose这个方法中,不管是列合并还是行合并其实是重写这个方法,将你的合并逻辑写在里面就可以
//列合并工具类
public class ExcelFillCellMergePrevColUtils implements CellWriteHandler {private static final String KEY ="%s-%s";//所有的合并信息都存在了这个map里面Map<String, Integer> mergeInfo = new HashMap<>();public ExcelFillCellMergePrevColUtils() {}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {//当前行int curRowIndex = cell.getRowIndex();//当前列int curColIndex = cell.getColumnIndex();Integer num = mergeInfo.get(String.format(KEY, curRowIndex, curColIndex));if(null != num){// 合并最后一行 ,列mergeWithPrevCol(writeSheetHolder, cell, curRowIndex, curColIndex,num);}}public void mergeWithPrevCol(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex, int num) {Sheet sheet = writeSheetHolder.getSheet();CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex, curRowIndex, curColIndex, curColIndex + num);sheet.addMergedRegion(cellRangeAddress);}//num从第几列开始增加多少列,(6,2,7)代表的意思就是第6行的第2列至第2+7也就是9列开始合并public void add (int curRowIndex, int curColIndex , int num){mergeInfo.put(String.format(KEY, curRowIndex, curColIndex),num);}}


可以参考下面的这个excel看一下,广西壮族自治区的航站合计是从第8行,第2列开始+2列的范围合并
列合并效果图:

4.行合并
行合并工具类初级版本:
报错位置:ExcelFillCellMergeStrategyUtils合并策略类的 mergeWithPrevRow()方法中
这一行代码会报空指针异常 java.lang.NullPointerException
Row preRow = cell.getSheet().getRow(curRowIndex - 1);

原因:
debug发现,cell.getSheet() 行的下标第0到42的数据行,获取的是同一个 sheet 实例
当下标为43时,执行cell.getSheet()获取到的 sheet 实例不一样
而下标0到42的行数据被存储到 存储sheet中。如果上一行为空则去缓存中获取上一行,
writeSheetHolder.getCachedSheet()
Row preRow = cell.getSheet().getRow(curRowIndex - 1);if (preRow == null) {// 当获取不到上一行数据时,使用缓存sheet中数据preRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);}Cell preCell=preRow.getCell(curColIndex);
行合并工具类最终版:
public class ExcelFillCellMergeStrategyUtils implements CellWriteHandler {/*** 合并字段的下标*/private int[] mergeColumnIndex;/*** 合并几行*/private int mergeRowIndex;public ExcelFillCellMergeStrategyUtils(int mergeRowIndex, int[] mergeColumnIndex) {this.mergeRowIndex = mergeRowIndex;this.mergeColumnIndex = mergeColumnIndex;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,Head head, Integer integer, Integer integer1, Boolean aBoolean) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {//当前行int curRowIndex = cell.getRowIndex();//当前列int curColIndex = cell.getColumnIndex();if (curRowIndex > mergeRowIndex) {for (int i = 0; i < mergeColumnIndex.length; i++) {if (curColIndex == mergeColumnIndex[i]) {mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);break;}}}}private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() :cell.getNumericCellValue();Row preRow = cell.getSheet().getRow(curRowIndex - 1);if (preRow == null) {// 当获取不到上一行数据时,使用缓存sheet中数据preRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);}Cell preCell=preRow.getCell(curColIndex);Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() :preCell.getNumericCellValue();// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行if (curData.equals(preData)) {Sheet sheet = writeSheetHolder.getSheet();List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();boolean isMerged = false;for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {CellRangeAddress cellRangeAddr = mergeRegions.get(i);// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {sheet.removeMergedRegion(i);cellRangeAddr.setLastRow(curRowIndex);sheet.addMergedRegion(cellRangeAddr);isMerged = true;}}// 若上一个单元格未被合并,则新增合并单元if (!isMerged) {CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,curColIndex);sheet.addMergedRegion(cellRangeAddress);}}}
}
5.excel字体样式内容居中
样式工具类:
public class CellStyleStrategy extends AbstractCellStyleStrategy {private WriteCellStyle headWriteCellStyle;private List<WriteCellStyle> contentWriteCellStyleList;private CellStyle headCellStyle;private List<CellStyle> contentCellStyleList;public CellStyleStrategy(WriteCellStyle headWriteCellStyle,List<WriteCellStyle> contentWriteCellStyleList) {this.headWriteCellStyle = headWriteCellStyle;this.contentWriteCellStyleList = contentWriteCellStyleList;}public CellStyleStrategy(WriteCellStyle headWriteCellStyle, WriteCellStyle contentWriteCellStyle) {this.headWriteCellStyle = headWriteCellStyle;contentWriteCellStyleList = new ArrayList<WriteCellStyle>();contentWriteCellStyleList.add(contentWriteCellStyle);}@Overrideprotected void initCellStyle(Workbook workbook) {if (headWriteCellStyle != null) {headCellStyle = StyleUtil.buildHeadCellStyle(workbook, headWriteCellStyle);}if (contentWriteCellStyleList != null && !contentWriteCellStyleList.isEmpty()) {contentCellStyleList = new ArrayList<CellStyle>();for (WriteCellStyle writeCellStyle : contentWriteCellStyleList) {contentCellStyleList.add(StyleUtil.buildContentCellStyle(workbook, writeCellStyle));}}}@Overrideprotected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {if (headCellStyle == null) {return;}cell.setCellStyle(headCellStyle);}@Overrideprotected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {if (contentCellStyleList == null || contentCellStyleList.isEmpty()) {return;}cell.setCellStyle(contentCellStyleList.get(0));}}
public CellStyleStrategy horizontalCellStyleStrategyBuilder() {WriteCellStyle headWriteCellStyle = new WriteCellStyle();//设置头字体WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short) 13);headWriteFont.setBold(true);headWriteCellStyle.setWriteFont(headWriteFont);//设置头居中headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//内容策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();//设置 水平居中contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//垂直居中contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);return new CellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);}
6.将三个工具类初始化后注册后最终代码:
public void exportExcel(HttpServletResponse response, String beginDate, String endDate) throws IOException {ExcelFillCellMergePrevColUtils excelFillCellMergePrevColUtils = new ExcelFillCellMergePrevColUtils();String terminalTotal = "航站合计";String provinceTotal = "省区合计";Map<String, ProvinceInfo> map = handlePlaneDownFire(beginDate, endDate);List<PlaneDownFire> list = new ArrayList<>();map.forEach((k, v) -> {//添加航站合计v.getTerminalInfos().forEach((k1, v1) -> {list.addAll(v1.getList());int size = list.size();excelFillCellMergePrevColUtils.add(size + 3, 2, 2);CommissionInfo terminalCommissionInfoTotal = v1.getSum();PlaneDownFire planeDownFire = CommissionInfoConvert.INSTANCE.commissionInfo2planeDownFire(terminalCommissionInfoTotal);planeDownFire.setProvincialArea(v.getName()).setMachineNumber(String.valueOf(size)).setFlyingCommission(String.valueOf(size)).setTerminal(v1.getName()).setModel(terminalTotal);list.add(planeDownFire);});int size = list.size();excelFillCellMergePrevColUtils.add(size + 3, 1, 3);//省区合计CommissionInfo provinceCommissionInfoTotal = v.getSum();PlaneDownFire planeDownFire = CommissionInfoConvert.INSTANCE.commissionInfo2planeDownFire(provinceCommissionInfoTotal);planeDownFire.setProvincialArea(v.getName()).setTerminal(provinceTotal).setModel(String.valueOf(size));list.add(planeDownFire);});//设置第几列开始合并int[] mergeColumnIndex = {0, 1, 2, 3};//设置第几行开始合并int mergeRowIndex = 3;ExcelFillCellMergeStrategyUtils excelFillCellMergeStrategyUtils = new ExcelFillCellMergeStrategyUtils(mergeRowIndex, mergeColumnIndex);InputStream template = new PathMatchingResourcePatternResolver().getResource("templates/飞机扑救火场统计表.xlsx").getInputStream();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码response.setHeader("Content-Disposition","attachment;filename=" + java.net.URLEncoder.encode("飞机扑救火场统计表.xlsx", "UTF-8"));//ExcelWriter该对象用于通过POI将值写入ExcelExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(template)//样式注册.registerWriteHandler(horizontalCellStyleStrategyBuilder())//行注册.registerWriteHandler(excelFillCellMergeStrategyUtils)//列注册.registerWriteHandler(excelFillCellMergePrevColUtils).build();//构建excel的sheetWriteSheet writeSheet = EasyExcel.writerSheet().build();Map<String, String> fileData = new HashMap<>();fileData.put("beginDate", beginDate);fileData.put("endDate", endDate);excelWriter.fill(fileData, writeSheet);excelWriter.fill(list, writeSheet);excelWriter.finish();}
总结:EasyExcel动态导出几乎能够满足大部分需求,说到底还是实现CellWriteHandler 类里面的
相关文章:
EasyExcel模板导出(行和列自动合并)
1.需求背景: ①需要从第三方获取数据,第三方接口有两个参数,开始时间和结束时间 ②获取回来的数据并没有入库,所以不能通过数据库将数据归类统计,excel合并大概的流程是判断上一行或者左右相邻列是否相同,然后进行合并,所以不能是零散的数据且客户要求每一个自治区和每一个航站…...
EOCR-i3MZ/iFMZ施耐德漏电保护继电器产品简介
EOCR-i3MZ/iFMZ是施耐德EOCR的新一代电子式电动机保护器产品,具有过电流、欠电流、缺相、逆相、堵转、失速、三相不平衡、接地等保护功能。EOCR-i3MZ/iFMZ是通讯型产品,提供Modbus RTU通讯协议,RS485接口。 为方便设备维护人员排查电动机的故…...
golang开发--beego入门
Beego 是一个基于 Go 语言的开源框架,用于构建 Web 应用程序和 API。它采用了一些常见的设计模式,以提高开发效率、代码可维护性和可扩展性。 一,MVC设计模式 Beego 框架采用了经典的 MVC(Model-View-Controller)设计…...
python调取一欧易API并写一个比特币均线交易策略
比特币均线交易策略是一种基于比特币价格的移动均线的交易策略。它通过计算不同时间段的移动均线来确定买入和卖出点。 具体步骤如下: 确定要使用的均线。常用的均线包括5日、10日、20日、50日和200日均线。较短的均线可以更快地反应价格变动,而较长的均…...
使用arthas排查请求超时问题
现象 客户端调用服务时间出现偶尔超时现象 排查 因为服务已开启arthas,使用trace命令监控 $ trace com.lizz slowfun #cost > 1000 -n 10 监控com.lizz类中的slowfun方法,输出用时超过1000ms的记录,记录10条 Press CtrlC to abort. Aff…...
SAP ABAP EXCEL 下载模板并导入
具体参考: ABAP EXCEL 下载摸板 获取数据模板文件路径 FORM fm_get_filepath .DATA: lv_filename TYPE string,lv_path TYPE string,lv_fullpath TYPE string,lv_title TYPE string.co_objid ZMMRP002.CONCATENATE co_objid - sy-datum sy-uzeit INTO l…...
Map集合体系
Map集合的概述 Map集合是一种双列集合,每个元素包含两个数据。 Map集合的每个元素的格式:keyvalue(键值对元素)。 Map集合也被称为“键值对集合”。 Map集合的完整格式:{key1value1 , key2value2 , key3value3 , ...} Map集合的使用场景…...
速度与稳定性的完美结合:深入横测ToDesk、TeamViewer和AnyDesk
文章目录 前言什么是远程办公?远程办公的优势 远程办公软件横测对象远程软件的注册&安装ToDeskTeamViewerAnyDesk 各场景下的实操体验1.办公文件传输及丢包率2.玩游戏操作延迟、稳定3.追剧画质流畅度、稳定4.临时技术支持SOS模式 收费情况与设备连接数总结 前言…...
数据库系统的结构
数据库系统的结构 1 数据抽象1.1 物理层1.2 逻辑层1.3 视图层 2 实例和模式3 数据独立性4 数据模型4.1 基于对象的逻辑模型4.2 基于记录的逻辑模型4.3 基于记录的物理模型 5 数据库语言5.1 数据定义语言 DDL5.2 数据操纵语言 DML 6 事务7 存储管理器8 数据库系统的总体结构 1 数…...
ngrok编译
ngrok编译 安装golang 官方golang安装文档:https://golang.google.cn/doc/install 配置国内源 go env -w GOPROXYhttps://goproxy.cn,direct关掉GO111MODULE go env -w GO111MODULEoff 配置访问github proxy_host$1 # 192.168.126.173 proxy_port$1 # 7890 exp…...
YOLOv5改进 | 卷积篇 | 通过RFAConv重塑空间注意力(深度学习的前沿突破)
一、本文介绍 本文给大家带来的改进机制是RFAConv,全称为Receptive-Field Attention Convolution,是一种全新的空间注意力机制。与传统的空间注意力方法相比,RFAConv能够更有效地处理图像中的细节和复杂模式(适用于所有的检测对象都有一定的…...
056:vue工具 --- CSS在线格式化
第056个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下,本专栏提供行之有效的源代码示例和信息点介绍,做到灵活运用。 (1)提供vue2的一些基本操作:安装、引用,模板使…...
自定义IDEA代码补全插件
目标: 对于项目中的静态方法(主要是各种工具类里的静态方法),可以在输入方法名时直接提示相关的静态方法,选中后自动补全代码,并导入静态类。 设计: 初步构想,用户选择要导入的文…...
uniapp uview1.0 页面多个upload上传、回显之后处理数据
<view class"img-title w-s-color-3 f-28 row">商品图片</view><u-upload ref"images" :header"header" :file-list"fileListImages" :action"action" name"iFile" icon-name"camera"u…...
生活中的物理2——人类迷惑行为(用笔扎手)
1实验 材料 笔、手 实验 1、先用手轻轻碰一下笔尖(未成年人须家长监护) 2、再用另一只手碰碰笔尾 你发现了什么?? 2发现 你会发现碰笔尖的手明显比碰笔尾的手更痛 你想想为什么 3原理 压强f/s 笔尖的面积明显比笔尾的小 …...
vue3表格导入导出.xlsx
在这次使用时恰好整出来了,希望大家也能学习到,特此分享出来 使用前确保安装以下模块,最好全局配置element-plus ### 展示一下 ### ###导出选项 ### ###导入de数据 ### 安装的模块 npm install js-table2excel // 安装js-table2excel n…...
vscode dart语言出现蓝色波浪线
pubspec.yaml 注释掉:flutter_lints: ^2.0.0 analysis_options.yaml 注释掉:include: package:flutter_lints/flutter.yaml...
一种磁盘上循环覆盖文件策略
目录标题 1. 前言2. 软件设计流程思路3. 模拟测试3.1 分区准备工作3.2 模拟写数据3.3 测试 1. 前言 实际开发中经常需要存储数据, 无论是存储日志,还是二进制数据(图片,雷达数据或视频文件等), 不能一直存,是否存在一种策略: 当磁盘空间不足时…...
elementui消息弹出框MessageBox英文内容不换行问题
问题:当MessageBox内容为中文时,会自动换行,但当内容为英文时不会触发自动换行 如图,内容名称为英文时,名称太长会戳出提示框,不会自动换行 为数字英文会在英文数字处换行但是我们往往不需要它换行 解决方…...
WPF——样式和控件模板、数据绑定与校验转换
样式和控件模板 合并资源字典 Style简单样式的定义和使用 ControlTemplate控件模板的定义和使用 定义 使用 Trigger触发器 数据绑定与校验转换 数据绑定的设置 代码层实现绑定 数据模板DataTemplate xml文件的读取与显示 方法的返回值作为源绑定到控件中ObjectDataProvider L…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
PydanticAI快速入门示例
参考链接:https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...
