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

easy-excel fill+模板的情况下 如何合并单元格

文章目录前言一、思路二、使用步骤1.模板2.service方法3.策略4.效果总结前言easy-excel 导出excel时,遇到需要保留模板内的格式和表头等,在使用模板fill模式填充数据的情况下,单元格合并比较麻烦 在easy-excel版本比较老(2.x),升级牵扯到poi升级又涉及到poi-tl等组件也要升级的情况下,使用本文方法可实现单元格合并,如果有更好的方案欢迎交流 AI辅助开发的情况下可以把本问题喂给ai提供思路一、思路先使用fill导出数据到内存,在内存中使用poi对需要合并的单元格进行操作二、使用步骤1.模板第5行为动态数据行,前后表头表尾都是保留的,为了实现导出所有数据后,A5相同的值合并单元格,// 两次填充,对固定单元格和动态行list行进行添值处理// 填充单个数据元数据excelWriter.fill(batchMetaMap,writeSheet);// 填充列表数据FillConfig fillConfigFillConfig.builder().forceNewRow(Boolean.TRUE).build();excelWriter.fill(rowList,fillConfig,writeSheet);2.service方法代码如下示例Override publicvoidexportYearPlan(Long masterId,HttpServletResponse response,OpsPlanMaster opsPlanMaster){try{// 1. 查询该主表下的所有年计划子表记录ListOpsPlanYearplanListthis.listByMasterId(masterId);if(opsPlanMasternull||planListnull||planList.isEmpty()){return;}// 2. 设置响应头response.setContentType(application/vnd.ms-excel);response.setCharacterEncoding(utf-8);String fileNameURLEncoder.encode(年度检查巡视计划,UTF-8);response.setHeader(Content-disposition,attachment;filenamefileName.xlsx);// 3. 加载模板文件String templatePathtemplate/excel/OpsPlanYearFillTemplate.xlsx;try(InputStream templateStreamResourceLoaderUtil.getResourceAsStream(templatePath)){if(templateStreamnull){throw newRuntimeException(模板文件不存在templatePath);}// 4. 准备动态行数据ListMapString,ObjectrowListnew ArrayList();for(OpsPlanYear plan:planList){MapString,Objectrownew HashMap();row.put(categoryName,plan.getCategoryName());row.put(subCategoryName,plan.getSubCategoryName());row.put(month01,plan.getMonth01()!null?plan.getMonth01():);row.put(month02,plan.getMonth02()!null?plan.getMonth02():);row.put(month03,plan.getMonth03()!null?plan.getMonth03():);row.put(month04,plan.getMonth04()!null?plan.getMonth04():);row.put(month05,plan.getMonth05()!null?plan.getMonth05():);row.put(month06,plan.getMonth06()!null?plan.getMonth06():);row.put(month07,plan.getMonth07()!null?plan.getMonth07():);row.put(month08,plan.getMonth08()!null?plan.getMonth08():);row.put(month09,plan.getMonth09()!null?plan.getMonth09():);row.put(month10,plan.getMonth10()!null?plan.getMonth10():);row.put(month11,plan.getMonth11()!null?plan.getMonth11():);row.put(month12,plan.getMonth12()!null?plan.getMonth12():);row.put(ownerName,plan.getOwnerName()!null?plan.getOwnerName():);row.put(remark,plan.getRemark()!null?plan.getRemark():);rowList.add(row);}// 填充批次元数据年月信息、部门、审核人、制表人等MapString,ObjectbatchMetaMapnew HashMap();batchMetaMap.put(orgName,opsPlanMaster.getOrgName());// 部门名称batchMetaMap.put(planDate,opsPlanMaster.getPlanDate().format(DateTimeFormatter.ofPattern(yyyy年MM月dd日)));// 标题中的年月batchMetaMap.put(approvedBy,opsPlanMaster.getApprovedBy());// 审核人batchMetaMap.put(reviewedBy,opsPlanMaster.getReviewedBy());batchMetaMap.put(preparedBy,opsPlanMaster.getPreparedBy());// 使用自定义合并策略合并A列大类名称从第5行索引4开始FillMergeStrategy mergeStrategynewFillMergeStrategy(4,0);// 先将Excel写入内存ByteArrayOutputStream byteArrayOutputStreamnewByteArrayOutputStream();ExcelWriter excelWriterEasyExcel.write(byteArrayOutputStream).withTemplate(templateStream).build();WriteSheet writeSheetEasyExcel.writerSheet(0).build();// 填充单个数据元数据excelWriter.fill(batchMetaMap,writeSheet);// 填充列表数据FillConfig fillConfigFillConfig.builder().forceNewRow(Boolean.TRUE).build();excelWriter.fill(rowList,fillConfig,writeSheet);excelWriter.finish();// 用POI打开内存中的Excel执行合并try(Workbook workbookWorkbookFactory.create(newByteArrayInputStream(byteArrayOutputStream.toByteArray()))){org.apache.poi.ss.usermodel.Sheet sheetworkbook.getSheetAt(0);mergeStrategy.doMergeWithPoi(sheet);// 将合并后的Excel写入响应workbook.write(response.getOutputStream());}catch(Exception e){throw newRuntimeException(合并单元格失败e.getMessage(),e);}}}catch(IOException e){throw newRuntimeException(导出失败e.getMessage(),e);}}3.策略代码如下示例package com.bjhz.microservice.assets.server.full.listener;import com.alibaba.excel.ExcelWriter;import com.alibaba.excel.write.metadata.WriteSheet;import lombok.extern.slf4j.Slf4j;import org.apache.poi.ss.usermodel.Cell;import org.apache.poi.ss.usermodel.DateUtil;import org.apache.poi.ss.usermodel.Row;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.util.CellRangeAddress;/** * EasyExcel Fill模式动态单元格合并策略 * * p功能说明/p * ul * li支持多列独立合并每列根据单元格内容自动判断是否合并/li * li仅当相邻行的单元格内容完全相同时才执行合并/li * li在ExcelWriter.finish()之前手动调用doMerge执行合并/li * /ul * * p使用示例/p * pre * // 创建合并策略 * FillMergeStrategy strategy new FillMergeStrategy(4, 0); * * // 在所有fill操作完成后finish之前调用 * strategy.doMerge(excelWriter, writeSheet); * * // 合并A列和B列索引0和1从第5行开始 * FillMergeStrategy strategy new FillMergeStrategy(4, 0, 1); * /pre * * author * date 2026-04-26 */Slf4j public class FillMergeStrategy{/** 需要合并的列索引数组从0开始A列 0 */private finalint[]mergeColumnIndexes;/** 数据开始的行索引从0开始计数例如第5行传入4 */private finalintstartRowIndex;/** * 构造函数 * * param startRowIndex 数据开始的行索引从0开始例如第5行传入4 * param columnIndexes 需要合并的列索引数组可变参数 */publicFillMergeStrategy(intstartRowIndex,int...columnIndexes){this.startRowIndexstartRowIndex;this.mergeColumnIndexescolumnIndexes;}/** * 执行单元格合并 * * p调用时机/p * ul * li在所有fill操作完成后/li * li在excelWriter.finish()之前/li * /ul * * param excelWriter EasyExcel写入器 * param writeSheet 写入的Sheet */publicvoiddoMerge(ExcelWriter excelWriter,WriteSheet writeSheet){Sheet sheetexcelWriter.writeContext().writeSheetHolder().getSheet();log.info(开始执行单元格合并startRowIndex{}, mergeColumnIndexes{},startRowIndex,mergeColumnIndexes);log.info(Sheet总行数: {},sheet.getLastRowNum());// 遍历每一列需要合并的列for(intcolumnIndex:mergeColumnIndexes){mergeColumnByContent(sheet,columnIndex);}log.info(单元格合并完成);}/** * 使用POI Sheet执行单元格合并 * * p调用时机/p * ul * liExcelWriter.finish()之后/li * li使用POI打开Excel后/li * /ul * * param sheet POI的Sheet对象 */publicvoiddoMergeWithPoi(Sheet sheet){log.info(开始执行单元格合并startRowIndex{}, mergeColumnIndexes{},startRowIndex,mergeColumnIndexes);log.info(Sheet总行数: {},sheet.getLastRowNum());// 遍历每一列需要合并的列for(intcolumnIndex:mergeColumnIndexes){mergeColumnByContent(sheet,columnIndex);}log.info(单元格合并完成);}/** * 对指定列执行内容相同的单元格合并 * * param sheet Excel Sheet对象 * param columnIndex 列索引从0开始 */privatevoidmergeColumnByContent(Sheet sheet,intcolumnIndex){intlastRowNumsheet.getLastRowNum();if(lastRowNumstartRowIndex){log.info(数据行数不足第{}列无需合并,columnIndex);return;}String lastValuenull;intmergeStartRow-1;intmergeCount0;// 从指定行开始遍历for(introwIndexstartRowIndex;rowIndexlastRowNum;rowIndex){Row rowsheet.getRow(rowIndex);if(rownull){continue;}Cell cellrow.getCell(columnIndex);String currentValuegetCellValue(cell);// 当前值与上一行值相同记录合并起始位置if(currentValue!nullcurrentValue.equals(lastValue)){if(mergeStartRow-1){mergeStartRowrowIndex-1;}}else{// 当前值与上一行值不同执行上一段合并if(mergeStartRow!-1){intmergeEndRowrowIndex-1;log.info(合并第{}列从第{}行到第{}行值{},columnIndex,mergeStartRow,mergeEndRow,lastValue);CellRangeAddress regionnewCellRangeAddress(mergeStartRow,mergeEndRow,columnIndex,columnIndex);try{sheet.addMergedRegion(region);clearMergedCells(sheet,region);mergeCount;}catch(Exception e){log.warn(合并失败: {},e.getMessage());}mergeStartRow-1;}}lastValuecurrentValue;}// 处理最后一段合并if(mergeStartRow!-1lastRowNummergeStartRow){log.info(合并第{}列从第{}行到第{}行最后一行值{},columnIndex,mergeStartRow,lastRowNum,lastValue);CellRangeAddress regionnewCellRangeAddress(mergeStartRow,lastRowNum,columnIndex,columnIndex);try{sheet.addMergedRegion(region);clearMergedCells(sheet,region);mergeCount;}catch(Exception e){log.warn(合并失败: {},e.getMessage());}}log.info(第{}列合并完成共合并{}个区域,columnIndex,mergeCount);}/** * 清空合并区域中除第一个单元格外的其他单元格的值 * * param sheet Excel Sheet对象 * param region 合并区域 */privatevoidclearMergedCells(Sheet sheet,CellRangeAddress region){for(introwIndexregion.getFirstRow()1;rowIndexregion.getLastRow();rowIndex){Row rowsheet.getRow(rowIndex);if(row!null){Cell cellrow.getCell(region.getFirstColumn());if(cell!null){// POI 3.16兼容设置单元格类型为空来清空值cell.setCellType(Cell.CELL_TYPE_BLANK);}}}}/** * 安全获取单元格的字符串值 * * param cell Excel单元格对象 * return 单元格的字符串值如果单元格为null则返回null */private StringgetCellValue(Cell cell){if(cellnull){returnnull;}intcellTypecell.getCellType();if(cellTypeCell.CELL_TYPE_STRING){returncell.getStringCellValue();}elseif(cellTypeCell.CELL_TYPE_NUMERIC){if(DateUtil.isCellDateFormatted(cell)){returnString.valueOf(cell.getDateCellValue());}else{returnString.valueOf(cell.getNumericCellValue());}}elseif(cellTypeCell.CELL_TYPE_BOOLEAN){returnString.valueOf(cell.getBooleanCellValue());}elseif(cellTypeCell.CELL_TYPE_FORMULA){returncell.getCellFormula();}returnnull;}}示例代码,思路是这样,代码随便看看clearMergedCells必须要有,否则虽然看着单元格合并了,但是下面的单元格每个还有自己的值,不像在excel里直接执行合并,只保留上面的值,这样的话在导入的情况下,会导致你按空处理合并单元格的第二行会莫名奇妙读到值,具体解释看最后4.效果导出效果示例总结先使用fill导出数据到内存,在内存中使用poi对需要合并的单元格进行操作踩坑问题:导出策略中,如果只合并单元格,不进行置空处理,这样导出的excel,合并的单元格并不像我们在excel中合并单元格一样只保留左上角的值,下面单元格也是有值的,如果是导出的时候当成空置判断就会有问题在 Excel 中合并单元格后只有左上角的单元格保留值其他被合并的单元格应该被清空。但 POI 的 addMergedRegion() 只是设置合并区域不会自动清除其他单元格的值。解决方案:需要修改 FillMergeStrategy 在执行合并后清空被合并区域中除了第一个单元格之外的其他单元格的值代码已经修复

