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 && toRowIdx > lastRows ){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.rhkb.cn/news/468762.html

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

相关文章

微深节能 煤码头自动化翻堆及取料集控系统 格雷母线

一、系统概述 微深节能在煤码头自动化翻堆及取料集控系统中引入了格雷母线高精度位移测量系统&#xff0c;该系统是一项重要的技术创新&#xff0c;显著提升了煤码头作业的自动化水平和精确性。它主要用于实现对斗轮堆取料机等大型机械设备的精准定位和自动化控制&#xff0c;从…

LeetCode 热题100 之 栈

1.有效的括号 思路分析&#xff1a;我们可以使用栈&#xff08;stack&#xff09;来解决这个问题。栈是一种先进后出的数据结构&#xff0c;这与括号匹配的需求非常契合。 unordered_map<char, char> bracket_map&#xff1a;这个哈希表用来存储右括号与左括号的对应关系…

yolov11-seg数据集制作训练推理流程:

文章目录 前言一、数据集制作二、模型训练推理&#xff1a; 前言 随着深度学习技术的不断发展&#xff0c;目标检测与分割技术在计算机视觉领域扮演着越来越重要的角色。YOLO&#xff08;You Only Look Once&#xff09;作为一种高效、实时的目标检测算法&#xff0c;自提出以…

基于Spring Boot的乡政府管理系统设计与实现,LW+源码+讲解

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装乡政府管理系统软件来发挥其高效地信息处理的作用&#xf…

python的学习

0.tips 1.变量命名规则 2.变量的赋值 3.变量的类型 int&#xff0c;float&#xff0c;str&#xff08;双引号、单引号、三引号包含都可以&#xff09; 类型带来的意义 动态类型的基本特性 4.注释 5.控制台 格式化字符串f-string 输入/输出input 6.运算符 算术运算符 //&…

信息安全工程师(79)网络安全测评概况

一、定义与目的 网络安全测评是指参照一定的标准规范要求&#xff0c;通过一系列的技术、管理方法&#xff0c;获取评估对象的网络安全状况信息&#xff0c;并对其给出相应的网络安全情况综合判定。其对象主要为信息系统的组成要素或信息系统自身。网络安全测评的目的是为了提高…

【GoWeb示例】通过示例学习 Go 的 Web 编程

文章目录 你好世界HTTP 服务器路由&#xff08;使用 gorilla/mux&#xff09;连接到 MySQL 数据库MySQL 数据库简单操作模板静态资源和文件操作表单处理中间件&#xff08;基础&#xff09;中间件&#xff08;高级&#xff09;会话JSONWebsockets密码哈希 你好世界 Go语言创建…

UnixBench和Geekbench进行服务器跑分

1 概述 服务器的基准测试&#xff0c;常见的测试工具有UnixBench、Geekbench、sysbench等。本文主要介绍UnixBench和Geekbench。 1.1 UnixBench UnixBench是一款开源的测试UNIX系统基本性能的工具&#xff08;https://github.com/kdlucas/byte-unixbench&#xff09;&#x…

打造个性化时钟应用:结合视觉与听觉的创新实践

​ 在数字时代&#xff0c;虽然手机、电脑等设备已经能够非常方便地显示时间&#xff0c;但一款融合了视觉艺术和声音效果的桌面时钟仍能给我们的日常生活带来不一样的体验。本文将引导读者通过Python语言及其强大的库支持来创建一个具有整点及半点报时功能的美观时钟界面。该项…

ASMR助眠声音视频素材去哪找 吃播助眠素材网站分享

在快节奏的现代生活中&#xff0c;越来越多的人感到压力山大&#xff0c;许多人开始寻求助眠和放松的方式。而ASMR&#xff08;自发性知觉经络反应&#xff09;助眠声音视频&#xff0c;凭借其独特的声音刺激和放松效果&#xff0c;成为了睡前的“神器”。如果你是一位内容创作…

Ente: 我们的 Monorepo 经验

原文&#xff1a;manav - 2024.10.29 九个月前&#xff0c;我们切换到了 monorepo。在此&#xff0c;我将介绍我们迄今为止的切换经验。 这并不是一份规范性的建议&#xff0c;而是一个经验的分享&#xff0c;目的是希望能够帮助其他团队做出明智的决策。 与大多数岔路不同&…

css:还是语法

emmet的使用 emmet是一个插件&#xff0c;Emmet 是 Zen Coding 的升级版&#xff0c;由 Zen Coding 的原作者进行开发&#xff0c;可以快速的编写 HTML、CSS 以及实现其他的功能。很多文本编辑器都支持&#xff0c;我们只是学会使用它&#xff1a; 生成html结构 <!-- emme…

常见计算机网络知识整理(未完,整理中。。。)

TCP和UDP区别 TCP是面向连接的协议&#xff0c;发送数据前要先建立连接&#xff1b;UDP是无连接的协议&#xff0c;发送数据前不需要建立连接&#xff0c;是没有可靠性&#xff1b; TCP只支持点对点通信&#xff0c;UDP支持一对一、一对多、多对一、多对多&#xff1b; TCP是…

javascript实现国密sm4算法(支持微信小程序)

概述&#xff1a; 本人前端需要实现sm4计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sm4的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sm4的javascript代码&#xff0c;并成功验证好分多次计算sm4数据 测试平台&#xff1a; …

深度解读AI在数字档案馆中的创新应用:高效识别与智能档案管理

一、项目背景介绍 在信息化浪潮推动下&#xff0c;基于OCR技术的纸质档案电子化方案成为解决档案管理难题的有效途径。该方案通过先进的OCR技术&#xff0c;能够统一采集各类档案数据&#xff0c;无论是手写文件、打印文件、复古文档还是照片或扫描的历史资料&#xff0c;都能实…

C++ | Leetcode C++题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; class Solution { public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;for (auto& widths : wall) {int n widths.size();int sum 0;for (int i 0; i < n - 1; i) {sum wi…

【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)

文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略&#xff08;policy&#xff09;价值函数&#xff08;value function&#xff09;模型&#xff08;model&#xff09; 强化学习和启发式算法比较强化学习步骤代码走…

模糊搜索:在不确定性中寻找精确结果

目录 模糊搜索&#xff1a;在不确定性中寻找精确结果 一、引言 二、模糊搜索的背景 三、模糊搜索的原理 1、编辑距离&#xff08;Levenshtein Distance&#xff09;&#xff1a; 2、Jaccard 相似系数&#xff1a; 3、Soundex 算法&#xff1a; 4、TF-IDF&#xff08;词…

MyBatis5-缓存

目录 一级缓存 二级缓存 MyBatis缓存查询的顺序 整合第三方缓存EHCache 一级缓存 一级缓存是 SqlSession 级别的&#xff0c;通过同一个 SqlSession 查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就会从缓存中直接获取&#xff0c;不会从数据库重新访问 一…

95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数

目录 1.双向链表的头插 方法一 方法二 2.双向链表的头删 3.双向链表的销毁 4.双向链表的某个节点的数据查找 5.双向链表的中间插入 5.双向链表的中间删除 6.对比顺序表和链表 承接94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删文章 1.双向链表的头插 方法…