From ae5bec994d8c792dfa5ad5bd322e33834d98f02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E8=BE=9E=E6=9C=AA=E5=AF=92?= <545073804@qq.com> Date: Tue, 12 Aug 2025 18:26:18 +0800 Subject: [PATCH] =?UTF-8?q?upadte=20=E4=BC=98=E5=8C=96Excel=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=A0=BC=E5=90=88=E5=B9=B6=E4=BB=A3=E7=A0=81=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=98=8E=E7=A1=AE=E5=A4=84=E7=90=86=E7=B1=BB?= =?UTF-8?q?=E8=81=8C=E8=B4=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/excel/core/CellMergeHandler.java | 152 ++++++++++++++++++ .../common/excel/core/CellMergeStrategy.java | 151 ++--------------- 2 files changed, 169 insertions(+), 134 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java new file mode 100644 index 000000000..dd37b6795 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java @@ -0,0 +1,152 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.SneakyThrows; +import org.apache.poi.ss.util.CellRangeAddress; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.excel.annotation.CellMerge; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * 单元格合并处理器 + * + * @author Lion Li + */ +public class CellMergeHandler { + + private final boolean hasTitle; + private int rowIndex; + + private CellMergeHandler(final boolean hasTitle) { + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + } + + @SneakyThrows + public List handle(List list) { + List cellList = new ArrayList<>(); + if (CollUtil.isEmpty(list)) { + return cellList; + } + Class clazz = list.get(0).getClass(); + boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class); + Field[] fields = ReflectUtils.getFields(clazz, field -> { + if ("serialVersionUID".equals(field.getName())) { + return false; + } + if (field.isAnnotationPresent(ExcelIgnore.class)) { + return false; + } + return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class); + }); + + // 有注解的字段 + List mergeFields = new ArrayList<>(); + List mergeFieldsIndex = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (field.isAnnotationPresent(CellMerge.class)) { + CellMerge cm = field.getAnnotation(CellMerge.class); + mergeFields.add(field); + mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + } + + Map map = new HashMap<>(); + // 生成两两合并单元格 + for (int i = 0; i < list.size(); i++) { + Object rowObj = list.get(i); + for (int j = 0; j < mergeFields.size(); j++) { + Field field = mergeFields.get(j); + Object val = ReflectUtils.invokeGetter(rowObj, field.getName()); + + int colNum = mergeFieldsIndex.get(j); + if (!map.containsKey(field)) { + map.put(field, new RepeatCell(val, i)); + } else { + RepeatCell repeatCell = map.get(field); + Object cellValue = repeatCell.value(); + if (cellValue == null || "".equals(cellValue)) { + // 空值跳过不合并 + continue; + } + + if (!cellValue.equals(val)) { + if ((i - repeatCell.current() > 1)) { + cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } else if (i == list.size() - 1) { + if (!isMerge(list, i, field)) { + // 如果最后一行不能合并,检查之前的数据是否需要合并 + if (i - repeatCell.current() > 1) { + cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + } else if (i > repeatCell.current()) { + // 如果最后一行可以合并,则直接合并到最后 + cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex, colNum, colNum)); + } + } else if (!isMerge(list, i, field)) { + if ((i - repeatCell.current() > 1)) { + cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } + } + } + } + return cellList; + } + + private boolean isMerge(List list, int i, Field field) { + boolean isMerge = true; + CellMerge cm = field.getAnnotation(CellMerge.class); + final String[] mergeBy = cm.mergeBy(); + if (StrUtil.isAllNotBlank(mergeBy)) { + //比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真 + for (String fieldName : mergeBy) { + final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName); + final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName); + if (!Objects.equals(valPre, valCurrent)) { + //依赖字段如有任一不等值,则标记为不可合并 + isMerge = false; + } + } + } + return isMerge; + } + + record RepeatCell(Object value, int current) {} + + /** + * 创建一个单元格合并处理器实例 + * + * @param hasTitle 是否合并标题 + * @return 单元格合并处理器 + */ + public static CellMergeHandler of(final boolean hasTitle) { + return new CellMergeHandler(hasTitle); + } + + /** + * 创建一个单元格合并处理器实例(默认不合并标题) + * + * @return 单元格合并处理器 + */ + public static CellMergeHandler of() { + return new CellMergeHandler(false); + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java index 3e1ccfd67..34bf4a42f 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java @@ -1,26 +1,15 @@ package org.dromara.common.excel.core; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.metadata.Head; import cn.idev.excel.write.handler.WorkbookWriteHandler; import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext; import cn.idev.excel.write.merge.AbstractMergeStrategy; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; -import org.dromara.common.core.utils.reflect.ReflectUtils; -import org.dromara.common.excel.annotation.CellMerge; -import java.lang.reflect.Field; import java.util.*; /** @@ -32,145 +21,39 @@ import java.util.*; public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler { private final List cellList; - private final boolean hasTitle; - private int rowIndex; + + public CellMergeStrategy(List cellList) { + this.cellList = cellList; + } public CellMergeStrategy(List list, boolean hasTitle) { - this.hasTitle = hasTitle; - // 行合并开始下标 - this.rowIndex = hasTitle ? 1 : 0; - this.cellList = handle(list, hasTitle); + this.cellList = CellMergeHandler.of(hasTitle).handle(list); } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + if (CollUtil.isEmpty(cellList)){ + return; + } //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空 final int rowIndex = cell.getRowIndex(); - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress cellAddresses : cellList) { - final int firstRow = cellAddresses.getFirstRow(); - if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ - cell.setBlank(); - } + for (CellRangeAddress cellAddresses : cellList) { + final int firstRow = cellAddresses.getFirstRow(); + if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ + cell.setBlank(); } } } @Override public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) { + if (CollUtil.isEmpty(cellList)){ + return; + } //当前表格写完后,统一写入 - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress item : cellList) { - context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); - } + for (CellRangeAddress item : cellList) { + context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); } } - @SneakyThrows - private List handle(List list, boolean hasTitle) { - List cellList = new ArrayList<>(); - if (CollUtil.isEmpty(list)) { - return cellList; - } - Class clazz = list.get(0).getClass(); - boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class); - Field[] fields = ReflectUtils.getFields(clazz, field -> { - if ("serialVersionUID".equals(field.getName())) { - return false; - } - if (field.isAnnotationPresent(ExcelIgnore.class)) { - return false; - } - return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class); - }); - - // 有注解的字段 - List mergeFields = new ArrayList<>(); - List mergeFieldsIndex = new ArrayList<>(); - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - if (field.isAnnotationPresent(CellMerge.class)) { - CellMerge cm = field.getAnnotation(CellMerge.class); - mergeFields.add(field); - mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); - if (hasTitle) { - ExcelProperty property = field.getAnnotation(ExcelProperty.class); - rowIndex = Math.max(rowIndex, property.value().length); - } - } - } - - Map map = new HashMap<>(); - // 生成两两合并单元格 - for (int i = 0; i < list.size(); i++) { - Object rowObj = list.get(i); - for (int j = 0; j < mergeFields.size(); j++) { - Field field = mergeFields.get(j); - Object val = ReflectUtils.invokeGetter(rowObj, field.getName()); - - int colNum = mergeFieldsIndex.get(j); - if (!map.containsKey(field)) { - map.put(field, new RepeatCell(val, i)); - } else { - RepeatCell repeatCell = map.get(field); - Object cellValue = repeatCell.getValue(); - if (cellValue == null || "".equals(cellValue)) { - // 空值跳过不合并 - continue; - } - - if (!cellValue.equals(val)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } else if (i == list.size() - 1) { - if (!isMerge(list, i, field)) { - // 如果最后一行不能合并,检查之前的数据是否需要合并 - if (i - repeatCell.getCurrent() > 1) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - } else if (i > repeatCell.getCurrent()) { - // 如果最后一行可以合并,则直接合并到最后 - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); - } - } else if (!isMerge(list, i, field)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } - } - } - } - return cellList; - } - - private boolean isMerge(List list, int i, Field field) { - boolean isMerge = true; - CellMerge cm = field.getAnnotation(CellMerge.class); - final String[] mergeBy = cm.mergeBy(); - if (StrUtil.isAllNotBlank(mergeBy)) { - //比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真 - for (String fieldName : mergeBy) { - final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName); - final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName); - if (!Objects.equals(valPre, valCurrent)) { - //依赖字段如有任一不等值,则标记为不可合并 - isMerge = false; - } - } - } - return isMerge; - } - - @Data - @AllArgsConstructor - static class RepeatCell { - - private Object value; - - private int current; - - } }