相关文章:

easy-excel fill+模板的情况下 如何合并单元格

文章目录前言一、思路二、使用步骤1.模板2.service方法3.策略4.效果总结前言 easy-excel 导出excel时,遇到需要保留模板内的格式和表头等,在使用模板fill模式填充数据的情况下,单元格合并比较麻烦 在easy-excel版本比较老(2.x),升级牵扯到poi升级又涉及到poi-tl等组件也要升级…...

Qt:创建一套基于HSL颜色体系的颜色库

HSL颜色体系知识见: https://blog.csdn.net/xulibo5828/article/details/160521898 执行脚本: # -*- coding: utf-8 -*- # 色相字典 h_dict {"灰色": "0","红色": "0","棕色": "20",&q…...

开箱即用的本地AI对话平台部署与深度使用指南

1. 项目概述:一个开箱即用的本地AI对话平台最近在折腾AI应用本地化部署的朋友,可能都绕不开一个核心痛点:想要一个界面友好、功能全面、又能保护隐私的AI对话工具,但要么需要自己从零搭建,技术栈复杂;要么就…...

终极指南:如何为Switch选择最合适的游戏安装器

终极指南:如何为Switch选择最合适的游戏安装器 【免费下载链接】Awoo-Installer A No-Bullshit NSP, NSZ, XCI, and XCZ Installer for Nintendo Switch 项目地址: https://gitcode.com/gh_mirrors/aw/Awoo-Installer 如果你正在寻找一款真正"即开即用&…...

