20220827 使用EasyExcel导出复杂表格

1、前言

突然有个比较复杂的表格导出需求,写着挺好玩的,记录一下。

网络上找了很多教程,最终使用EasyExcel官方文档和参考文档里的两篇实现了功能,感谢一下~

需要实现的表格如图所示:

先分析一下表格,可以看出整个表格大致分了四个区域:①第2-8行,②第9行,③第10-14行,④第15行。

其中,①和③类似(需要对单元格进行合并),②和④类似,改变了单元格style(这个地方的数据可以改成上一个区域的总结)。

初步想法是在一个sheet页面,通过多个table 解决(①②③④均为独立table)。

 

2、自定义合并单元格策略

2.1 初始化数据

①和③数据,模拟从数据库读取需要下载的列表信息 7个;

    private List<DownloadDTO> getFruitData() {
        List<DownloadDTO> returnList = new ArrayList<>();
        DownloadDTO d1 = new DownloadDTO();
        d1.setCategory("水果");
        d1.setFruit("苹果");
        d1.setColor("红色");
        d1.setProduceDate(new Date());

        DownloadDTO d2 = new DownloadDTO();
        BeanUtil.copyProperties(d1, d2);
        d2.setColor("绿色");

        DownloadDTO d3 = new DownloadDTO();
        BeanUtil.copyProperties(d1, d3);
        d2.setColor("白色");

        DownloadDTO t1 = new DownloadDTO();
        t1.setCategory("水果");
        t1.setFruit("香蕉");
        t1.setColor("黄色");
        t1.setProduceDate(new Date());

        DownloadDTO t2 = new DownloadDTO();
        BeanUtil.copyProperties(t1, t2);
        t2.setColor("青色");

        DownloadDTO w1 = new DownloadDTO();
        w1.setCategory("水果");
        w1.setFruit("西瓜");
        w1.setColor("绿色");
        w1.setProduceDate(new Date());

        DownloadDTO w2 = new DownloadDTO();
        BeanUtil.copyProperties(w1, w2);
        w2.setColor("黄色");

        returnList.add(d1);
        returnList.add(d2);
        returnList.add(d3);
        returnList.add(t1);
        returnList.add(t2);
        returnList.add(w1);
        returnList.add(w2);
        return returnList;
    }

②和④数据

    private static List<DemoData> data() {
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 1; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
//            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

分组合并单元格策略

   /**
    * 分组查询各个种类的数量,比如['3','2'],代表苹果这一组数据有三个,那么对应的第二列就应该合并三个单元格;
    * 香蕉这一组数据有两个,对应的第二列就应该合并两个单元格。
    * 使用LinkedHashMap保证合并表格顺序不乱
    */
    private List<Integer> getGroupData() {
        // 表格数据
        List<DownloadDTO> fruitList = getFruitData();
        // 需要分组合并列的数据
        List<Integer> groupList = new ArrayList<>();

        // 想要按照put顺序输出,我们可以采用LinkedHashMap(),它内部有一个链表,保持插入的顺序。迭代的时候,也是按照插入顺序迭代,而且迭代比HashMap快。
        Map<String, Integer> fruitMap = new LinkedHashMap<>();

        for (DownloadDTO fruit : fruitList) {
            //按照fruitName分组保存在map中
            fruitMap.merge(fruit.getFruit(), 1, Integer::sum);
        }
        for (Map.Entry<String, Integer> entry : fruitMap.entrySet()) {
            groupList.add(entry.getValue());
        }
        return groupList;
    }

2.2 表格合并策略

TableMergeStrategy继承AbstractMergeStrategy,重写merge方法实现自定义合并策略

由于没有找到读取同一sheet页每个表格的api,所以这里用了笨方法。

在excel写入数据时,使用tableIndex记录每个table第一行所在的index,方便写入CellRangeAddress坐标。

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import java.util.List;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

public class TableMergeStrategy extends AbstractMergeStrategy {

    // 表格数据
    private List<DownloadDTO> fruitList;
    // 表格分组合并数据
    private List<Integer> fruitGroupCount;
    private Sheet sheet;
    // 每个表格的第一行的index
    private Integer tableIndex = 1;

    public TableMergeStrategy(List<DownloadDTO> fruitList, List<Integer> fruitGroupCount) {
        this.fruitList = fruitList;
        this.fruitGroupCount = fruitGroupCount;
    }

    public TableMergeStrategy(List<DownloadDTO> fruitList, List<Integer> fruitGroupCount, Integer tableIndex) {
        this.fruitList = fruitList;
        this.fruitGroupCount = fruitGroupCount;
        this.tableIndex = tableIndex;
    }

    // 将该列全部合并成一个单元格
    private void mergeCommonColumn(Integer index) {
        System.out.println("tableIndex:"+tableIndex+"|fruitList.size():"+fruitList.size()+"|index:"+index);
        if (fruitList.size() <= 1) {
            return;
        }
        CellRangeAddress cellRangeAddress = new CellRangeAddress(tableIndex, tableIndex + fruitList.size() - 1, index, index);
        sheet.addMergedRegionUnsafe(cellRangeAddress);
    }

    // 按照分组将各种类别分别合并成一个单元格
    private void mergeGroupColumn(Integer index) {
        Integer rowCnt = tableIndex;
        for (Integer count : fruitGroupCount) {
            System.out.println("rowCnt:"+rowCnt+"|rowCnt + count - 1:"+(rowCnt + count - 1));
            // 只有一行的格子合并会报错,所以需要直接返回(不要忘了rowCnt需要增加1)
            if (count <= 1) {
                rowCnt += count;
                continue;
            }
            CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCnt, rowCnt + count - 1, index, index);
            sheet.addMergedRegionUnsafe(cellRangeAddress);
            rowCnt += count;
        }
    }

    @Override
    protected void merge(org.apache.poi.ss.usermodel.Sheet sheet, Cell cell, Head head, Integer integer) {
        this.sheet = sheet;
        // 当前行
        if (cell.getRowIndex() == tableIndex) {
            // 当前列
            switch (cell.getColumnIndex()) {
                case 0:
                    //第一列
                    this.mergeCommonColumn(0);
                    break;
                case 1:
                    //第二列
                    this.mergeGroupColumn(1);
                    break;
                case 2:
                    break;
                case 3:
                    this.mergeCommonColumn(3);
                    break;
                default:
                    break;
            }
        }

    }
}

