excel 动态表头与合并列

零、希望Springboot-java导出excel文件,包括动态表头与下边合并的列

使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下

一、代码

1、依赖

    implementation(group: 'org.apache.poi',name: 'poi-ooxml',version: '4.1.0')implementation(group: 'org.apache.poi',name: 'poi',version: '4.1.0')implementation(group: 'cn.hutool', name: 'hutool-all', version: '5.8.3')

2、工具类 ExcelMergeUtil.java


import cn.hutool.json.JSONUtil;
import com.longze.fengqx.HeaderNode;
import com.longze.fengqx.PoiModel;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;/*** @author Fengqx* @version 1.0* @description: excel文件合并* @date 2023/8/20 13:13*/
public class ExcelMergeUtil {public static SXSSFSheet createExcelHead(SXSSFWorkbook book, String sheetName, String headJson){List<HeaderNode> headerNodes = JSONUtil.toList(headJson, HeaderNode.class);SXSSFSheet sxssfSheet = book.createSheet(sheetName);CellStyle headStyle = book.createCellStyle();defaultHeadStyle(headStyle);//表头层级int deep = headerNodes.stream().map(HeaderNode::getRow).reduce(Integer::max).orElse(1);for (int i = 0; i < deep; i++) {sxssfSheet.createRow(i);}//创建单元格for (HeaderNode headerNode : headerNodes) {int row = headerNode.getRow();int col = headerNode.getColumn();SXSSFCell sxssfCell = sxssfSheet.getRow(row).createCell(col);sxssfSheet.setColumnWidth(col, headerNode.getWidth() * 256);sxssfCell.setCellStyle(headStyle);sxssfCell.setCellValue(headerNode.getHeaderName());CellRangeAddress region;//是否跨列if (headerNode.isOverNode()) {region = new CellRangeAddress(row, deep, col, col);} else {region = new CellRangeAddress(row, row, col, (col + headerNode.getOverNodeCount() - 1));}if (region.getNumberOfCells() > 1) {sxssfSheet.addMergedRegionUnsafe(region);//合并后设置下边框RegionUtil.setBorderTop(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderLeft(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderBottom(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderRight(BorderStyle.THIN, region, sxssfSheet);}}return sxssfSheet;}public static void mergeCellFunc(Sheet sheet, String[] title, String[] field, List<Map<String, String>> list, Integer deep,List<Integer> mergeIndex){Map<String, List<Map<String, String>>> map = Maps.newHashMap();map.put("测试合并数据", list);// 模拟大数据量情况下,任务中心可分页查询接口,分批返回数据List<List<Map<String, String>>> listList =excelPageByNum(list, 5);// 数据总数 + 表头int size = listList.stream().mapToInt(List::size).sum() + deep;List<PoiModel> poiModels = Lists.newArrayList();for (List<Map<String, String>> listmid : listList) {for (Map<String, String> mapMid : listmid) {int index = sheet.getLastRowNum()+1;Row row = sheet.createRow(index);for (int i = 0; i < title.length; i++) {String titleField = field[i];String old = null;if (index > deep+1) {old = poiModels.get(i) == null ? null : poiModels.get(i).getContent();}for (int k : mergeIndex) {if (index == deep+1) {PoiModel poiModel =new PoiModel(mapMid.get(titleField),mapMid.get(titleField),null,deep+1,i);poiModels.add(poiModel);break;}PoiModel poiModel = poiModels.get(i);String content = mapMid.get(titleField);// 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并if (i > 0 && k == i) {// 如果不需要考虑当前行与上一行内容相同,但是它们的前一列内容不一样则不合并的情况,把或条件删除if (!StringUtils.equalsIgnoreCase(content, poiModel.getContent())
//                                    || (StringUtils.equalsIgnoreCase(content, poiModel.getContent()) && !StringUtils.equalsIgnoreCase(poiModels.get(i - 1).getOldContent(),mapMid.get(field[i - 1])))) {get(poiModel, content, index, i, sheet);}}// 处理第一列的情况if (k == i && i == 0 && !StringUtils.equalsIgnoreCase(content,poiModel.getContent())) {get(poiModel, content, index, i, sheet);}// 最后一行没有后续的行与之比较,所有当到最后一行时则直接合并对应列的相同内容if (k == i && index == size && poiModels.get(i).getRowIndex() != index) {CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(), index, poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());sheet.addMergedRegion(cra);}}Cell cell = row.createCell(i);cell.setCellValue(mapMid.get(titleField));poiModels.get(i).setOldContent(old);}}}}/*** 表头样式** @param headStyle*/private static void defaultHeadStyle(CellStyle headStyle) {headStyle.setBorderTop(BorderStyle.THIN);headStyle.setBorderLeft(BorderStyle.THIN);headStyle.setBorderBottom(BorderStyle.THIN);headStyle.setBorderRight(BorderStyle.THIN);headStyle.setAlignment(HorizontalAlignment.CENTER);headStyle.setVerticalAlignment(VerticalAlignment.CENTER);headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);headStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());}//合并单元格private static void get(PoiModel poiModel, String content, int index, int i, Sheet sheet) {if (poiModel.getRowIndex() != index - 1) {CellRangeAddress cra = new CellRangeAddress(poiModel.getRowIndex(), index - 1, poiModel.getCellIndex(), poiModel.getCellIndex());//在sheet里增加合并单元格sheet.addMergedRegion(cra);}/*重新记录该列的内容为当前内容,行标记改为当前行标记,列标记则为当前列*/poiModel.setContent(content);poiModel.setRowIndex(index);poiModel.setCellIndex(i);}public static <T> List<List<T>> excelPageByNum(List<T> list, int pageSize) {return IntStream.range(0, list.size()).boxed().filter(t -> t % pageSize == 0).map(t -> list.stream().skip(t).limit(pageSize).collect(Collectors.toList())).collect(Collectors.toList());}
}

3、实体对象

HeaderNode.java  和 PoiModel.java

public class PoiModel {private String content;private String oldContent;private String primaryKey;private int rowIndex;private int cellIndex;public PoiModel() {}public PoiModel(String content, String oldContent, String primaryKey, int rowIndex, int cellIndex) {this.content = content;this.oldContent = oldContent;this.primaryKey = primaryKey;this.rowIndex = rowIndex;this.cellIndex = cellIndex;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getOldContent() {return oldContent;}public void setOldContent(String oldContent) {this.oldContent = oldContent;}public String getPrimaryKey() {return primaryKey;}public void setPrimaryKey(String primaryKey) {this.primaryKey = primaryKey;}public int getRowIndex() {return rowIndex;}public void setRowIndex(int rowIndex) {this.rowIndex = rowIndex;}public int getCellIndex() {return cellIndex;}public void setCellIndex(int cellIndex) {this.cellIndex = cellIndex;}
}public class HeaderNode {/*** 标题头*/private String headerName;/*** 层级*/private int row;/*** 非叶子节点列跨度*/private int overNodeCount;/*** 当前列没有子节点*/private boolean overNode = true;/*** 列*/private int column;/*** 宽度*/private int width = 13;public String getHeaderName() {return headerName;}public void setHeaderName(String headerName) {this.headerName = headerName;}public int getRow() {return row;}public void setRow(int row) {this.row = row;}public int getOverNodeCount() {return overNodeCount;}public void setOverNodeCount(int overNodeCount) {this.overNodeCount = overNodeCount;}public boolean isOverNode() {return overNode;}public void setOverNode(boolean overNode) {this.overNode = overNode;}public int getColumn() {return column;}public void setColumn(int column) {this.column = column;}public int getWidth() {return width;}public void setWidth(int width) {this.width = width;}
}

4、下载Controller

