easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头

    • 原版
    • 表头和表体字体美化
    • 自动拼接错误提示列
    • 自适应宽度
    • 自动合并单元格
        • 使用Easyexcel
        • 使用poi导出

在后台管理开发的工作中,离不开的就是导出excel了. 如果是简单的导出, 直接easyexcel三四行代码就可以, 但是如果产品业务需要更美观的话, 就需要我们自己去做一些改造
以下代码为自己反复调试后暂用的代码, 如果后面还有优化的话会更新.

原版

首先看下效果对比

  • 原版
    在这里插入图片描述
    乍一看还行, 但是有几个问题, 表头字体大了点, 列宽一样,要自己每个去调整. ,重复单元格想要合并
    以及我们有时候, 需要校验导入的模板是否正确, 错误的话想在后面加提示. 所以不得不自己自动手了

表头和表体字体美化

在这里插入图片描述

直接上代码

 
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;import java.util.List;
import java.util.Map;/*** ExcelStyleTool** @author zgd* @date 2024/3/13 17:16*/
public class ExcelStyleTool {/*** 设置excel样式*/public static HorizontalCellStyleStrategy getStyleStrategy() {// 头的策略  样式调整WriteCellStyle headWriteCellStyle = new WriteCellStyle();//设置头部样式setHeadStyle(headWriteCellStyle, true, true);// 设置细边框setBorder(headWriteCellStyle);//表头字体样式WriteFont headWriteFont = getHeadFont(IndexedColors.SKY_BLUE.getIndex());headWriteCellStyle.setWriteFont(headWriteFont);// 内容的策略WriteCellStyle contentStyle = new WriteCellStyle();//设置内容样式setHeadStyle(headWriteCellStyle, false, false);//设置边框
//        setBorder(contentStyle);//内容字体WriteFont contentWriteFont = getContentFont();contentStyle.setWriteFont(contentWriteFont);// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现return new HorizontalCellStyleStrategy(headWriteCellStyle, contentStyle);}/*** 获取表头字体* @param color* @return*/private static WriteFont getHeadFont(Short color){//表头字体样式WriteFont headWriteFont = new WriteFont();// 头字号headWriteFont.setFontHeightInPoints((short) 10);// 字体样式headWriteFont.setFontName("微软雅黑");// 字体颜色headWriteFont.setColor(color);// 字体加粗headWriteFont.setBold(true);return headWriteFont;}/*** 获取内容字体* @return*/private static WriteFont getContentFont(){//内容字体WriteFont contentWriteFont = new WriteFont();contentWriteFont.setFontHeightInPoints((short) 9);contentWriteFont.setFontName("Arial");contentWriteFont.setBold(false);return contentWriteFont;}/**** @param cellStyle* @param wrappedFlag   自动换行标识,true:开启自动换行* @param centerFlag    水平居中开关,true:开启水平居中*/private static void setHeadStyle(WriteCellStyle cellStyle, boolean wrappedFlag, boolean centerFlag){// 头背景 白色cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());if(wrappedFlag){// 自动换行cellStyle.setWrapped(true);}if(centerFlag){// 水平对齐方式cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);}// 垂直对齐方式cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);}/*** 设置边框* @param cellStyle*/private static void setBorder(WriteCellStyle cellStyle){// 设置细边框cellStyle.setBorderBottom(BorderStyle.THIN);cellStyle.setBorderLeft(BorderStyle.THIN);cellStyle.setBorderRight(BorderStyle.THIN);cellStyle.setBorderTop(BorderStyle.THIN);// 设置边框颜色 25灰度cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());}/*** 得到自定义单元格策略, 内容居中* @return*/public static BeautyStyleStrategy getBeautyCellStyleStrategyCenter(){//灰色表头样式WriteCellStyle headWriteCellStyle = new WriteCellStyle();setHeadStyle(headWriteCellStyle, true, true);setBorder(headWriteCellStyle);WriteFont headWriteFontBlue = getHeadFont(IndexedColors.BLACK.getIndex());headWriteCellStyle.setWriteFont(headWriteFontBlue);//背景色headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());//居中对其内容样式WriteFont contentWriteFont2 = getContentFont();WriteCellStyle contentStyleCenter = new WriteCellStyle();contentStyleCenter.setHorizontalAlignment(HorizontalAlignment.CENTER);contentStyleCenter.setVerticalAlignment(VerticalAlignment.CENTER);
//        setBorder(contentStyleCenter);contentStyleCenter.setWriteFont(contentWriteFont2);return new BeautyStyleStrategy(headWriteCellStyle,null,contentStyleCenter);}/**得到内容左对齐的策略* @return*/public static BeautyStyleStrategy getBeautyCellStyleStrategyLeft(){//灰色表头样式WriteCellStyle headWriteCellStyle = new WriteCellStyle();setHeadStyle(headWriteCellStyle, true, true);setBorder(headWriteCellStyle);WriteFont headWriteFont = getHeadFont(IndexedColors.BLACK.getIndex());headWriteCellStyle.setWriteFont(headWriteFont);//背景色headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());//内容文字WriteFont contentWriteFont = getContentFont();//左对齐内容样式WriteCellStyle contentStyleLeft = new WriteCellStyle();contentStyleLeft.setHorizontalAlignment(HorizontalAlignment.LEFT);contentStyleLeft.setVerticalAlignment(VerticalAlignment.CENTER);
//        setBorder(contentStyleLeft);contentStyleLeft.setWriteFont(contentWriteFont);return new BeautyStyleStrategy(headWriteCellStyle,contentStyleLeft,null);}public static CustomColumnWidthStyleStrategy getColumnWidthStrategy(int minBytes, int maxBytes){return new CustomColumnWidthStyleStrategy(minBytes,maxBytes);}public static CustomColumnWidthStyleStrategy getAutoBeautyColumnWidthStrategy(){//比较合适的自适应宽度return new CustomColumnWidthStyleStrategy(8,50);}/*** @param headIdx  标题行* @param colIdx   错误所在列. 下标从0开始. 如果没指定,自动取标题行的下一列* @param errTitle* @param errMap   错误信息,key是内容list的下标(为了方便list遍历时传值),最终它的行是 headIdx+errMap+1*/public static AddErrColWriteHandler getAddErrColWriteHandler(Integer headIdx, Integer colIdx, String errTitle, Map<Integer, String> errMap){return new AddErrColWriteHandler(headIdx, colIdx, errTitle, errMap);}/***  @param headIdx  标题行*   @param errTitle*    @param errMap   错误信息,key是内容list的下标,从0开始(为了方便list遍历时传值),最终它的行是 headIdx+errMap+1*/public static AddErrColWriteHandler getAddErrColWriteHandler( Integer headIdx, String errTitle, Map<Integer, String> errMap){return new AddErrColWriteHandler(headIdx,  errTitle, errMap);}/*** 获取合并单元格处理器* @return*/public static CustomMergeCellWriteHandler getMergeHandler() {return new CustomMergeCellWriteHandler();}/*** 获取合并单元格处理器* @param firstRow* @param lastRow* @param firstCol* @param lastCol* @return*/public static CustomMergeCellWriteHandler getMergeHandler(int firstRow, int lastRow, int firstCol, int lastCol) {return new CustomMergeCellWriteHandler(firstRow, lastRow, firstCol, lastCol);}/*** 获取 按列 相同的值进行合并的处理器* @param colList* @return*/public static LoopColRangeWriteHandler getLoopColRangeWriteHandler(List<Integer> colList,int fromRow,int toRow) {return new LoopColRangeWriteHandler(colList,fromRow,toRow);}
}

策略类

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.AbstractCellStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Workbook;import java.util.ArrayList;
import java.util.List;/*** 自定义拦截器*/
@Slf4j
public class BeautyStyleStrategy extends AbstractCellStyleStrategy {//这个是 easyexcel 的蓝色表头样式private WriteCellStyle headWriteCellStyle;//这个是 easyexcel 的左对齐内容样式private List<WriteCellStyle> contentWriteCellStyleListLeft;//这个是 easyexcel 的居中对其内容样式private List<WriteCellStyle> contentWriteCellStyleListCenter;//这个是 poi 的表头样式,中间会有一步转换private CellStyle headCellStyle;//这个是 poi 的左对齐内容样式,中间会有一步转换private List<CellStyle> contentCellStyleListLeft;//这个是 poi 的居中对齐内容样式,中间会有一步转换private List<CellStyle> contentCellStyleListCenter;//斑马纹构造方法public BeautyStyleStrategy(WriteCellStyle headWriteCellStyle,List<WriteCellStyle> contentWriteCellStyleListLeft,List<WriteCellStyle> contentWriteCellStyleListCenter) {this.headWriteCellStyle = headWriteCellStyle;this.contentWriteCellStyleListLeft = contentWriteCellStyleListLeft;this.contentWriteCellStyleListCenter = contentWriteCellStyleListCenter;}//统一样式的构造方法public BeautyStyleStrategy(WriteCellStyle headWriteCellStyle,WriteCellStyle contentWriteCellStyleLeft, WriteCellStyle contentWriteCellStyleCenter) {this.headWriteCellStyle = headWriteCellStyle;contentWriteCellStyleListLeft = new ArrayList<>();if (contentWriteCellStyleLeft != null){contentWriteCellStyleListLeft.add(contentWriteCellStyleLeft);}contentWriteCellStyleListCenter = new ArrayList<>();if (contentWriteCellStyleCenter != null){contentWriteCellStyleListCenter.add(contentWriteCellStyleCenter);}}//实例化后进行 easyexcel -> poi 样式的转换@Overrideprotected void initCellStyle(Workbook workbook) {if (headWriteCellStyle != null) {headCellStyle = StyleUtil.buildHeadCellStyle(workbook, headWriteCellStyle);}if (contentWriteCellStyleListLeft != null && !contentWriteCellStyleListLeft.isEmpty()) {contentCellStyleListLeft = new ArrayList<CellStyle>();for (WriteCellStyle writeCellStyle : contentWriteCellStyleListLeft) {if (writeCellStyle == null){continue;}contentCellStyleListLeft.add(StyleUtil.buildContentCellStyle(workbook, writeCellStyle));}}if (contentWriteCellStyleListCenter != null && !contentWriteCellStyleListCenter.isEmpty()) {contentCellStyleListCenter = new ArrayList<CellStyle>();for (WriteCellStyle writeCellStyle : contentWriteCellStyleListCenter) {if (writeCellStyle == null){continue;}contentCellStyleListCenter.add(StyleUtil.buildContentCellStyle(workbook, writeCellStyle));}}}//设置表头样式@Overrideprotected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {int colIndex = cell.getColumnIndex();//同样,根据不同的列编号选择使用不同的内容样式if (headCellStyle == null) {return;}cell.setCellStyle(headCellStyle);}//设置内容样式@Overrideprotected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {int rowIndex = cell.getRowIndex();//同样,根据不同的列编号选择使用不同的内容样式
//        if (rowIndex > 0) {
//            if (contentCellStyleListCenter == null || contentCellStyleListCenter.isEmpty()) {
//                return;
//            }
//            cell.setCellStyle(contentCellStyleListCenter.get(relativeRowIndex % contentCellStyleListCenter.size()));
//        }if (contentCellStyleListCenter != null && !contentCellStyleListCenter.isEmpty()) {cell.setCellStyle(contentCellStyleListCenter.get(0));}else if(contentCellStyleListLeft != null && !contentCellStyleListLeft.isEmpty()){cell.setCellStyle(contentCellStyleListLeft.get(0));}}}
  EasyExcel.write(os, KnowledgePanoramaActivityExcelVO.class).sheet("配置情况")
//美化表头和表体字体.registerWriteHandler(ExcelStyleTool.getBeautyCellStyleStrategyLeft()).doWrite(records);
  1. 如果希望表体内容能够居中显示,只需简单地使用ExcelStyleTool.getBeautyCellStyleStrategyCenter()方法。这只是一个基础框架,可以根据实际需求自由调整颜色、字体等样式。为此,可以在ExcelStyleTool类中新建一个方法,并在该方法中创建一个新的BeautyStyleStrategy对象进行返回。