2.3 开始写excel

为了多尝试一下,①②③④每个区域都设置成了不同的样式。

每个table的WriteTable都是独立的。(如果是分总的格式,可以循环写)

HorizontalCellStyleStrategy为样式实现

index很重要,用于记录每个表格的第一行,定位合并单元格的行数坐标

    @Test
    public void writeExcel03() {
//-------------初始化Excel-----------------------------------
        String fileName = FileUtils.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
	// 只需要在初始阶段设置表头,后面的表格都不需要表头
        ExcelWriter excelWriter = EasyExcel.write(fileName).excelType(ExcelTypeEnum.XLSX).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").head(DownloadDTO.class).needHead(Boolean.TRUE).build();
//-------------设置单元格样式-----------------------------------
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为红色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 20);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 背景绿色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
//        contentWriteFont.setFontHeightInPoints((short) 20);
//        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);

//-------------区域①开始-----------------------------------
        Integer index = 1;
        System.out.println("index1:"+index);

        TableMergeStrategy mergeStrategy = new TableMergeStrategy(getFruitData(), getGroupData(), index);

        // 需要表头设置为true,WriteTable一些属性会继承自WriteSheet
        WriteTable writeTable = EasyExcel.writerTable(0).head(DownloadDTO.class).needHead(Boolean.FALSE)
                .registerWriteHandler(mergeStrategy)
                .build();
        excelWriter.write(getFruitData(), writeSheet, writeTable);
//-------------区域①结束----------------------------------

//-------------区域②开始----------------------------------
        index = index + getFruitData().size();

        System.out.println("index2:"+index);
//        TableMergeStrategy mergeStrategy2 = new TableMergeStrategy(getFruitData1(), getGroupData1(),index);
        WriteTable writeTable2 = EasyExcel.writerTable(1).head(DemoData.class).needHead(Boolean.FALSE)
                .registerWriteHandler(horizontalCellStyleStrategy)
                .build();
        excelWriter.write(data(), writeSheet, writeTable2);
//-------------区域②结束----------------------------------

//-------------区域③开始----------------------------------
        index = index + data().size();

        System.out.println("index3:"+index);
        TableMergeStrategy mergeStrategy3 = new TableMergeStrategy(getFruitData2(), getGroupData2(),index);
        WriteTable writeTable3 = EasyExcel.writerTable(2).head(DownloadDTO.class).needHead(Boolean.FALSE)
                .registerWriteHandler(mergeStrategy3)
                .build();
        excelWriter.write(getFruitData2(), writeSheet, writeTable3);
//-------------区域③结束----------------------------------

//-------------区域④开始----------------------------------
        index = index + getFruitData2().size();

        System.out.println("index4:"+index);

        WriteTable writeTable4 = EasyExcel.writerTable(3).head(DemoData.class).needHead(Boolean.FALSE).build();
        excelWriter.write(data(), writeSheet, writeTable4);

        index = index + data().size();

        System.out.println("index5:"+index);
//-------------区域④结束----------------------------------
        // 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
    }

打开输出的excel文件就是例图啦,成功实现~

 

参考文档:

https://www.cnblogs.com/monianxd/p/16359369.html

https://www.jianshu.com/p/1bc1b9dccc52/

 

2022.08.27

posted @ 2022-08-27 17:36  MerryRose  阅读(3680)  评论(0编辑  收藏  举报