EasyExcel模板导出(行和列自动合并)

2023-12-20 12:15

本文主要是介绍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模板导出(行和列自动合并)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/516007

相关文章

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

不删数据还能合并磁盘? 让电脑C盘D盘合并并保留数据的技巧

《不删数据还能合并磁盘?让电脑C盘D盘合并并保留数据的技巧》在Windows操作系统中,合并C盘和D盘是一个相对复杂的任务,尤其是当你不希望删除其中的数据时,幸运的是,有几种方法可以实现这一目标且在... 在电脑生产时,制造商常为C盘分配较小的磁盘空间,以确保软件在运行过程中不会出现磁盘空间不足的问题。但在

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

java poi实现Excel多级表头导出方式(多级表头,复杂表头)

《javapoi实现Excel多级表头导出方式(多级表头,复杂表头)》文章介绍了使用javapoi库实现Excel多级表头导出的方法,通过主代码、合并单元格、设置表头单元格宽度、填充数据、web下载... 目录Java poi实现Excel多级表头导出(多级表头,复杂表头)上代码1.主代码2.合并单元格3.

使用 Python 和 LabelMe 实现图片验证码的自动标注功能

《使用Python和LabelMe实现图片验证码的自动标注功能》文章介绍了如何使用Python和LabelMe自动标注图片验证码,主要步骤包括图像预处理、OCR识别和生成标注文件,通过结合Pa... 目录使用 python 和 LabelMe 实现图片验证码的自动标注环境准备必备工具安装依赖实现自动标注核心

QT实现TCP客户端自动连接

《QT实现TCP客户端自动连接》这篇文章主要为大家详细介绍了QT中一个TCP客户端自动连接的测试模型,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录版本 1:没有取消按钮 测试效果测试代码版本 2:有取消按钮测试效果测试代码版本 1:没有取消按钮 测试效果缺陷:无法手动停

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que