EasyExcel 导出数据,无模版,动态多级表头。
效果图
我这个表前六列是写死的,后面全都是动态的,支持无限横竖扩容。
核心代码
public void listExport(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/vnd.ms-excel");
String fileName = URLEncoder.encode(EVALUATION_DATA_EXPORT + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")) + ".xls", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
//内容样式策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//垂直居中,水平居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
contentWriteCellStyle.setShrinkToFit(Boolean.TRUE);
//设置 自动换行
contentWriteCellStyle.setWrapped(true);
// 字体策略
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short) 12);
contentWriteCellStyle.setWriteFont(contentWriteFont);
//头策略使用默认
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//表头
List<List<String>> lists = new ArrayList<>();
getMyEvaluationHeader(myEvaluationScoreResultModel.getMyEvaluationHeader(), lists);
//数据
List<List<String>> myEvaluationData = getMyEvaluationData(myEvaluationScoreResultModel);
EasyExcel.write(response.getOutputStream())
.head(lists)
.excelType(ExcelTypeEnum.XLS)
.needHead(true)
.sheet("data")
.registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle))
.useDefaultStyle(true)
.doWrite(myEvaluationData);
}
private void getMyEvaluationHeader(List<PerformanceItemModel> myEvaluationHeader, List<List<String>> lists) {
lists.add(Stream.of("姓名").collect(Collectors.toList()));
lists.add(Stream.of("工号").collect(Collectors.toList()));
lists.add(Stream.of("年级").collect(Collectors.toList()));
lists.add(Stream.of("学科").collect(Collectors.toList()));
lists.add(Stream.of("评估模版").collect(Collectors.toList()));
lists.add(Stream.of("自评状态").collect(Collectors.toList()));
List<List<String>> allPaths = getAllPaths(myEvaluationHeader);
lists.addAll(allPaths);
int maxSize = -1;
for (List<String> allPath : allPaths) {
if(allPath.size() > maxSize){
maxSize = allPath.size();
}
}
for (List<String> allPath : lists) {
int size = maxSize - allPath.size();
String indexName = allPath.get(allPath.size() - 1);
for (int i = 0; i < size; i++) {
allPath.add(indexName);
}
}
}
public static List<List<String>> getAllPaths(List<PerformanceItemModel> tree) {
List<List<String>> paths = new ArrayList<>();
List<String> currentPath = new ArrayList<>();
traverseTree(tree, currentPath, paths);
return paths;
}
private static void traverseTree(List<PerformanceItemModel> tree, List<String> currentPath, List<List<String>> paths) {
if (tree == null || tree.isEmpty()) {
return;
}
for (PerformanceItemModel node : tree) {
currentPath.add(node.getItemName()); // 添加当前节点到路径中
if (node.getPerformanceItemModelList() != null && !node.getPerformanceItemModelList().isEmpty()) {
traverseTree(node.getPerformanceItemModelList(), currentPath, paths); // 递归遍历子节点
} else {
paths.add(new ArrayList<>(currentPath)); // 当到达叶子节点时,添加路径
}
currentPath.remove(currentPath.size() - 1); // 回溯以探索其他路径
}
}
主要是使用 .head(lists) 实现动态表头 .doWrite(myEvaluationData) 填充数据。数据和表头都要包装成二维数组 List<List>。
其中表头格式会复杂一点,我这里的原始表头数据是树形结构,需要转化为二维数组,二维数组的高是树的高度。二维数组的宽是列的数量。
核心代码是 getAllPaths() 方法,这个方法会遍历树的所有路径,并返回 所有路径的 List<List> allPaths。然后测量树的高度,填充二维数组 allPaths 的空缺位置(这个很重要,这是合并单元格的关键)。
这是debug的截图,结合理解下。
表头:
[[姓名, 姓名, 姓名, 姓名],
[工号, 工号, 工号, 工号],
[年级, 年级, 年级, 年级],
[学科, 学科, 学科, 学科],
[评估模版, 评估模版, 评估模版, 评估模版],
[自评状态, 自评状态, 自评状态, 自评状态],
[备课, 早上备课, 早上备课, 早上备课],
[备课, 中午备课, 中午备课, 中午备课],
[备课, 下午备课, 下午备课, 下午备课],
[备课, 晚上备课, 晚上备课, 晚上备课],
[备课, 单项小计, 单项小计, 单项小计],
[吃饭, 吃早饭, 吃包子, 吃酸菜包],
[吃饭, 吃早饭, 吃包子, 单项小计],
[吃饭, 吃早饭, 单项小计, 单项小计],
[吃饭, 吃午饭, 吃午饭, 吃午饭],
[吃饭, 单项小计, 单项小计, 单项小计]]
数据: