当前位置: 首页 > news >正文

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);

截图如下

荆轲刺秦王!

相关文章:

excel 动态表头与合并列

零、希望Springboot-java导出excel文件&#xff0c;包括动态表头与下边合并的列 使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下 一、代码 1、依赖 implementation(group: org.apache.poi,name: poi-ooxml,version: 4.1.0)implementation(group: org.apache.po…...

jenkins自动部署微服务到docker

1、代码上传到git&#xff1b; 2、jenkins拉取git的代码&#xff0c;maven打包&#xff0c;使用插件生成镜像&#xff0c;自动上传docker&#xff1b; 两个插件&#xff0c;一个打包插件&#xff0c;一个创建镜像上传docker仓库.&#xff08;将dockerfile内容搬到插件配置&…...

【蔚来汽车】蔚来20220713第三题-旅游规划 <模拟、滑动窗口>

【蔚来汽车】蔚来20220713第三题-旅游规划 牛牛对 n 个城市旅游情况进行了规划&#xff0c;已知每个城市有两种属性 x 和 y &#xff0c;其中 x 表示去第 i 号城市的花费&#xff0c;y 表示在第 i 号城市游玩后会得到的开心值。   现在牛牛希望从中挑选出一些城市去游玩&…...

[解决方案]Antd TreeSelect/Select placeholder失效

&#x1f50e;嘿&#xff0c;这里是慰慰&#x1f469;&#x1f3fb;‍&#x1f393;&#xff0c;会发各种类型的文章&#xff0c;智能专业&#xff0c;从事前端&#x1f43e; &#x1f389;如果有帮助的话&#xff0c;就点个赞叭&#xff0c;让我开心一下&#xff01;&#x1f…...

微人事 部门管理 模块 (十五)

部门管理的树展示和搜索 数据展示页是个树&#xff0c;我们一次性把数据加载出来也可以通过点一次id加载查询出来出来子部门&#xff0c;我们用一次拿到说有json数据加载出来 数据不多可以用递归&#xff0c;数据很多就用懒加载的方式 由于子部门比较深就不适合&#xff0c;权…...

【Terraform学习】使用 Terraform 从 EC2 实例访问 S3 存储桶(Terraform-AWS最佳实战学习)

使用 Terraform 从 EC2 实例访问 S3 存储桶 实验步骤 前提条件 安装 Terraform&#xff1a; 地址 下载仓库代码模版 本实验代码位于 task_ec2_s3connet 文件夹中。 变量文件 variables.tf 在上面的代码中&#xff0c;您将声明&#xff0c;aws_access_key&#xff0c;aws_…...

ZDRE6VP4-1X/50MG24K4V比例压力阀放大器

DRE 6-11/100MG24K4M比、DRE 10-6X/200YMG24K4M、DRE 20-52/200YMG24K4M、DRE 20-6X/200YMG24K4M、ZDRE6VP1-1X/315MG24N9K4M、ZDRE6VP4-1X/50MG24K4V、Z3DRE6VP2-2X/50G24K4M、Z3DRE6VP1-2X/100G24K4M、Z3DRE10VP2-1X/100XYG24K4M、Z3DRE10VP1-1X/315XLG24K4V 功能: 设定值通…...

纠缠辅助的量子网络:原理、技术、发展与挑战

7月11日&#xff0c;中国科大网络空间安全学院和陆军院士工作室李忠辉博士为第一作者、薛开平教授为通讯作者的量子网络综述论文“Entanglement-Assisted Quantum Networks: Mechanics, Enabling Technologies, Challenges, and Research Directions”在通信领域知名期刊《IEEE…...

React Native 可触摸组件基础知识

在 React Native 中要实现可触摸的组件方式有三种&#xff0c;第一种方式就是使用TouchableHighlight组件&#xff0c;第二种方式就是使用TouchableOpacity组件&#xff0c;最后一种方式就是使用TouchableWithoutFeedback组件。 TouchableHighlight TouchableHighlight组件主…...

用户、权限和Vim编辑器

