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

news2024/9/29 11:24:42

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将值写入Excel
        ExcelWriter 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() {
    }
 
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {
 
    }
 
    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {
 
    }
 
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
 
    }
 
    @Override
    public 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;
    }
 
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                 Head head, Integer integer, Integer integer1, Boolean aBoolean) {
 
    }
 
    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
                                Head head, Integer integer, Boolean aBoolean) {
 
 
 
 
    }
 
 
 
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                       CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
 
    }
 
    @Override
    public 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);
    }
 
    @Override
    protected 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));
            }
        }
    }
 
    @Override
    protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        if (headCellStyle == null) {
            return;
        }
        cell.setCellStyle(headCellStyle);
    }
 
    @Override
    protected 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将值写入Excel
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(template)
                     //样式注册
                    .registerWriteHandler(horizontalCellStyleStrategyBuilder())
                     //行注册
                    .registerWriteHandler(excelFillCellMergeStrategyUtils)
                     //列注册
                    .registerWriteHandler(excelFillCellMergePrevColUtils)
                    .build();
            //构建excel的sheet
            WriteSheet 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 类里面的

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ios备忘录怎么导入华为 方法介绍

作为一个常常需要在不同设备间切换的人&#xff0c;我深知备忘录的重要性。那些突如其来的灵感、重要的会议提醒、甚至是生活中的琐碎小事&#xff0c;我们都习惯性地记录在备忘录里。但当我决定从iPhone转向华为时&#xff0c;一个问题困扰了我&#xff1a;如何将那些珍贵的备…

Unity学习笔记(零基础到就业)|Chapter01:C#入门

Unity学习笔记&#xff08;零基础到就业&#xff09;&#xff5c;Chapter01:C#入门 前言一、控制台输入输出语句二、初识变量1.一些好用的tips2.变量声明的固定写法3.变量类型 三、变量的本质1.变量的存储空间2.变量的本质&#xff1a;2进制 四、变量的命名规范1.必须遵守的规则…

EasyExcel合并相同内容单元格及动态标题功能的实现

一、最初版本 导出的结果&#xff1a; 对应实体类代码&#xff1a; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentLoopMerge; import com.al…

qt-C++笔记之使用QLabel和QPushButton实现一个bool状态的指示灯

qt-C笔记之使用QLabel和QPushButton实现一个bool状态的指示灯 code review! 文章目录 qt-C笔记之使用QLabel和QPushButton实现一个bool状态的指示灯1.QPushButton实现2.QLabel实现2.QLabel实现-对错符号 1.QPushButton实现 运行 代码 #include <QtWidgets>class Ind…

Spring Boot + MinIO 实现文件切片极速上传技术

文章目录 1. 引言2. 文件切片上传简介3. 技术选型3.1 Spring Boot3.2 MinIO 4. 搭建Spring Boot项目5. 集成MinIO5.1 配置MinIO连接信息5.2 MinIO配置类 6. 文件切片上传实现6.1 控制器层6.2 服务层6.3 文件切片上传逻辑 7. 文件合并逻辑8. 页面展示9. 性能优化与拓展9.1 性能优…

ros2机器人在gazebo中移动方案

原文连接Gazebo - Docs: Moving the robot (gazebosim.org) 很重要的地方&#xff1a;使用虚拟机运行Ubuntu的时候&#xff0c;需要关闭”加速3D图形“的那个选项&#xff0c;否则gazebo无法正常显示。 Moving the robot&#xff08;使用命令移动机器人示例&#xff09; In t…

maven 项目导入异常问题

问题如下 一、 tomcat正再运行的包是哪一个 不同构建、打包情况下分别运行 out\artifacts下 当直接去Project Structure下去构建artifacts 后&#xff0c;运行tomcat 则会在out下target下 reimport项目后,则会在artifacts自动生成部署包。删除tomcat之前deployment 下的包(同…

一种基于外观-运动语义表示一致性的视频异常检测框架 论文阅读

A VIDEO ANOMALY DETECTION FRAMEWORK BASED ON APPEARANCE-MOTION SEMANTICS REPRESENTATION CONSISTENCY 论文阅读 ABSTRACT1. INTRODUCTION2. PROPOSED METHOD3. EXPERIMENTAL RESULTS4. CONCLUSION阅读总结&#xff1a; 论文标题&#xff1a;A VIDEO ANOMALY DETECTION FRA…

线上环境如何正确配置 Django 的 DEBUG?

