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…...
避坑指南:注册个体户时,经营范围怎么选才不影响以后开票和接项目?
技术创业者必读:个体户经营范围选择的战略与实操指南 在数字经济蓬勃发展的今天,越来越多的技术从业者选择以个体户形式开启创业之路。作为企业合法经营的"身份证",营业执照中经营范围的填写看似简单,实则暗藏玄机。一个…...
AI 写作一键生成超简单,焦圈儿免费积分福利等你来领
「现在写一篇公众号推文,没三四个小时都下不来。」一位做个人 IP 的朋友跟我抱怨。问题不在于工具太少,而在于门槛太高, 要么你得自己熬夜改稿,要么你得学一堆复杂 Prompt,才能把 AI 伺候好。内容行业正在进入一个悖论…...
期货合约乘数与最小变动价位:从 Quote 读规格做下单预算
前言 写天勤量化下单逻辑时,若手数、保证金和盈亏对不上账,我一般会先查合约规格有没有读错。乘数、最小变动价位(一跳)、涨跌停价都在 Quote 里,用统一字段做预算,比手算或硬编码合约表更不容易在换月后踩…...
手机店还会存在吗
这两年买手机,有个很常见的小场景:人先进店,把样机拿起来拍几张照片,摸一下边框,试试重量,再问店员有没有现货。问完价格以后,很多人会低头打开电商平台。 门店最尴尬的地方就在这里。它承担了体…...
百度网盘直链解析终极指南:3分钟实现高速下载的完整教程
百度网盘直链解析终极指南:3分钟实现高速下载的完整教程 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 还在为百度网盘下载速度慢而烦恼吗?每次下载大…...
告别GDB依赖:在NEMU里打造专属调试器,我是如何搞定单步执行与内存扫描的
从零构建教学级调试器:NEMU Monitor模块深度解析与实践指南 在计算机系统与体系结构的学习过程中,调试器如同探索程序执行奥秘的显微镜。传统调试工具如GDB虽然功能强大,但其内部工作机制对初学者而言却如同黑箱。本文将带您深入NEMU模拟器的…...
Perplexity+知网联合检索的7类失效场景全图谱:从DOI解析失败到CSSCI标识丢失的终极修复手册
更多请点击: https://intelliparadigm.com 第一章:Perplexity知网联合检索的失效机理总论 当用户尝试将 Perplexity AI 的实时网络推理能力与知网(CNKI)学术资源库进行协同调用时,系统级耦合在协议层、语义层与权限层…...
从KITTI的pkl文件到模型输入:OpenPCDet数据流水线内部运作全揭秘
从KITTI的pkl文件到模型输入:OpenPCDet数据流水线内部运作全揭秘 在3D目标检测领域,KITTI数据集作为行业标杆,其数据处理流程的复杂性往往成为算法落地的第一道门槛。OpenPCDet框架通过精心设计的预处理系统,将原始传感器数据转化…...
MySQL 8.3远程连接踩坑记:Navicat提示caching_sha2_password错误的完整修复流程
MySQL 8.3远程连接认证插件问题深度解析与实战修复指南 1. 问题现象与背景分析 那天下午,当我正尝试用Navicat Premium 16连接新部署的MySQL 8.3数据库时,屏幕上突然弹出的红色错误框让我的咖啡杯悬在了半空: Authentication plugin caching_…...
企业号码认证服务:实现座机、手机来电显示公司名称+品牌LOGO
在如今的商业环境下,一通没有身份标识的电话,想要敲开客户的大门已经变得越来越难。反诈意识的普及,让人们对陌生呼叫筑起了厚厚的防御墙。许多企业在开展客户回访、售后跟进或业务接洽时,频繁遭遇拒接、秒挂的窘境。投入了大笔的…...