AI绘画提示词工程:从社区宝藏库到个人知识体系构建

1. 项目概述:一个AI绘画提示词的“宝藏库”如果你玩过Midjourney、Stable Diffusion或者DALL-E 3这类AI绘画工具,那你一定有过这样的经历:脑子里有个绝妙的画面,但打出来的提示词(Prompt)却总是词不达意&am…...

Composition-RL:大语言模型强化学习中的组合提示技术

1. Composition-RL:大语言模型强化学习中的组合提示技术解析在大型语言模型(LLM)的训练过程中,强化学习与可验证奖励(Reinforcement Learning with Verifiable Rewards, RLVR)已成为提升模型推理能力的关键…...

NCM文件解密终极指南:免费工具快速解锁网易云音乐加密格式

NCM文件解密终极指南:免费工具快速解锁网易云音乐加密格式 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾遇到过从网易云音乐下载的歌曲只能在特定软件中播放的困扰?那些以.ncm为扩展名的文件&#…...

2026年小程序商城哪个比较好用?

2026年小程序商城哪个比较好用?小程序商城好不好用,取决于企业需求与平台能力的匹配度。从行业数据来看,不同需求场景下"好用"的标准不同:纯电商交易型适合专用电商平台,商城会员营销一体化适合国内SaaS平台&#xff0…...