 @GetMapping(value = "/downExcel")@ResponseBodypublic void downExcel(HttpServletResponse response,@RequestParam(required = true) String type) throws Exception {try {tengxunService.downExcel(response, type);} catch (Exception ex) {throw ex;}}

5、下载service

  @Overridepublic void downloadExcel(HttpServletResponse response, String type)throws Exception {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码String fileName = URLEncoder.encode("腾讯充值文件", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");OutputStream os = response.getOutputStream();
//加工数据List<Map<String, String>> list = Lists.newArrayList();for(int i=0;i<chongzhiList.size();i++){Chongzhi dto=new Chongzhi ();list.add(JSONObject.parseObject(JSON.toJSONString(dto),Map.class));}String weekStart ="08.01";String weekEnd ="08.07";String nextWeekStart = "08.08";String nextWeekEnd ="08.15";//合并单元格方法try {String customizeLabel = "[{\"headerName\":\"区域\",\"column\":0,\"row\":0}," +"{\"headerName\":\"用户姓名\",\"column\":1,\"row\":0}," +"{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值记录\",\"column\":2,\"row\":0}," +"{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值详情\",\"column\":3,\"row\":0,\"overNodeCount\":4,\"overNode\":false},{\"headerName\":\"充值时间\",\"column\":3,\"row\":1}," +"{\"headerName\":\"充值项目\",\"column\":4,\"row\":\"1\"}," +"{\"headerName\":\"充值方式\",\"column\":5,\"row\":1}," +"{\"headerName\":\"充值金额\",\"column\":6,\"row\":1}," +"{\"headerName\":\"下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划\",\"column\":7,\"row\":0}]";//1、生bookSXSSFWorkbook book = new SXSSFWorkbook();//2、生成动态标题SXSSFSheet sxssfSheet = ExcelMerge.createExcelHead(book,"Sheet1", customizeLabel);//3、取数据对应字段值  汇总String[] title = {"区域", "用户姓名", "本周("+weekStart+"-"+weekEnd+")充值记录", "充值时间", "充值项目", "充值方式", "充值金额", "下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划"};//4、取数据对应属性的字段值  汇总String[] field = {"areaName", "name", "chongzjilu", "chongzTime", "chongzProject", "chongzMethod", "chongzMoney", "nextChongz"};//5、需要合并的列List<Integer> mergeIndex = Arrays.asList(0,1,7);//6、合并ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);//创建excel文件 下载book.write(os);} catch (IOException e){logger.error("文件导出失败,错误信息{}",e);// 重置responseresponse.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");Map<String, String> map = new HashMap<>();map.put("statusCode", "500");map.put("message", "下载文件失败" + e.getMessage());response.getWriter().println(JSON.toJSONString(map));}finally {try {os.close();} catch (IOException e) {e.printStackTrace();}}}

三、下载

完事通过controller调用下载接口,直接可以下载出文件

可以任意改表头,与选择是否要合并的字段,当做参数传入,将需要合并的列顺序传入即可完成合并,一步到位,十分方便

//5、需要合并的列  
List<Integer> mergeIndex = Arrays.asList(0,1,7);
ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);

截图如下

荆轲刺秦王!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Cortex-M3权威指南】学习笔记2 - 指令集

目录 指令集汇编语言基础UAL 近距离检视指令数据传输数据处理子程呼叫与无条件跳转指令标志位与条件转移指令隔离指令饱和运算 CM3 中新引入指令MRS\MSRIF-THENCBZ/CBNZSDIV/UDIVREV RBITSXTBTBB,TBH 指令集 汇编语言基础 一条简单的汇编指令格式&#xff08;注释使用一个分号…

4.12 TCP 连接,一端断电和进程崩溃有什么区别?

目录 TCP keepalive TCP 的保活机制 主机崩溃 进程崩溃 有数据传输的场景 客户端主机宕机&#xff0c;又迅速重启 客户端主机宕机&#xff0c;一直没有重启 TCP连接服务器宕机和进程退出情况总结 TCP keepalive TCP 的保活机制 TCP 保活机制需要通过 socket 接口设置 S…

【Unity小技巧】unity2d平台制作一根三七调的鱼竿效果(附git源码)

文章目录 前言素材开始源码参考完结 前言 今天我们做一个2d鱼竿的效果&#xff0c;先看一下效果成品效果 素材 鱼竿 开始 首先创建一个2D URP项目 &#xff0c;创建一个空物体作为鱼竿&#xff0c;并创建两个子物体作为开始和结束点 配置层级如下 鱼竿和鱼线加Line Ren…

k8s之工作负载、Deployment、DaemonSet、StatefulSet、Job、CronJob及GC

文章目录 1、工作负载1.1、定义1.2、分类 2、Deployment2.1、定义2.2、Deployment创建2.3、Deployment 更新机制2.3.1、比例缩放&#xff08;Proportional Scaling&#xff09;2.3.2、HPA&#xff08;动态扩缩容&#xff09;2.3.2.1、需要先安装metrics-server2.3.2.2、配置hpa…

JavaScript(笔记)

目录 Hello World JavaScript 的变量 JavaScript 动态类型 隐式类型转换 JavaScript 数组 JavaScript 函数 JavaScript 中变量的作用域 对象 DOM 选中页面元素 事件 获取 / 修改元素内容 获取 / 修改元素属性 获取 / 修改 表单元素属性 获取 / 修改样式属性 新…

<C++> 内存管理

1.C/C内存分布 让我们先来看看下面这段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char *pChar3 "abcd";int *ptr1 (int *) mal…

3D姿态相关的损失函数

loss_mpjpe: 计算预测3D关键点与真值之间的平均距离误差(MPJPE)。 loss_n_mpjpe: 计算去除尺度后预测3D关键点误差(N-MPJPE),评估结构误差。 loss_velocity: 计算3D关键点的速度/移动的误差,评估运动的平滑程度。 loss_limb_var: 计算肢体长度的方差,引导生成合理的肢体长度…

vue 简单实验 v-model 变量和htm值双向绑定

1.代码 <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"two-way-binding"><p>{{ message }}</p><input v-model"message" /> </div> <script>…

Django REST framework实现api接口

drf 是Django REST framework的简称&#xff0c;drf 是基于django的一个api 接口实现框架&#xff0c;REST是接口设计的一种风格。 一、 安装drf pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install …

Java 时间日期处理,工作必用(建议收藏)

工作中经常会遇到对时间日期进行处理的业务&#xff0c;像日期类的API个人觉得不需要背&#xff0c;需要的时候去查资料就行。我整理了Java8之前及之后日期类常用的时间日期处理方法&#xff0c;方便工作需要时查找&#xff0c;觉得有用的朋友可以收藏。 一、日期格式化和解析 …

分布式事务-seata框架

文章目录 分布式事务0.学习目标1.分布式事务问题1.1.本地事务1.2.分布式事务1.3.演示分布式事务问题 2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2.1.3.分区容错2.1.4.矛盾 2.2.BASE理论2.3.解决分布式事务的思路 3.初识Seata3.1.Seata的架构3.2.部署TC服务3.3.微服务集成S…

C++避坑——most vexing parse问题

1."坑"的问题是什么&#xff1f; 先看一段代码&#xff1a; class Functor { public:void operator()(){std::cout << "我是线程的初始函数" << std::endl;} };int main() {std::thread t(Functor());// 强制高速编译器这是一个构造函数!t.j…