概要
关于execl静态表头+动态表头的一些想法和实践。由于项目中需要导出execl的静态表头+动态表头,于是便记录了遇见的问题和一些解决问题的思路。
execl版本
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.7</version>
</dependency>
easyexcel官网对动态表头的案例
把其中最重要的部分标红了,动态表头和数据是分开写的。
思考1: 动态表头写入和数据如何映射正确并写入正确。
思考2: 业务场景中是否存在导出的字段是横表和竖表。如果为竖表导出的业务场景可能更复杂,表头和数据的映射也为更复杂。
ps: 横表和竖表的区别简单解释下
横表
+----+-------+-------+
| id | col1 | col2 |
+----+-------+-------+
| 1 | val1 | val2 |
| 2 | val3 | val4 |
+----+-------+-------+
竖表
+----+-------+-----+
| id | attr | val |
+----+-------+-----+
| 1 | col1 | val1|
| 2 | col2 | val2|
| 3 | col1 | val3|
| 4 | col2 | val4|
+----+-------+-----+
业务场景分析
目前是以竖表的场景来解决的,因为竖表可以包含横表的方式
先写入表头的部分,后写入数据。相当于需要在代码逻辑处理好映射关系,保证表头和数据能够映射上,并且不会错位。上图
案例1
如果表头1、表头2、表头3…表头8,其中表头1、表头2、表头3、表头4为静态表头、后面的为动态表头(只是表头名称的变更)
案例2
如果表头1、表头2、表头3…表头8,其中表头1、表头2、表头3、表头4为静态表头、后面的为动态表头(不只是表头名称的变更,表头的数量随表字段变化而变化)
技术解决方案
解决思路:
- 可以将静态表头和动态表头都作为动态表头写入,并且保证写入顺序。
- 写入数据的时候,需要根据表头的写入顺序和数据一一映射。
- 如果导出存在多条记录,每一条对应的动态表头数据都不一致,
比如说第1条记录静态表头+动态表头一共有30个字段,第2条记录静态表头+动态表头一共有40个字段,第3条记录静态表头+动态表头一共有45个字段,第4条记录静态表头+动态表头一共有50个字段
,如果存在这种复杂的场景就需要先把动态表头的部分全部先取并集
,再和静态表头拼接起来,这样表头的部分才是完整的,但是也会存在表头和数据错位的问题,这种解决方式就需要动态扩容
,举个例子:所有数据动态表头部分取完并集后长度为30,加上静态表头20,一共就是50,第一条记录静态20+动态10=30,第二条记录静态20+动态20=并集40,第三条记录静态20+动态25=并集45,这个时候数据长度也不一样,就容易错位
,如图
写下表头和数据映射的核心代码,因为其他代码为组装业务数据没有参考意义
private static List<List<Object>> data(List<String> headList, List<List<ExportField>> exportDataList) {
Map<String, Integer> haedOrderMap = Maps.newLinkedHashMap();
IntStream.range(0, headList.size()).forEachOrdered(i -> {
String head = headList.get(i);
haedOrderMap.put(head, i);
});
List<List<ExportField>> orderData = Lists.newLinkedList();
// 每列
for (List<ExportField> fields : exportDataList) {
growList(headList.size(), fields);
List<ExportField> orderList = Lists.newLinkedList();
// 每行
IntStream.range(0, headList.size()).forEachOrdered(dataIndex -> {
ExportField export = fields.get(dataIndex);
Integer headIndex = haedOrderMap.getOrDefault(export.getPropertyName(), null);
boolean eq = Objects.equals(headIndex, dataIndex);
if (!eq) {
ExportField exportField = new ExportField();
exportField.setSort(dataIndex);
exportField.setFieldValue("");
orderList.add(exportField);
} else {
export.setSort(headIndex);
orderList.add(export);
}
});
orderData.add(orderList);
}
return orderData.stream()
.map(m -> m.stream().map(ExportField::getFieldValue).collect(Collectors.toList()))
.collect(Collectors.toList());
}
private static void growList(int headListSize, List<ExportField> fields) {
int fieldSize = fields.size();
if (fieldSize < headListSize) {
IntStream.range(fieldSize, headListSize).mapToObj(i -> {
ExportField exportField = new ExportField();
exportField.setSort(-1);
return Lists.newArrayList(exportField);
}).forEachOrdered(fields::addAll);
}
}
@Data
class ExportField {
private Long bizId;
private Long fieldId;
private String fieldName;
private Object fieldValue;
private int sort;
private String propertyName;
}
execl字体大小、样式的配置等等等
public static void main(String[] args) {
EasyExcelFactory.write("D:\\tmp\\" + "测试动态表头" + System.currentTimeMillis() + ".xlsx")
.registerWriteHandler(EasyExcelConfig.setConfigure())
.registerWriteHandler(new CustomRowHeightStrategy())
.registerWriteHandler(new CustomCellWidthStrategy())
.registerWriteHandler(new CustomSheetWriteHandler())
.head(head())
.sheet("sheet1")
.doWrite(data(Lists.newArrayList(), Lists.newArrayList()));
}
public class EasyExcelConfig {
// 配置字体,表头背景等
public static HorizontalCellStyleStrategy setConfigure() {
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景色
headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE1.getIndex());
WriteFont headWriteFont = new WriteFont();
// 加粗
headWriteFont.setBold(true);
headWriteFont.setFontHeightInPoints((short) 10);
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
// 设置单元格上边框为细线
headWriteCellStyle.setBorderTop(BorderStyle.THIN);
// 设置单元格右边框为细线
headWriteCellStyle.setBorderRight(BorderStyle.THIN);
// 设置单元格下边框为细线
headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
return new HorizontalCellStyleStrategy(headWriteCellStyle, getWriteCellStyle());
}
private static WriteCellStyle getWriteCellStyle() {
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 字体策略
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short) 8);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 导出数据垂直居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 导出数据水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
contentWriteCellStyle.setBorderLeft(BorderStyle.NONE);
contentWriteCellStyle.setBorderTop(BorderStyle.NONE);
contentWriteCellStyle.setBorderRight(BorderStyle.NONE);
contentWriteCellStyle.setBorderBottom(BorderStyle.NONE);
return contentWriteCellStyle;
}
}
/**
* 列宽
*/
public class CustomCellWidthStrategy extends AbstractHeadColumnWidthStyleStrategy {
/**
* 设置每一列的列宽
*/
@Override
protected Integer columnWidth(Head head, Integer columnIndex) {
return 18;
}
}
/**
* 行高
*/
public class CustomRowHeightStrategy extends AbstractRowHeightStyleStrategy {
/**
* 设置表头的行高
*/
@Override
protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
// 默认表头高度
row.setHeightInPoints(50);
}
/**
* 设置内容的行高
*/
@Override
protected void setContentColumnHeight(Row row, int relativeRowIndex) {
// 默认主体的高度
row.setHeightInPoints(30);
}
}
/**
* 行配置
*/
public class CustomSheetWriteHandler implements SheetWriteHandler {
public int colSplit = 0, rowSplit = 1, leftmostColumn = 0, topRow = 1;
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 得到Sheet
Sheet sheet = writeSheetHolder.getSheet();
// 设置冻结窗格第一行
sheet.createFreezePane(colSplit, rowSplit, leftmostColumn, topRow);
}
}
小结
如果需要完整的例子可留言有时间整个demo。
到这里就结束啦,祝大家生活愉快!