YOLO12检测结果后处理:NMS阈值调整与多框融合策略

YOLO12检测结果后处理:NMS阈值调整与多框融合策略 1. 引言:为什么检测框需要“精修”? 当你用YOLO12跑完一张图片,看到屏幕上密密麻麻的检测框时,是不是觉得大功告成了?先别急,这其实只是完成…...

计算机毕业设计 | SpringBoot+vue学生网上请假系统 高校教务管理系统(附源码+论文+开题报告)

1,绪论 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理学生网上请假系统的相关信息成为…...

2026年小程序商城如何上线

2026年小程序商城如何上线 小程序商城上线主要涉及三个阶段:平台注册与认证、功能配置与内容填充、提交审核与发布。整个上线周期从3天到3个月不等,SaaS平台方案可在5-14天内完成上线,是当前中小企业最常用的路径。根据微信官方数据&#xff…...

HelpingAI-15B:150亿参数情感对话大模型技术解析

1. 项目概述今天要和大家分享的是一个让我眼前一亮的开源项目——HelpingAI-15B。这个拥有150亿参数的大模型在情感对话领域带来了突破性的进展。作为一名长期关注对话系统发展的从业者,我第一时间测试了这个模型,发现它在理解用户情绪、提供共情回应方面…...

Gemini-3基准测试实战:性能优化与调优技巧

