diff --git a/fastexcel-core/src/main/java/cn/idev/excel/write/merge/DynamicMergeStrategy.java b/fastexcel-core/src/main/java/cn/idev/excel/write/merge/DynamicMergeStrategy.java new file mode 100644 index 000000000..835a040ad --- /dev/null +++ b/fastexcel-core/src/main/java/cn/idev/excel/write/merge/DynamicMergeStrategy.java @@ -0,0 +1,94 @@ +package cn.idev.excel.write.merge; + +import cn.idev.excel.write.handler.RowWriteHandler; +import cn.idev.excel.write.handler.context.RowWriteHandlerContext; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.util.CellRangeAddress; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * @Description Specifies that the column merges adjacent cells with the same content + * @Date 2025/3/8 + */ +public class DynamicMergeStrategy implements RowWriteHandler { + + /** + * You want to merge columns that are adjacent to the same cell data + */ + private final int columnIndex; + /** + * Extend column + */ + private final int columnExtend; + /** + * size of collection date + */ + private final int dataSize; + private final Deque rowStack = new ArrayDeque<>(); + + public DynamicMergeStrategy(int columnIndex, int dataSize) { + this(columnIndex, 1, dataSize); + } + + public DynamicMergeStrategy(int columnIndex, int columnExtend, int dataSize) { + if (columnExtend < 1) { + throw new IllegalArgumentException("ColumnExtend must be greater than 1"); + } + if (columnIndex < 0) { + throw new IllegalArgumentException("ColumnIndex must be greater than 0"); + } + if (dataSize <= 0) { + throw new IllegalArgumentException("dataSize must be greater than 0"); + } + this.columnIndex = columnIndex; + this.columnExtend = columnExtend; + this.dataSize = dataSize; + + } + + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + if (context.getHead() || context.getRelativeRowIndex() == null) { + return; + } + Row row = context.getRow(); + rowStack.push(new MergeRow(row, context.getRelativeRowIndex())); + if (context.getRelativeRowIndex() == (dataSize - 1)) { + while (!rowStack.isEmpty()) { + MergeRow lastRow = rowStack.pop(); + while (!rowStack.isEmpty()) { + MergeRow prevRow = rowStack.pop(); + + if (!prevRow.getRow().getCell(columnIndex).getStringCellValue().equals(lastRow.getRow().getCell(columnIndex).getStringCellValue())) { + if (lastRow.getRow().getRowNum() != (prevRow.getRow().getRowNum() + 1)) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(prevRow.getRow().getRowNum() + 1, + lastRow.getRow().getRowNum(), columnIndex, columnIndex + columnExtend - 1); + context.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(cellRangeAddress); + } + rowStack.push(prevRow); + break; + } else { + if (prevRow.getRelativeRowIndex().equals(0)) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(prevRow.getRow().getRowNum(), + lastRow.getRow().getRowNum(), columnIndex, columnIndex + columnExtend - 1); + context.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(cellRangeAddress); + } + } + } + + } + } + + } + + @Data + @AllArgsConstructor + public static class MergeRow { + private Row row; + private Integer relativeRowIndex; + } +} diff --git a/fastexcel-test/src/test/java/cn/idev/excel/test/demo/write/WriteTest.java b/fastexcel-test/src/test/java/cn/idev/excel/test/demo/write/WriteTest.java index 096b80d5b..b17538479 100644 --- a/fastexcel-test/src/test/java/cn/idev/excel/test/demo/write/WriteTest.java +++ b/fastexcel-test/src/test/java/cn/idev/excel/test/demo/write/WriteTest.java @@ -8,6 +8,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.Map; +import java.util.HashMap; import cn.idev.excel.EasyExcel; import cn.idev.excel.ExcelWriter; @@ -18,6 +20,7 @@ import cn.idev.excel.util.ListUtils; import cn.idev.excel.write.handler.CellWriteHandler; import cn.idev.excel.write.handler.context.CellWriteHandlerContext; +import cn.idev.excel.write.merge.DynamicMergeStrategy; import cn.idev.excel.write.merge.LoopMergeStrategy; import cn.idev.excel.write.style.HorizontalCellStyleStrategy; import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; @@ -457,7 +460,7 @@ public void handlerStyleWrite() { // 背景设置为红色 headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); WriteFont headWriteFont = new WriteFont(); - headWriteFont.setFontHeightInPoints((short)20); + headWriteFont.setFontHeightInPoints((short) 20); headWriteCellStyle.setWriteFont(headWriteFont); // 内容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); @@ -467,7 +470,7 @@ public void handlerStyleWrite() { contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); WriteFont contentWriteFont = new WriteFont(); // 字体大小 - contentWriteFont.setFontHeightInPoints((short)20); + contentWriteFont.setFontHeightInPoints((short) 20); contentWriteCellStyle.setWriteFont(contentWriteFont); // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 HorizontalCellStyleStrategy horizontalCellStyleStrategy = @@ -613,6 +616,154 @@ public void dynamicHeadWrite() { .doWrite(data()); } + /** + * 动态头,实时生成头写入 + * 指定列值相同时合并 + */ + @Test + public void customHeadReadAndDynamicMergeStrategy() { + List> maps = yearData(); + EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx") + .head(yearHead()) + .sheet("模板") + .registerWriteHandler(new DynamicMergeStrategy(0, maps.size())) + .registerWriteHandler(new DynamicMergeStrategy(2, maps.size())) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + .doWrite(maps); + + List> maps1 = yearData1(); + EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx") + .head(yearHead()) + .sheet("模板") + .registerWriteHandler(new DynamicMergeStrategy(0, maps1.size())) + .registerWriteHandler(new DynamicMergeStrategy(2, maps1.size())) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + .doWrite(maps1); + List> maps2 = yearData2(); + EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx") + .head(yearHead()) + .sheet("模板") + .registerWriteHandler(new DynamicMergeStrategy(0, maps2.size())) + .registerWriteHandler(new DynamicMergeStrategy(2, maps2.size())) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + .doWrite(maps2); + List> maps3 = yearData3(); + EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx") + .head(yearHead()) + .sheet("模板") + .registerWriteHandler(new DynamicMergeStrategy(0, maps3.size())) + .registerWriteHandler(new DynamicMergeStrategy(2, maps3.size())) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + .doWrite(maps3); + + List> maps4 = yearData4(); + EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx") + .head(yearHead()) + .sheet("模板") + .registerWriteHandler(new DynamicMergeStrategy(0, maps4.size())) + .registerWriteHandler(new DynamicMergeStrategy(2, maps4.size())) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + .doWrite(maps4); + } + + public List> yearHead() { + List> head = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + List h = new ArrayList<>(); + if (i < 3) { + h.add("第一季度"); + } + if (i >= 3 && i < 6) { + h.add("第二季度"); + } + if (i >= 6 && i < 9) { + h.add("第三季度"); + } + if (i >= 9) { + h.add("第四季度"); + } + h.add("第" + (i + 1) + "月"); + head.add(h); + } + return head; + } + + public List> yearData() { + List> data = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + Map map = new HashMap<>(); + for (int j = 0; j < 12; j++) { + if (i < 20) { + map.put(j, "第" + (j + 1) + "月" + "前20条数据"); + } else { + map.put(j, "第" + (j + 1) + "月"); + } + + } + data.add(map); + } + return data; + } + + public List> yearData1() { + List> data = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Map map = new HashMap<>(); + for (int j = 0; j < 12; j++) { + if (i > 0) { + map.put(j, (j + 1) + ""); + } else { + map.put(j, i + ""); + } + + } + data.add(map); + } + return data; + } + + public List> yearData4() { + List> data = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Map map = new HashMap<>(); + for (int j = 0; j < 12; j++) { + if (i < 2) { + map.put(j, (j + 1) + ""); + } else { + map.put(j, i + ""); + } + + } + data.add(map); + } + return data; + } + + public List> yearData3() { + List> data = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Map map = new HashMap<>(); + for (int j = 0; j < 12; j++) { + map.put(j, j + ""); + + } + data.add(map); + } + return data; + } + + public List> yearData2() { + List> data = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Map map = new HashMap<>(); + for (int j = 0; j < 12; j++) { + map.put(j, i + ""); + } + data.add(map); + } + return data; + } + /** * 自动列宽(不太精确) *