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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!