1. 项目背景与核心价值Gemini-3 Benchmarkathon这个项目名称直译为"双子座3代基准测试马拉松",从命名就能看出这是针对特定硬件或软件系统进行的持续性性能评估活动。这类benchmark活动在芯片设计、数据库优化、AI模型训练等领域十分常见,通常…...

代码随想录算法训练营第三十九天|LeetCode 198 打家劫舍、LeetCode 213 打家劫舍 ||、LeetCode 337 打家劫舍 |||

参考文章均来自代码随想录 LeetCode 198 打家劫舍 参考文章链接 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯…...

LoRA技术在AI视频生成中的应用与优化

1. 项目概述"Wan 2.1 Squish LoRA Video Tutorial"这个标题乍看简单,但包含了几个关键信息点。作为一名在AI生成内容领域摸爬滚打多年的从业者,我一眼就看出这是关于LoRA模型在视频生成中的应用教程。具体来说,Wan 2.1应该是某个特…...

Wan2.2-I2V-A14B风格迁移应用:将输入文本映射至特定艺术家视觉风格

Wan2.2-I2V-A14B风格迁移应用:将输入文本映射至特定艺术家视觉风格 1. 镜像概述与核心能力 Wan2.2-I2V-A14B是一款专为艺术风格视频生成设计的私有部署镜像,能够将文本描述转化为具有特定艺术家风格的动态视频作品。这个镜像经过深度优化,特…...

AI素养危机:技术认知与风险评估的实践指南

1. AI素养危机的现状与根源最近在技术社区里有个热议话题:我们正在AI素养培养上集体失败。这个现象不仅出现在普通用户群体,就连很多科技从业者也存在明显的认知断层。上个月我参加了一场行业研讨会,发现台下80%的开发者居然说不清大语言模型…...

走进涠洲岛环岛路,解锁火山海岸原生态风光

涠洲岛静卧于广西北海市南部的海域之中,作为中国最大且最年轻的火山岛,其地表形态完整记录了第四纪以来火山喷发与海洋侵蚀的共同作用。环岛游所经之处,海蚀崖、熔岩台地、珊瑚碎屑滩、渔村石屋依次展开,构成了一座没有围墙的火山…...

智能体框架开发指南:从ReAct模式到生产级Agentic应用构建

1. 项目概述:一个面向开发者的智能体框架 最近在GitHub上看到一个挺有意思的项目,叫 laugiov/agentic-dev-framework 。光看名字, agentic 这个词就挺抓人眼球的,它直译过来是“能动的”、“有自主性的”,和 dev-…...

注意力机制在LLM推理中的核心作用与优化策略