Author&#xff1a;rab Django Version&#xff1a;3.2 Python Version&#xff1a;3.9 目录 前言一、DEBUG True二、DEBUG False三、页面异常解决总结 前言 由于最近在学习 Django 的知识&#xff0c;于是尝试开发了一套 Blog 系统&#xff0c;在本地测试时是页面显示没问题…

数据智慧:如何利用可视化提升效率

数据可视化是一项强大的工具&#xff0c;能够显著提高工作效率和决策的准确性。下面我就以可视化从业者的角度&#xff0c;简单谈谈数据可视化是如何助力效率提升的。 直观理解复杂数据 数据可视化将抽象的数据转化为图表、图形或仪表盘&#xff0c;使数据更易于理解。这种直观…

智能优化算法应用:基于瞬态优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于瞬态优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于瞬态优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.瞬态优化算法4.实验参数设定5.算法结果6.…

中小型企业怎么选SD-WAN?

随着网络技术的发展&#xff0c;SD-WAN作为一种高效灵活的网络连接被越来越多的企业选用。在SD-WAN的组网过程中&#xff0c;中小型企业需要怎么做&#xff0c;以确保SD-WAN的成功实施&#xff1f;本文将详细介绍中小型企业在SD-WAN组网时需要考虑的关键因素&#xff0c;助力企…

IDEA shorten command line介绍和JAR manifest 导致mybatis找不到接口类处理

如果类路径太长&#xff0c;或者有许多VM参数&#xff0c;程序就无法启动。原因是大多数操作系统都有命令行长度限制。在这种情况下&#xff0c;IntelliJIDEA将试图缩短类路径。最好选中 classpath file模式。 shorten command line 选项提供三种选项缩短类路径。 none&#x…

node.js mongoose中间件(middleware)

目录 简介 定义模型 注册中间件 创建doc实例&#xff0c;并进行增删改查 方法名和注册的中间件名相匹配 执行结果 分析 错误处理中间件 手动抛出错误 注意点 简介 在mongoose中&#xff0c;中间件是一种允许在执行数据库操作前&#xff08;pre&#xff09;或后&…

机器学习---聚类(原型聚类、密度聚类、层次聚类)

1. 原型聚类 原型聚类也称为“基于原型的聚类” (prototype-based clustering)&#xff0c;此类算法假设聚类结构能通过一 组原型刻画。算法过程&#xff1a;通常情况下&#xff0c;算法先对原型进行初始化&#xff0c;再对原型进行迭代更新求解。著 名的原型聚类算法&#…

SQL进阶理论篇(十四):CBO优化器是如何计算代价的?

文章目录 简介能调整的代价模型的参数有哪些&#xff1f;mysql.server_costmysql.engine_cost 如何修改这些代价参数&#xff1f;代价模型具体是如何计算的参考文献 简介 大部分RDBMS都支持基于代价的优化器CBO&#xff0c;但其实CBO仍然存在缺陷&#xff08;比如参数配置的不…

OpenAI 疑似正在进行 GPT-4.5 灰度测试!

‍ 大家好&#xff0c;我是二狗。 今天&#xff0c;有网友爆料OpenAI疑似正在进行GPT-4.5灰度测试&#xff01; 当网友询问ChatGPT API调用查询模型的确切名称是什么时&#xff1f; ChatGPT的回答竟然是 gpt-4.5-turbo。 也有网友测试之后发现仍然是GPT-4模型。 这是有网友指…

解决腾讯云CentOS 6硬盘空间不足问题:从快照到数据迁移

引言&#xff1a; 随着数据的不断增加&#xff0c;服务器硬盘空间不足变成了许多运维人员必须面对的问题。此主机运行了httpd&#xff08;apache服务&#xff09;&#xff0c;提供对外web访问服务,web资源挂载在**/data/wwwroot目录下,http日志存放在/data/wwwlogs目录下&…

【MATLAB源码-第101期】基于matlab的蝙蝠优化算BA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 蝙蝠算法&#xff08;BA&#xff09;是一种基于群体智能的优化算法&#xff0c;灵感来源于蝙蝠捕食时的回声定位行为。这种算法模拟蝙蝠使用回声定位来探测猎物、避开障碍物的能力。在蝙蝠算法中&#xff0c;每只虚拟蝙蝠代表…

【Hadoop精讲】HDFS详解

目录 理论知识点 角色功能 元数据持久化 安全模式 SecondaryNameNode(SNN) 副本放置策略 HDFS写流程 HDFS读流程 HA高可用 CPA原则 Paxos算法 HA解决方案 HDFS-Fedration解决方案&#xff08;联邦机制&#xff09; 理论知识点 角色功能 元数据持久化 另一台机器就…