用户 用户分类 超级管理员&#xff1a;可以登录&#xff0c;拥有所有权限&#xff0c;用户Id为0 普通用户&#xff1a;可以登录&#xff0c;但只能操作家目录&#xff0c;用户Id为1000 程序用户&#xff1a;不能登录&#xff0c;用于管理程序&#xff0c;用户Id为1~999 添…...

git版本管理加合并笔记

目录 1.创建空文件夹&#xff0c;右键Bash here打开 2.打开链接&#xff0c;点击克隆下载&#xff0c;复制SSH链接 3.输入git SSH链接 回车 4.换成https在桌面上进行克隆仓库就正常了 5.去vscode里改东西 6.提交 7.创建dev分支 8.在dev里修改内容&#xff0c;提交&…...

Failed to load property source from location ‘classpath:/application.yml‘

项目场景&#xff1a; 今天到公司启动项目&#xff0c;突然发现项目起不起来了 问题描述 出现 Failed to load property source from location ‘classpath:/application.yml’ 错误 原因分析&#xff1a; 刚开始以为是 application.yml 中格式错误&#xff0c;但是发现同…...

Ajax复习

Ajax复习 一、简介 ​ AJAX 全称为 Asynchronous JavaScript And XML&#xff0c;就是异步的 JS 和 XML。 ​ 一句话总结&#xff1a;无刷新通信。 二、 特点 优点 无刷新通信 允许你根据用户事件来更新部分页面内容 缺点 没有浏览历史&#xff0c;不能回退 存在跨域问题…...

里式替换原则(LSP)

目录 简介: 作用: 过程: 总结: 简介: 里式替换原则&#xff08;Liskov Substitution Principle&#xff0c;简称LSP&#xff09;的提出者是美国计算机科学家Barbara Liskov。Barbara Liskov是一位计算机科学家&#xff0c;麻省理工学院教授&#xff0c;也是美国第一个计算机…...

mysql------做主从复制,读写分离

1.为什么要做主从复制&#xff08;主从复制的作用&#xff09; 做数据的热备&#xff0c;作为后备数据库&#xff0c;主数据库服务器故障后&#xff0c;可切换到从数据库继续工作&#xff0c;避免数据丢失。 架构的扩展。业务量越来越大,I/O访问频率过高&#xff0c;单机无法满…...

Anaconda虚拟环境跨系统迁移

旧设备运行代码 conda activate name conda env export > environment.yml注意&#xff1a;如果当前路径已经有了 environment.yml 文件&#xff0c;conda 会重写这个文件 新设备运行代码 # 将environment.yml 拷贝到新设备中conda env create -f environment.yml...

第四章 IRIS 编程简介 - Macros

文章目录 第四章 IRIS 编程简介 - MacrosMacrosInclude Files这些代码元素如何协同工作 第四章 IRIS 编程简介 - Macros Macros ObjectScript 还支持定义替换的宏。定义可以是一个值、整行代码或&#xff08;使用 ##continue 指令&#xff09;多行。使用宏来确保一致性。例如…...

大厂考核重点:mysql索引面试题

很多同学面对Mysql索引相关的面试题都是死记硬背的&#xff0c;这肯定是不行的&#xff0c;也不容易记住&#xff0c;所以大家还是要循循渐进&#xff0c;从理解开始&#xff0c;慢慢掌握&#xff0c;当然对于想要准备面试题的同学&#xff0c;这几个问题是需要记住并理解的&am…...

MySQL使用binlog日志做数据恢复

MySQL的binlog日志是MySQL日志中非常重要的一种日志&#xff0c;记录了数据库所有的DML操作。通过binlog日志我们可以进行数据库的读写分离、数据增量备份以及服务器宕机时的数据恢复。 定期备份固然可以在服务器发生宕机的时候快速的恢复数据&#xff0c;但传统的全量备份不可…...

USB Type-C端口集成式ESD静电保护方案 安全低成本

Type-C端口是根据USB3.x和USB4协议传输数据的&#xff0c;很容易受到电气过载&#xff08;EOS&#xff09;和静电放电&#xff08;ESD&#xff09;事件的影响。由于Type-C支持随意热插拔功能&#xff0c;其内部高集成度的芯片&#xff0c;更容易受到人体静电放电的伤害和损坏。…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...