  2. 如果还希望实现每行颜色间隔不同的效果,只需在contentWriteCellStyleListLeft或contentWriteCellStyleListCenter集合中,添加多个不同的样式。随后,只需取消setContentCellStyle方法中的注释即可应用这些样式。

自动拼接错误提示列

效果:
在这里插入图片描述

可以在原excel文件的基础上(即便是有丰富样式的模板文件), 在最后加一列, 然后填充我们的错误提示信息

 
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;import java.util.Map;/*** 自定义拦截器.新增注释,第一行头加批注*/
@Slf4j
public class AddErrColWriteHandler implements SheetWriteHandler {/*** 第几行*/private final Integer headIdx;private Integer colIdx;private final String errTitle;private Map<Integer, String> errMap;/*** @param headIdx  标题行* @param colIdx   错误所在列. 下标从0开始. 如果没指定,自动取标题行的下一列* @param errTitle* @param errMap   错误信息,key是内容list的下标(为了方便list遍历时传值),最终它的行是 headIdx+errMap+1*/public AddErrColWriteHandler(Integer headIdx, Integer colIdx, String errTitle, Map<Integer, String> errMap) {this.headIdx = headIdx  ;this.colIdx = colIdx;this.errTitle = errTitle;this.errMap = errMap;}/*** @param headIdx  标题行* @param errTitle* @param errMap   错误信息,key是内容list的下标,从0开始(为了方便list遍历时传值),最终它的行是 headIdx+errMap+1*/public AddErrColWriteHandler(Integer headIdx, String errTitle, Map<Integer, String> errMap) {this.headIdx = headIdx  ;this.errTitle = errTitle;this.errMap = errMap;}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {int lastCellNum = 0;Workbook workbook = writeWorkbookHolder.getWorkbook();CellStyle cellStyleHeader = getCellHeaderStyle(workbook);for (Integer i = 0; i <= headIdx; i++) {Row row = writeSheetHolder.getCachedSheet().getRow(headIdx);if (row == null) {row = writeSheetHolder.getCachedSheet().createRow(headIdx);}if (colIdx == null) {lastCellNum = Math.max(row.getLastCellNum(),lastCellNum);colIdx = (int) lastCellNum;}}int titleRowNum = headIdx;if (headIdx > 0){//合并单元格, easyExcel从第1行开始. 然后lastRow和lastCol都不包含自身所以加一
//            writeSheetHolder.getCachedSheet().addMergedRegion(new CellRangeAddress(0,headIdx,colIdx,colIdx));//单元格只保留最上面的,所以指定第一行titleRowNum = 0;}Row row = writeSheetHolder.getCachedSheet().getRow(titleRowNum);Cell cell = row.getCell(colIdx);if (cell == null) {cell = row.createCell(colIdx);}cell.setCellStyle(cellStyleHeader);cell.setCellValue(errTitle);// 内容样式CellStyle  cellStyle =  getCellStyle(workbook);Sheet sheet = writeSheetHolder.getCachedSheet();for (Map.Entry<Integer, String> en : errMap.entrySet()) {int rowIdx = en.getKey() + headIdx + 1;Row row0 = sheet.getRow(rowIdx);if (row0 == null) {row0 = sheet.createRow(rowIdx);}// 第几列。我这里是1.正常业务根据需求传递Cell cell0 = row0.getCell(colIdx);if (cell0 == null) {cell0 = row0.createCell(colIdx);}cell0.setCellStyle(cellStyle);cell0.setCellValue(en.getValue());}}private CellStyle getCellStyle(Workbook workbook) {CellStyle cellStyle = workbook.createCellStyle();
//            cellStyle.setAlignment(HorizontalAlignment.CENTER);cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());Font font = workbook.createFont();font.setFontName("微软雅黑");font.setFontHeightInPoints((short) 10);cellStyle.setFont(font);return cellStyle;}private CellStyle getCellHeaderStyle(Workbook workbook) {// 表头样式CellStyle cellStyle = workbook.createCellStyle();cellStyle.setAlignment(HorizontalAlignment.LEFT);cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());Font font = workbook.createFont();font.setFontName("微软雅黑");font.setColor((short) 10);font.setFontHeightInPoints((short) 12);cellStyle.setFont(font);return cellStyle;}
}

使用, 可以使用ExcelStyleTool的静态方法, 也可以自己直接new


int headIdx = 1;
//这个key是内容list的下标,从0开始(为了方便list遍历时传值),最终它的行是 headIdx+errMap+1,value是错误信息
Map<Integer,String> errMsgMap = new HashMap<>();
for (int i = 0; i < data.length; i++, dataRow++) {errMsgMap.put(i,"这是错误");
}
//file是上传的文件InputStream is = file.getInputStream();//os是response的输出流或者file文件的输出流EasyExcel.write(os).withTemplate(is).registerWriteHandler(new AddErrColWriteHandler(headIdx, "错误信息(重新导入请删除此列)", errMsgMap)).sheet().doWrite(new ArrayList());

自适应宽度

效果图:
在这里插入图片描述
可以看到可以根据列的文本长度(字体默认11的情况), 列宽有一个比较好的适应效果. 如果字体不一样, 修改calWidth方法里的计算方法.

 
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;import java.util.HashMap;
import java.util.List;
import java.util.Map;public class CustomColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {private static final int MAX_COLUMN_WIDTH = 255 * 256;private static final int MIN_COLUMN_WIDTH = 2 * 256;private final int maxColumnWidth;private final int minColumnWidth;public CustomColumnWidthStyleStrategy(int minBytes,int maxBytes) {this.maxColumnWidth = Math.min(calWidth(maxBytes), MAX_COLUMN_WIDTH);this.minColumnWidth = Math.max(calWidth(minBytes), MIN_COLUMN_WIDTH);}private static int calWidth(int bytes) {return bytes * 256 * 2 / 3;}public CustomColumnWidthStyleStrategy() {this.maxColumnWidth = MAX_COLUMN_WIDTH;this.minColumnWidth = MIN_COLUMN_WIDTH;}private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(8);@Overrideprotected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head,Integer relativeRowIndex, Boolean isHead) {boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);if (!needSetWidth) {return;}Map<Integer, Integer> maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<Integer, Integer>(16));Integer columnWidth = dataLength(cellDataList, cell, isHead);if (columnWidth < 0) {return;}if (columnWidth > maxColumnWidth) {columnWidth = maxColumnWidth;}if (columnWidth < minColumnWidth) {columnWidth = minColumnWidth;}Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());if (maxColumnWidth == null || columnWidth > maxColumnWidth) {maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth);}}private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {if (isHead) {//不考虑标题return -1;}CellData cellData = cellDataList.get(0);CellDataTypeEnum type = cellData.getType();if (type == null) {return minColumnWidth;}switch (type) {case STRING:return calWidth(cellData.getStringValue().getBytes().length);case BOOLEAN:return calWidth(cellData.getBooleanValue().toString().getBytes().length);case NUMBER:return calWidth(cellData.getNumberValue().toString().getBytes().length );default:return minColumnWidth;}}
}

使用方法和上面差不多, 不赘述了

自动合并单元格

效果,可以按列,将同样的内容的单元格自动合并, 如果需要按行合并, 差不多的思路实现, 这里没这个需求就没有做:
在这里插入图片描述

首先写一个公共方法. 用于针对某一列的同样内容进行合并单元格;

public static void mergeByCol(Sheet sheet, int colIdx, int fromRowIdx,int toRowIdx) {mergeByCol(sheet, colIdx, fromRowIdx,toRowIdx,false,VerticalAlignment.CENTER,HorizontalAlignment.CENTER);}/*** 根据列,将同样的单元格合并* @param sheet* @param colIdx* @param fromRowIdx*/public static void mergeByCol(Sheet sheet, int colIdx, int fromRowIdx,int toRowIdx,boolean wrap,VerticalAlignment verticalAlignment,HorizontalAlignment alignment) {Iterator<Row> it = sheet.rowIterator();int rows = -1;String lastVal = null;int lastRows = 0;while (it.hasNext()){Row row = it.next();rows++;if (fromRowIdx > rows){continue;}if (lastVal == null){lastRows = rows;lastVal = row.getCell(colIdx).getStringCellValue();} else {Cell cell = row.getCell(colIdx);String curVal = cell == null ? null : cell.getStringCellValue();if (lastVal.equals(curVal)){continue;} else {if (rows - 1 > lastRows) {//合并sheet.addMergedRegion(new CellRangeAddress(lastRows, rows - 1, colIdx, colIdx));//设置格式Cell topLeftCell = sheet.getRow(lastRows).getCell(colIdx);CellStyle originStyle = topLeftCell.getCellStyle();CellStyle cellStyle = sheet.getWorkbook().createCellStyle();cellStyle.cloneStyleFrom(originStyle);cellStyle.setVerticalAlignment(verticalAlignment);cellStyle.setAlignment(alignment);if (wrap){cellStyle.setWrapText(true);}}lastRows = rows;lastVal = curVal;}}}//遍历所有行以后,最后判断一次Cell cell = sheet.getRow(toRowIdx).getCell(colIdx);if (cell != null && lastVal != null ){String curVal = cell.getStringCellValue();if (Objects.equals(lastVal, curVal)){sheet.addMergedRegion(new CellRangeAddress(lastRows, toRowIdx, colIdx, colIdx));}}
使用Easyexcel
 
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import javafx.util.Pair;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;/*** 添加合并区Handler*/
public class LoopColRangeWriteHandler extends AbstractCellWriteHandler {private final Map<Integer, Pair<Integer,String>> mergeMap;/**从哪行开始,主要是跳过表头*/private final Integer fromRow;/*** 标志在哪一行结束合并,最重要的是放在最后一行的时候,让系统知道将前面的合并*/private final Integer toRow;public LoopColRangeWriteHandler(List<Integer> colList, Integer fromRow,Integer toRow) {mergeMap = colList.stream().collect(Collectors.toMap(i -> i, i -> new Pair<>(fromRow, "")));this.fromRow = fromRow;this.toRow = toRow;}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {String cellValue = cell.getStringCellValue();int columnIndex = cell.getColumnIndex();int rowIndex = cell.getRowIndex();if ((fromRow < 0 && isHead) || rowIndex < fromRow
//                || !mergeMap.containsKey(columnIndex)){return;}if (rowIndex == toRow){for (Integer colIndex : colList) {if (colIndex == columnIndex){ExcelUtil.mergeByCol(writeSheetHolder.getSheet(),colIndex,fromRow,toRow,true,VerticalAlignment.CENTER,HorizontalAlignment.LEFT);}}}}
}

然后用的话, 需要指定开始行和结束行, 结束行是为了让系统知道这是最后一行,需要对前面相同的内容进行合并了.

 EasyExcel.write(os, KnowledgePanoramaActivityExcelVO.class).sheet("配置情况")//这里结束行注意别搞错了,因为下标从0开始,所以要-1.registerWriteHandler(ExcelStyleTool.getLoopColRangeWriteHandler(CollectionUtil.toList( 3,4),titleRow,records.size()+titleRow-1)).doWrite(records);
使用poi导出
   Sheet studyPointSheet = workbook.getSheetAt(0);
... 填充内容
//填充完内容后使用ExcelUtil.mergeByCol(studyPointSheet, 2,2,studyPointSheet.getLastRowNum());ExcelUtil.mergeByCol(studyPointSheet, 3,2,studyPointSheet.getLastRowNum());workbook.write(os);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/59974.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算的学习

文章目录 概要何为边缘计算&#xff1f;现阶段&#xff0c;企业使用边缘计算相对云计算 整体架构流程边缘网络组件边缘计算与云安全 研究方向结合引用 概要 edge 何为边缘计算&#xff1f; 边缘计算&#xff08;英语&#xff1a;Edge computing&#xff09;&#xff0c;是一种…

SpringBoot在城镇保障性住房管理中的应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理城镇保障性住房管理系统的相关信息成为必然…

算法|牛客网华为机试41-52C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试21-30C 文章目录 HJ41 称砝码HJ42 学英语HJ43 迷宫问题HJ44 SudokuHJ45 名字的漂亮度HJ46 截取字符串HJ48 从单向链表中删除指定值的节点HJ50 四则运算HJ51 输出单向链表中倒数第k个结点HJ52 计算字符串的编辑距离 HJ41 称砝…

粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测

粒子群优化双向深度学习&#xff01;PSO-BiTCN-BiGRU-Attention多输入单输出回归预测 目录 粒子群优化双向深度学习&#xff01;PSO-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现PSO-BiTCN-BiGRU-Attention粒子…

「Mac玩转仓颉内测版1」入门篇1 - Cangjie环境的搭建

本篇详细介绍在Mac系统上快速搭建Cangjie开发环境的步骤&#xff0c;涵盖VSCode的下载与安装、Cangjie插件的离线安装、工具链的配置及验证。通过这些步骤&#xff0c;确保开发环境配置完成&#xff0c;为Cangjie项目开发提供稳定的基础支持。 关键词 Cangjie开发环境搭建VSC…

协程6 --- HOOK

文章目录 HOOK 概述链接运行时动态链接 linux上的常见HOOK方式修改函数指针用户态动态库拦截getpidmalloc 第一版malloc 第二版malloc/free通过指针获取到空间大小malloc 第三版strncmp 内核态系统调用拦截堆栈式文件系统 协程的HOOK HOOK 概述 原理&#xff1a;修改符号指向 …

ResNet 残差网络 (乘法→加法的思想 - 残差连接是所有前沿模型的标配) + 代码实现 ——笔记2.16《动手学深度学习》

目录 前言 0. 乘法变加法的思想 1. 函数类 2. 残差块 (讲解代码) QA: 残差这个概念的体现&#xff1f; 3. ResNet模型 (代码讲解) 补充&#xff1a;更多版本的ResNet 4. 训练模型 5. 小结 6. ResNet的两大卖点 6.1 加深模型可以退化为浅层模型 6.2 用加法解决梯度消…

iphone怎么删除重复的照片的新策略

Phone用户常常面临存储空间不足的问题&#xff0c;其中一个主要原因是相册中的重复照片。这些重复项不仅占用了大量的存储空间&#xff0c;还会影响设备的整体性能。本文将向您展示iphone怎么删除重复的照片的方法&#xff0c;包括一些利用工具来自动化这个过程的创新方法。 识…

软件缺陷等级评定综述

1. 前言 正确评估软件缺陷等级&#xff0c;在项目的生命周期中有着重要的作用&#xff1a; 指导缺陷修复的优先级和资源分配 在软件开发和维护过程中&#xff0c;资源&#xff08;包括人力、时间和资金&#xff09;是有限的。通过明确缺陷的危险等级&#xff0c;可以帮助团队合…

【Pikachu】Cross-Site Scripting跨站脚本攻击实战

只管把目标定在高峰&#xff0c;人家要笑就让他去笑&#xff01; 1.XSS&#xff08;跨站脚本&#xff09;概述 XSS&#xff08;跨站脚本&#xff09;概述 Cross-Site Scripting 简称为“CSS”&#xff0c;为避免与前端叠成样式表的缩写"CSS"冲突&#xff0c;故又称…

【SpringBoot】 黑马大事件笔记-day2

目录 用户部分 实体类属性的参数校验 更新用户密码 文章部分 规定josn日期输出格式 分组校验 上期回顾&#xff1a;【SpringBoot】 黑马大事件笔记-day1 用户部分 实体类属性的参数校验 对应的接口文档&#xff1a; 基本信息 请求路径&#xff1a;/user/update 请求方式&#…

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…

QT信号和槽与自定义的信号和槽

QT信号和槽与自定义的信号和槽 1.概述 这篇文章介绍下QT信号和槽的入门知识&#xff0c;通过一个案例介绍如何创建信号和槽&#xff0c;并调用他们。 2.信号和槽使用 下面通过点击按钮关闭窗口的案例介绍如何使用信号和槽。 创建按钮 在widget.cpp文件中创建按钮代码如下 …

unity显示获取 年月日周几【日期】

unity显示获取 年月日周几【日期】 public void ShowDate(Text txt){//txt.text DateTime now DateTime.Now; // 获取当前时间int year now.Year; // 获取年份int month now.Month; // 获取月份&#xff08;1-12&#xff09;int day now.Day; // 获取天数&#xff08;1-31&…

emr上使用sparkrunner运行beam数据流水线

参考资料 https://time.geekbang.org/column/intro/167?tabcatalog Apache Beam和其他开源项目不太一样&#xff0c;它并不是一个数据处理平台&#xff0c;本身也无法对数据进行处理。Beam所提供的是一个统一的编程模型思想&#xff0c;而我们可以通过这个统一出来的接口来编…

AUTOSAR CP SocketAdaptor(SoAd)规范导读

《AUTOSAR_SWS_SocketAdaptor》规范的主要内容包括&#xff1a; 简介和功能概述&#xff1a;说明了 AUTOSAR 基本软件模块 Socket Adaptor&#xff08;SoAd&#xff09;的功能、API 和配置。数据传输的 TCP/IP 概念在计算和电信环境中已成为标准&#xff0c;应用程序的寻址等…

代码随想录-栈和队列-用栈实现队列

问题描述 题目描述中有说不存在空栈的pop和peek&#xff0c;所以无需判断这个 解析 重点在于思路&#xff0c;代码白给。 要用栈实现队列&#xff0c;肯定是两个栈才可以。一个做入队操作&#xff0c;一个做出队操作。 首先入队简单&#xff0c;往栈里加就完事了。 出队复…

【设计模式】结构型模式(四):组合模式、享元模式

《设计模式之结构型模式》系列&#xff0c;共包含以下文章&#xff1a; 结构型模式&#xff08;一&#xff09;&#xff1a;适配器模式、装饰器模式结构型模式&#xff08;二&#xff09;&#xff1a;代理模式结构型模式&#xff08;三&#xff09;&#xff1a;桥接模式、外观…

轻型民用无人驾驶航空器安全操控------理论考试多旋翼部分笔记

官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 说明&#xff1a;一是法规部分&#xff1b;二是多旋翼部分 本笔记全部来源于轻型民用无人驾驶航空器安全操控视频讲解平台 目录 官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 一、轻型民用无人…

【leetcode练习·二叉树】用「分解问题」思维解题 I

本文参考labuladong算法笔记[【强化练习】用「分解问题」思维解题 I | labuladong 的算法笔记] 105. 从前序与中序遍历序列构造二叉树 | 力扣 | LeetCode | 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵…