• SpringBoot 使用EasyExcel 导出Excel报表(单元格合并)


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    SpringBoot 使用Alibaba提供的EasyExcel导出Excel报表。

    本文中涉及的业务逻辑有:

    1. 前端发起请求,后端封装Excel信息,直接在浏览器完成下载业务;
    2. 在后端,通过代码,控制导出的Excel样式。(主要包括:横向、纵向合并单元格

    本文中后端导出Excel的数据,只包括导出列表数据,具体指:将一个List< List< String>>类型的数据导出到Excel中。(可将数据导出到不同的sheet中)

    也就是说导出的数据没有固定的对象格式。如果需要导出List< Object> 的列表,需要封装导出对象,并对属性配置@ExcelProperty等信息,此文目前将不涉及这部分的内容。

    最终效果展示:
    在这里插入图片描述


    一、导入依赖

    导入相关依赖

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>2.2.6version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、代码

    1. 导出简单的Excel

    // 导出的Excel 文件名称
    String fileName = "xxx.xlsx";
    try {
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Type", "application/vnd.ms-excel");
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.addHeader("Cache-Control", "no-cache");
    
        // 创建ExcelWriter对象
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
    
        // 写入第一个sheet的数据
        WriteSheet sheet1 = EasyExcel.writerSheet(0, "Sheet1")
                .registerWriteHandler(mergePrevCol1)
                .build();
        // 设置Excel表头(表头横向展示)
        sheet1.setHead(Arrays.asList(Arrays.asList(""),Arrays.asList("1#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3#加热炉"),Arrays.asList("1"),Arrays.asList("2")));
        // 写入数据
        excelWriter.write(heatFurnace1And3MZLValueList, sheet1);
    
        // 关闭ExcelWriter
        excelWriter.finish();
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    以上就能完成简单的导出Excel报表的功能

    请注意Controller对应接口的返回值,一般设置为void

        @PostMapping(value = "/export")
        @ApiOperation(value = "导出报表")
        public void export(@RequestBody RecordExportBO bean, HttpServletResponse response) {
        	// 这里的RecordExportBO 为封装接收前端数据的对象
            iService.export(bean,response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 代码控制导出报表的格式

    这里主要包括控制表头的格式以及合并单元格

    A. 合并行的工具类

    package cisdi.info.imc.device.model.common.util;
    
    import com.alibaba.excel.metadata.CellData;
    import com.alibaba.excel.metadata.Head;
    import com.alibaba.excel.write.handler.CellWriteHandler;
    import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
    import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
    import org.apache.poi.ss.usermodel.Cell;
    import org.apache.poi.ss.usermodel.CellType;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.util.CellRangeAddress;
    
    import java.util.List;
    
    /**
     * 合并Excel的行(内容相同的行才合并)
     */
    public class ExcelFillCellMergeStrategyUtil implements CellWriteHandler {
        private int[] mergeColumnIndex; //数组存放这一行需要合并那几列  [0,1,2] 在这mergeRowIndex行中合并 0 、1、2列
        private int mergeRowIndex;  // 存放需要向上合并的列
        private Integer noMergeRowIndex;// 不要合并的列
    
        public ExcelFillCellMergeStrategyUtil() {
        }
    
        public ExcelFillCellMergeStrategyUtil(int mergeRowIndex, int[] mergeColumnIndex) {
            this.mergeRowIndex = mergeRowIndex;
            this.mergeColumnIndex = mergeColumnIndex;
        }
    
        public ExcelFillCellMergeStrategyUtil(int mergeRowIndex, int[] mergeColumnIndex, Integer noMergeRowIndex) {
            this.mergeColumnIndex = mergeColumnIndex;
            this.mergeRowIndex = mergeRowIndex;
            this.noMergeRowIndex = noMergeRowIndex;
        }
    
        @Override
        public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
    
        }
    
        @Override
        public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
    
        }
    
        @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;
                    }
                }
            }
        }
    
        /**
         * 当前单元格向上合并
         * .
         * @param writeSheetHolder writeSheetHolder
         * @param cell             当前单元格
         * @param curRowIndex      当前行
         * @param curColIndex      当前列
         */
        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 ( (noMergeRowIndex != null) &&   noMergeRowIndex == (curRowIndex - 1)  ){
                return;
            }
            // 将当前单元格数据与上一个单元格数据比较
            Boolean dataBool = preData.equals(curData);
    
            //此处需要注意:所以获取每一行第一列数据和上一行第一列数据进行比较,如果相等合并
            boolean equals = cell.getRow().getCell(0).getStringCellValue().equals(cell.getSheet().getRow(curRowIndex - 1).getCell(0).getStringCellValue());
            if (dataBool && equals) {
                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);
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    B. 合并列的工具类

    package cisdi.info.imc.device.model.common.util;
    
    import com.alibaba.excel.metadata.CellData;
    import com.alibaba.excel.metadata.Head;
    import com.alibaba.excel.write.handler.CellWriteHandler;
    import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
    import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
    import org.apache.poi.ss.usermodel.Cell;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.util.CellRangeAddress;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.List;
    
    /**
     * 合并Excel的列
     */
    public class ExcelFillCellMergePrevColUtil implements CellWriteHandler {
        private static final String KEY ="%s-%s";
        //所有的合并信息都存在了这个map里面
        Map<String, Integer> mergeInfo = new HashMap<>();
    
        public ExcelFillCellMergePrevColUtil() {
        }
        public ExcelFillCellMergePrevColUtil(Map<String, Integer> mergeInfo) {
            this.mergeInfo = mergeInfo;
        }
    
        @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从第几列开始增加多少列
        // curRowIndex 在第几行进行行合并
        // curColIndex 在第几列进行合并
        // num 合并多少格
        // 比如我上图中中心需要在第三行 从0列开始合并三列 所以我可以传入 (3,0,2)
        public void add (int curRowIndex,  int curColIndex , int num){
            mergeInfo.put(String.format(KEY, curRowIndex, curColIndex),num);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    C. 控制表头格式

    /**
    * 设置导出报表的表格格式
    * @return
    */
    public CellStyleStrategyUtil horizontalCellStyleStrategyBuilder() {
       WriteCellStyle headWriteCellStyle = new WriteCellStyle();
       //设置头字体
       WriteFont headWriteFont = new WriteFont();
       headWriteFont.setFontHeightInPoints((short) 15);
       headWriteFont.setBold(true);
       headWriteCellStyle.setWriteFont(headWriteFont);
       //设置头居中
       headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
       //内容策略
       WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
       //设置 水平居中
       contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
       //垂直居中
       contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    
       return new CellStyleStrategyUtil(headWriteCellStyle, contentWriteCellStyle,Boolean.TRUE);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    D. 具体用法

    直接结合具体例子,通过例子中关键代码的注解进行介绍:

    // 导出的Excel 文件名称
    String fileName = "xxx.xlsx";
    try {
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Type", "application/vnd.ms-excel");
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.addHeader("Cache-Control", "no-cache");
    
        // 创建ExcelWriter对象
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
    
        // 创建第一个sheet的列合并工具类对象
        ExcelFillCellMergePrevColUtil mergePrevCol1 = new ExcelFillCellMergePrevColUtil();
        // 第一个参数:合并哪一行;第二个参数:合并的起始列;第三个参数:往右合并的列数;
        // 指一条代表合并第0行的第1,2,3列。
        mergePrevCol1.add(0,1,2);
        // 这一行代表合并第0行的4,5,6列
        mergePrevCol1.add(0,4,2);
    
        // 写入第一个sheet的数据
        WriteSheet sheet1 = EasyExcel.writerSheet(0, "Sheet1")
                // 字体表格样式工具类,下方展示
                .registerWriteHandler(horizontalCellStyleStrategyBuilder())
                // 合并行。第一个参数:从第几行开始合并、第二个参数:合并哪几列、第三个参数:到哪一行为止。
                // 这一行的意思是:从第0行开始合并第0列,到第2行为止。也就是合并了第0行和第1行的第0列。(注意这里必须合并列的内容相同才能够合并)
                .registerWriteHandler(new ExcelFillCellMergeStrategyUtil(0,new int[]{0}, 2))
                // 合并列。
                .registerWriteHandler(mergePrevCol1)
                .build();
        // 设置Excel表头
        // Collections.singletonList("") 这样设置表头的第0列才能与第1行的第0列合并。使用Arrays.asList("")是不行的。
        sheet1.setHead(Arrays.asList(Collections.singletonList(""),Arrays.asList("1#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3#加热炉"),Arrays.asList("1"),Arrays.asList("2")));
        // 写入数据
        // list1的类型为List>
        excelWriter.write(list1, sheet1);
    
        // 创建第二个sheet的列合并工具类对象
        ExcelFillCellMergePrevColUtil mergePrevCol2 = new ExcelFillCellMergePrevColUtil();
        mergePrevCol2.add(0,1,3);
        mergePrevCol2.add(0,5,3);
    
        // 写入第二个sheet的数据
        WriteSheet sheet2 = EasyExcel.writerSheet(1, "Sheet2")
                .registerWriteHandler(horizontalCellStyleStrategyBuilder()) // 字体表格样式工具类,下方展示
                .registerWriteHandler(new ExcelFillCellMergeStrategyUtil(0,new int[]{0}, 2))
                .registerWriteHandler(mergePrevCol2)
                .build();
        sheet2.setHead(Arrays.asList(Collections.singletonList(""),Arrays.asList("2#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3"),Arrays.asList("4#加热炉"),Arrays.asList("1"),Arrays.asList("2"),Arrays.asList("3")));
         // list2的类型为List>
        excelWriter.write(list2, sheet2);
    
        // 关闭ExcelWriter
        excelWriter.finish();
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    效果图
    在这里插入图片描述
    在这里插入图片描述


    总结

    阿巴阿巴…

    文章内容就到这里结尾了,整个过程鄙人亲力亲为。如果你的需求相同,又还是无法达到想要的效果,抑或是出现了什么问题,欢迎找笔者沟通!!!

    此外,本篇文章中单元格合并的工具类等代码,并非我自己亲自完成,如有侵权,请及时联系笔者。

  • 相关阅读:
    函数调用方式_stdcall 、 _cdecl 、_thiscall
    已知 list 数组请写出一段代码,实现功能: 数组内type 相同的元素只保留 votes 最大的元素,并且元素需要按照 votes 从大到小排序。
    Linux下用C语言实现<<图书管理系统>>
    什么是死锁、定位和修复
    【JavaEE重点知识归纳】第10节:Object类和String类
    基于蒙特卡洛的大规模电动汽车充电行为分析(Matlab代码实现)
    Groq:从头设计一个张量流式处理器架构
    NFT 的潜力:扩展的艺术品鉴定证书
    MP3文件的构成
    一道小学一年级数学题,国产大模型全军覆没
  • 原文地址:https://blog.csdn.net/qq_53574266/article/details/134334909