diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/annotation/ExcelCell.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/annotation/ExcelCell.java index 1da9ea1a..ffd8c67b 100644 --- a/core-parent/poi/src/main/java/icu/easyj/poi/excel/annotation/ExcelCell.java +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/annotation/ExcelCell.java @@ -22,6 +22,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import icu.easyj.poi.excel.hook.ICellMerger; +import icu.easyj.poi.excel.hook.NoneCellMerger; + /** * excel表格的列对应属性的注解 * @@ -58,6 +61,9 @@ // 格式化(应用于Date数据、浮点数据等等的格式化输出) String format() default ""; + // 合并单元格控制器 + Class> cellMergerClass() default NoneCellMerger.class; + // 显示效果相关属性 @@ -81,4 +87,5 @@ // 是否隐藏列 boolean hidden() default false; + } diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/ICellMerger.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/ICellMerger.java new file mode 100644 index 00000000..63663640 --- /dev/null +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/ICellMerger.java @@ -0,0 +1,17 @@ +package icu.easyj.poi.excel.hook; + +import icu.easyj.poi.excel.model.ExcelCellMapping; + +public interface ICellMerger { + + /** + * 是否合并单元格 + * + * @param cellMapping 当前单元格 + * @param previous 上一条数据 + * @param current 当前数据 + * @return 是否合并:true=合并 | false=不合并 + */ + boolean needMerge(ExcelCellMapping cellMapping, T previous, T current); + +} diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/NoneCellMerger.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/NoneCellMerger.java new file mode 100644 index 00000000..c27b2643 --- /dev/null +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/hook/NoneCellMerger.java @@ -0,0 +1,12 @@ +package icu.easyj.poi.excel.hook; + +import icu.easyj.poi.excel.model.ExcelCellMapping; + +public class NoneCellMerger implements ICellMerger { + + @Override + public boolean needMerge(ExcelCellMapping cellMapping, Object previous, Object current) { + return false; + } + +} diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/model/ExcelCellMapping.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/model/ExcelCellMapping.java index 3b2c8f0b..31355ca4 100644 --- a/core-parent/poi/src/main/java/icu/easyj/poi/excel/model/ExcelCellMapping.java +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/model/ExcelCellMapping.java @@ -30,6 +30,7 @@ import icu.easyj.poi.excel.annotation.Excel; import icu.easyj.poi.excel.annotation.ExcelCell; import icu.easyj.poi.excel.annotation.ExcelCells; +import icu.easyj.poi.excel.hook.ICellMerger; import icu.easyj.poi.excel.style.ExcelFormats; import icu.easyj.poi.excel.util.ExcelColorUtils; import org.apache.commons.lang3.ArrayUtils; @@ -68,6 +69,8 @@ public class ExcelCellMapping implements Serializable { private String falseText; // boolean型数据为false时,显示的文字 private Map convertMap; // 值转换 private Map convertMap2; // 值反向转换 + private Class> cellMergerClass; + //******************************** getter and setter *********************************/ @@ -231,6 +234,18 @@ public void setConvertMap2(Map convertMap2) { this.convertMap2 = convertMap2; } + public Class> getCellMergerClass() { + return cellMergerClass; + } + + public void setCellMergerClass(Class> cellMergerClass) { + this.cellMergerClass = cellMergerClass; + } + + public String getColumnFromFieldOrColumn() { + return StringUtils.isNotBlank(column) ? column : field.getName(); + } + //******************************** static *********************************/ @@ -382,6 +397,7 @@ public static ExcelCellMapping toMapping(ExcelCell anno, Class clazz, Field f cellMapping.setConvertMap(convertMap); cellMapping.setConvertMap2(convertMap2); } + cellMapping.setCellMergerClass(anno.cellMergerClass()); // 处理映射信息,初始化部分情况的映射内容 if (StringUtils.isEmpty(cellMapping.getFormat())) { diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelRowUtils.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelRowUtils.java index f9156d79..2a665ef9 100644 --- a/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelRowUtils.java +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelRowUtils.java @@ -22,6 +22,8 @@ import icu.easyj.core.util.ArrayUtils; import icu.easyj.core.util.ReflectionUtils; import icu.easyj.core.util.StringUtils; +import icu.easyj.poi.excel.hook.ICellMerger; +import icu.easyj.poi.excel.hook.NoneCellMerger; import icu.easyj.poi.excel.model.ExcelCellMapping; import icu.easyj.poi.excel.model.ExcelMapping; import org.apache.poi.ss.usermodel.Cell; @@ -336,50 +338,105 @@ public static void createDataRows(Sheet sheet, List dataList, ExcelMapping ma /** * @since 0.7.8 */ - public static void mergeSameCells(Sheet sheet, ExcelMapping mapping) { - if (ArrayUtils.isEmpty(mapping.getMergeSameCells())) { - return; // 未定义需要合并的单元格 + public static void mergeSameCells(Sheet sheet, List dataList, ExcelMapping mapping) { + if (dataList.size() < 2) { + return; } - String[] mergeFieldNames = mapping.getMergeSameCells(); - int[] mergeCellNums = getMergeCellNums(mapping, mergeFieldNames); + if (ArrayUtils.isNotEmpty(mapping.getMergeSameCells())) { + //region 方案1:指定列合并单元格 - // 列号进行排序 - Arrays.sort(mergeCellNums); + String[] mergeFieldNames = mapping.getMergeSameCells(); + int[] mergeCellNums = getMergeCellNums(mapping, mergeFieldNames); - if (mapping.isNeedNumberCell()) { - for (int i = 0; i < mergeCellNums.length; i++) { - mergeCellNums[i]++; + // 列号进行排序 + Arrays.sort(mergeCellNums); + + if (mapping.isNeedNumberCell()) { + for (int i = 0; i < mergeCellNums.length; i++) { + mergeCellNums[i]++; + } } - } - int mergeStartRowNum = -1; - int mergeEndRowNum = -1; - int startRowNum = mapping.isNeedHeadRow() ? 2 : 1; - for (int i = startRowNum; i < sheet.getPhysicalNumberOfRows(); i++) { - if (isSameCells(sheet.getRow(i - 1), sheet.getRow(i), mergeCellNums)) { - if (mergeStartRowNum == -1) { - mergeStartRowNum = i - 1; - mergeEndRowNum = i; - } else { - mergeEndRowNum++; + int mergeStartRowNum = -1; + int mergeEndRowNum = -1; + int startRowNum = mapping.isNeedHeadRow() ? 2 : 1; + for (int i = startRowNum; i < sheet.getPhysicalNumberOfRows(); i++) { + if (isSameCells(sheet.getRow(i - 1), sheet.getRow(i), mergeCellNums)) { + if (mergeStartRowNum == -1) { + mergeStartRowNum = i - 1; + mergeEndRowNum = i; + } else { + mergeEndRowNum++; + } + continue; + } + + if (mergeStartRowNum >= 0) { + for (int mergeCellNum : mergeCellNums) { + sheet.addMergedRegion(new CellRangeAddress(mergeStartRowNum, mergeEndRowNum, mergeCellNum, mergeCellNum)); + } + mergeStartRowNum = -1; + mergeEndRowNum = -1; } - continue; } if (mergeStartRowNum >= 0) { for (int mergeCellNum : mergeCellNums) { sheet.addMergedRegion(new CellRangeAddress(mergeStartRowNum, mergeEndRowNum, mergeCellNum, mergeCellNum)); } - mergeStartRowNum = -1; - mergeEndRowNum = -1; } - } - if (mergeStartRowNum >= 0) { - for (int mergeCellNum : mergeCellNums) { - sheet.addMergedRegion(new CellRangeAddress(mergeStartRowNum, mergeEndRowNum, mergeCellNum, mergeCellNum)); + //endregion 方案1:指定列合并单元格 end + } else { + //region 方案2:自定义合并单元格 + + for (ExcelCellMapping cellMapping : mapping.getCellMappingList()) { + if (cellMapping.getCellMergerClass() == null || cellMapping.getCellMergerClass() == NoneCellMerger.class) { + continue; + } + + int cellNum = cellMapping.getCellNum() + (mapping.isNeedNumberCell() ? 1 : 0); + ICellMerger cellMerger = ReflectionUtils.getSingleton(cellMapping.getCellMergerClass()); + + int mergeStartRowNum = -1; + int mergeEndRowNum = -1; + int startRowNum = mapping.isNeedHeadRow() ? 2 : 1; + if (mapping.isNeedHeadRow()) { + Integer headRowNum = ExcelUtils.findHeadRowNum(sheet, 0, mapping); + if (headRowNum != null) { + startRowNum = headRowNum + 2; + } + } + + + for (int i = startRowNum; i < sheet.getPhysicalNumberOfRows(); i++) { + Object previous = dataList.get(i - startRowNum); + Object current = dataList.get(i - startRowNum + 1); + + if (cellMerger.needMerge(cellMapping, previous, current)) { + if (mergeStartRowNum == -1) { + mergeStartRowNum = i - 1; + mergeEndRowNum = i; + } else { + mergeEndRowNum++; + } + continue; + } + + if (mergeStartRowNum >= 0) { + sheet.addMergedRegion(new CellRangeAddress(mergeStartRowNum, mergeEndRowNum, cellNum, cellNum)); + mergeStartRowNum = -1; + mergeEndRowNum = -1; + } + } + + if (mergeStartRowNum >= 0) { + sheet.addMergedRegion(new CellRangeAddress(mergeStartRowNum, mergeEndRowNum, cellNum, cellNum)); + } } + + //endregion } } diff --git a/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelUtils.java b/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelUtils.java index 6b960f5f..644089ec 100644 --- a/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelUtils.java +++ b/core-parent/poi/src/main/java/icu/easyj/poi/excel/util/ExcelUtils.java @@ -216,7 +216,7 @@ public static boolean getHasNumberCell(Sheet sheet, ExcelMapping mapping) { * @return 头行号 */ @Nullable - private static Integer findHeadRowNum(Sheet sheet, int firstRowNum, ExcelMapping mapping) { + static Integer findHeadRowNum(Sheet sheet, int firstRowNum, ExcelMapping mapping) { // 只检测前3行 int i = 0; while (i < 3) { @@ -261,7 +261,7 @@ private static Sheet generateSheet(Workbook book, List dataList, ExcelMapping // 创建数据行 ExcelRowUtils.createDataRows(sheet, dataList, mapping); // 合并单元格 @since 0.7.8 - ExcelRowUtils.mergeSameCells(sheet, mapping); + ExcelRowUtils.mergeSameCells(sheet, dataList, mapping); } // 触发勾子:afterCreateDataRows diff --git a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/ExcelUtilsTest.java b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/ExcelUtilsTest.java index 4006d17f..55146c39 100644 --- a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/ExcelUtilsTest.java +++ b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/ExcelUtilsTest.java @@ -101,6 +101,9 @@ public void testMapToExcel() throws Exception { // list to excel List list = new ArrayList<>(); list.add(new TestClass("aaa", 1, 0, new Date(2020 - 1900, 1 - 1, 1), "desc111", Long.parseLong(Integer.MIN_VALUE + ""), 1.1D, new BigDecimal("1.1"))); + list.add(new TestClass("aaa", 1, 0, new Date(2020 - 1900, 1 - 1, 1), "desc111", Long.parseLong(Integer.MIN_VALUE + ""), 1.1D, new BigDecimal("1.1"))); + list.add(new TestClass("aaa", 0, 0, new Date(2020 - 1900, 1 - 1, 1), "desc111", Long.parseLong(Integer.MIN_VALUE + ""), 1.1D, new BigDecimal("1.1"))); + list.add(new TestClass("aaa", 0, 0, new Date(2020 - 1900, 1 - 1, 1), "desc111", Long.parseLong(Integer.MIN_VALUE + ""), 1.1D, new BigDecimal("1.1"))); list.add(new TestClass("bbb", 2, 1, new Date(2019 - 1900, 2 - 1, 2), "desc222", Long.parseLong(Integer.MAX_VALUE + ""), -1.1D, new BigDecimal("-1.1"))); list.add(new TestClass("ccc", 3, 2, new Date(2018 - 1900, 3 - 1, 3), "desc333", Long.MIN_VALUE, -Double.MAX_VALUE, new BigDecimal(-Double.MAX_VALUE))); list.add(new TestClass("ddd", 4, 3, new Date(2017 - 1900, 4 - 1, 4), "desc444", Long.MAX_VALUE, Double.MAX_VALUE, new BigDecimal(Double.MAX_VALUE))); diff --git a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/hook/MyCellMerger.java b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/hook/MyCellMerger.java new file mode 100644 index 00000000..47f3c9cd --- /dev/null +++ b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/hook/MyCellMerger.java @@ -0,0 +1,29 @@ +package icu.easyj.poi.excel.util.hook; + +import java.util.Objects; + +import icu.easyj.poi.excel.hook.ICellMerger; +import icu.easyj.poi.excel.model.ExcelCellMapping; +import icu.easyj.poi.excel.util.model.TestClass; + +public class MyCellMerger implements ICellMerger { + + @Override + public boolean needMerge(ExcelCellMapping cellMapping, TestClass previous, TestClass current) { + switch (cellMapping.getColumnFromFieldOrColumn()) { + case "name": + return Objects.equals(previous.getName(), current.getName()); + case "age": + return Objects.equals(previous.getName(), current.getName()) && + Objects.equals(previous.getAge(), current.getAge()); + case "bClass.age": + return Objects.equals(previous.getName(), current.getName()) && + Objects.equals( + previous.getbClass() == null ? null : previous.getbClass().getAge(), + current.getbClass() == null ? null : current.getbClass().getAge() + ); + default: + return false; + } + } +} diff --git a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestBClass.java b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestBClass.java index 5609df44..b5288bf3 100644 --- a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestBClass.java +++ b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestBClass.java @@ -49,4 +49,12 @@ public Integer getAge() { public void setAge(Integer age) { this.age = age; } + + @Override + public String toString() { + return "TestBClass{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } } diff --git a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestClass.java b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestClass.java index 8ec9ab90..fb091acd 100644 --- a/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestClass.java +++ b/core-parent/poi/src/test/java/icu/easyj/poi/excel/util/model/TestClass.java @@ -21,6 +21,7 @@ import icu.easyj.poi.excel.annotation.Excel; import icu.easyj.poi.excel.annotation.ExcelCell; import icu.easyj.poi.excel.annotation.ExcelCustomRowConfig; +import icu.easyj.poi.excel.util.hook.MyCellMerger; import icu.easyj.poi.excel.util.hook.TestListToExcelHook; /** @@ -31,18 +32,18 @@ @Excel( toExcelHookClasses = {TestListToExcelHook.class}, customFirstRow = @ExcelCustomRowConfig(fontSize = 20, fontBold = false, rowHeight = 40, align = "left", verAlign = "top"), - showFooterRow = true, - mergeSameCells = {"name", "age", "bClass.age"} + showFooterRow = true + //mergeSameCells = {"name", "age", "bClass.age"} ) public class TestClass { - @ExcelCell(headName = "姓名", cellNum = 0) + @ExcelCell(headName = "姓名", cellNum = 0, cellMergerClass = MyCellMerger.class) private String name; - @ExcelCell(headName = "年龄", cellNum = 1) + @ExcelCell(headName = "年龄", cellNum = 1, cellMergerClass = MyCellMerger.class) private Integer age; - @ExcelCell(headName = "周岁", cellNum = 2, column = "age") + @ExcelCell(headName = "周岁", cellNum = 2, column = "age", cellMergerClass = MyCellMerger.class) private TestBClass bClass; @ExcelCell(headName = "出生日期", cellNum = 3, format = "yyyy-MM-dd")