1. 注意力机制在LLM推理中的核心作用注意力机制作为Transformer架构的核心组件,其本质是一种信息路由系统。在自回归生成过程中,每个新token的生成都依赖于对历史上下文的动态加权聚合。这种机制的技术实现基于三个核心向量:查询(…...

AI安全评估:从黑盒到白盒的深度实践

1. 项目概述:AI安全评估的现状与挑战在人工智能技术快速发展的今天,大型语言模型(LLM)和多模态模型(MLLM)的安全性问题已成为行业关注的焦点。随着模型能力的不断提升,其潜在风险也呈现出复杂化…...

CLI与MCP对比:命令行与图形界面的运维效率之争

1. 命令行界面与多控制面板的世纪之争第一次在服务器机房看到老运维用纯命令行界面(CLI)操作整个数据中心时,那种行云流水的操作给我留下了深刻印象。而隔壁工位的产品经理却坚持认为,现代多控制面板(MCP)才…...

如何通过开源工具OmenSuperHub优化惠普OMEN游戏本性能:完整指南

如何通过开源工具OmenSuperHub优化惠普OMEN游戏本性能:完整指南 【免费下载链接】OmenSuperHub 使用 WMI BIOS控制性能和风扇速度,自动解除DB功耗限制。 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 还在为惠普OMEN游戏本官方控制…...

终极Photon-GAMS光影包教程:3步将方块世界变电影大片

终极Photon-GAMS光影包教程:3步将方块世界变电影大片 【免费下载链接】Photon-GAMS Personal fork of Photon shaders 项目地址: https://gitcode.com/gh_mirrors/ph/Photon-GAMS 还在为Minecraft那单调的像素画面而烦恼吗?想要一键让方块世界拥有…...

大模型在软件开发中的实践挑战与优化策略

1. 大模型如何改变软件开发的游戏规则去年我在重构一个遗留系统时,第一次尝试用大模型辅助解决代码迁移问题。当时需要将VB6的老旧模块转换为C#,本以为大模型能轻松搞定,结果生成的代码里竟然出现了VB6特有的On Error Resume Next语句——这个…...

YOLOv8与nli-MiniLM2-L6-H768联合作业:图像描述文本的合规性审核

YOLOv8与nli-MiniLM2-L6-H768联合作业:图像描述文本的合规性审核 1. 社交平台面临的内容审核挑战 每天有数以亿计的图片在社交平台上被上传和分享,如何高效准确地识别其中的违规内容成为平台运营者的头号难题。传统人工审核团队面临三大困境&#xff1…...

内容创作者福音:LongCat-Image-Edit V2快速生成统一风格配图

内容创作者福音:LongCat-Image-Edit V2快速生成统一风格配图 你有没有过这样的经历?写一篇深度文章,花了两天时间,最后卡在配图上——要么找不到风格统一的图片,要么找到的图片版权不明,要么自己动手做图&…...

工厂生产瓶颈工序识别,3个实操方法快速定位:2026智能工厂效能优化全景盘点

在2026年的工业4.0深化阶段,制造企业的竞争已从单纯的“产能比拼”转向“响应速度与柔性交付”的博弈。生产瓶颈(Bottleneck)作为制约整条生产线产出的“短板”,其识别与优化直接决定了企业的OEE(设备综合效率&#xf…...

原创文档:基于Chaboche物理约束与LSTM残差学习的316L不锈钢循环塑性灰箱本构建模研究

摘要:针对316L不锈钢循环塑性响应的非线性、路径依赖及滞回特征,传统经验本构模型在复杂加载条件下描述能力有限,纯数据驱动模型又缺乏物理可解释性。为兼顾物理意义与预测精度,本文提出一种基于Chaboche物理约束与LSTM残差学习的…...

基于Chaboche物理约束与LSTM残差学习的316L不锈钢循环塑性灰箱本构建模研究

摘要:针对316L不锈钢循环塑性响应的非线性、路径依赖及滞回特征,传统经验本构模型在复杂加载条件下描述能力有限,纯数据驱动模型又缺乏物理可解释性。为兼顾物理意义与预测精度,本文提出一种基于Chaboche物理约束与LSTM残差学习的…...