当前位置: 首页 > news >正文

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);

相关文章:

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

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头 原版表头和表体字体美化自动拼接错误提示列自适应宽度自动合并单元格使用Easyexcel使用poi导出 在后台管理开发的工作中,离不开的就是导出excel了. 如果是简单的导出, 直接easyexce…...

ANDROIDWORLD: A Dynamic Benchmarking Environment for Autonomous Agents论文学习

这个任务是基于androidenv的。这个环境之前学过&#xff0c;是一个用来进行强化学习的线上环境。而这篇文章的工作就是要给一些任务加上中间的奖励信号。这种训练环境的优点就是动态&#xff0c;与静态的数据集&#xff08;比如说我自己的工作&#xff09;不同&#xff0c;因此…...

Docker 常用命令详解(详细版)

Docker 是一个开源的容器化平台&#xff0c;它使得开发人员可以打包应用程序及其所有依赖项&#xff0c;并在任何环境中运行。Docker 提供了简单而强大的命令行工具来管理容器、镜像、网络等。本文将详细介绍 Docker 的常用命令及其使用方法。 1. 安装 Docker 在使用 Docker …...

【网络安全 | 甲方安全建设】分布式系统、Redis分布式锁及Redisson看门狗机制

未经许可,不得转载。 文章目录 分布式系统分布式系统的核心特性分布式系统的典型架构分布式锁概念Redis 分布式锁原理互斥性锁释放锁的唯一性具体实现Redisson分布式锁分布式系统 分布式系统是一种由多台计算机(节点)组成的系统,这些节点通过网络相互连接并协同工作,共同…...

「QT」几何数据类 之 QLineF 浮点型直线类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…...

Treeland 技术揭秘,如何使得 DDE 纵享丝滑?

近日&#xff0c;deepin&#xff08;深度&#xff09;社区亮相COSCon24 第九届中国开源年会开源市集&#xff0c;且社区资深桌面研发工程师张丁元为大家来了《Treeland&#xff0c;DDE进步的阶梯》技术分享。 就着这个机会&#xff0c;今天就让我们一起来聊聊如何在追求华丽动…...

快速了解SpringBoot 统一功能处理

拦截器 什么是拦截器&#xff1a; 拦截器是Spring框架提供的重要功能之一&#xff0c;主要进行拦截用户请求&#xff0c;在指定方法前后&#xff0c;根据业务需求&#xff0c;执行预先设定的代码。 也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以…...

C++区分数组的引用和引用的数组

void f(int (&arr)[10]) {//正确} void f1(int &arr[10]) {//不允许使用引用的数组} []&#xff08;数组下标运算符&#xff09;的优先级高于&&#xff08;取地址运算符&#xff09;。所有表达式&arr[i]等价于&(arr[i]) 引用的数组 一个包含引用的数组&…...

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…...

ESLint 使用教程(五):ESLint 和 Prettier 的结合使用与冲突解决

前言 在现代前端开发中&#xff0c;代码质量与代码风格的统一是两个非常重要的方面。良好的代码质量能减少 bug 的产生&#xff0c;而统一的代码风格则能提高团队协作的效率。为了实现这两个目标&#xff0c;我们通常会使用一些工具。 为了保证代码的可读性和维护性&#xff0…...

uniApp之uni-file-picker使用踩坑

标题党~也不算坑吧 就是初体验 上传是需要存储一下子的&#xff0c;我以为uniApp是自己免费开的服务给大家中转使用&#xff0c;就没管这个事&#xff0c;但是官网是这么说的&#xff1a; 就我是怎么发现的&#xff0c;使用了一段时间后&#xff0c;上传的图片都裂了&#xff…...

【C语言】。末尼

用js写一个冒泡排序 当然可以。在JavaScript中&#xff0c;你可以使用递归的方式来实现冒泡排序算法。这是一个简单的示例&#xff1a; function bubbleSort(arr) { let len arr.length; if(len < 1){ return arr; // 如果数组只有一个元素或为空&#xf…...

【鉴权】深入解析 Token:身份认证的核心技术

目录 引言一、Token 的定义与概念1.1 Token 是什么&#xff1f;1.2 无状态性与自包含性1.3 Token 的工作流程1.3.1 基本工作流程1.3.2 工作流程图示 二、Token 的常见用途2.1 用户身份验证2.2 授权控制2.3 防止跨站请求伪造&#xff08;CSRF&#xff09;2.4 跨域认证 三、Token…...

FastReport将停止 .NET Framework 上的 WebReport 更新

从2024/ 12 /1 日起&#xff0c;Fastreport将停止发布更新和提供对 FastReport.Web (.NET Framework) 的技术支持。该库一直是使用 Online Designer 的许多项目中报告的核心。这些更改意味着 FastReport.Web (Legacy) 库&#xff08;FastReport.Net包的一部分&#xff09;将不再…...

面试:TCP、UDP如何解决丢包问题

文章目录 一、TCP丢包原因、解决办法1.1 TCP为什么会丢包1.2 TCP传输协议如何解决丢包问题1.3 其他丢包情况&#xff08;拓展&#xff09;1.4 补充1.4.1 TCP端口号1.4.2 多个TCP请求的逻辑1.4.3 处理大量TCP连接请求的方法1.4.4 总结 二、UDP丢包2.1 UDP协议2.1.1 UDP简介2.1.2…...

在Ubuntu下安装RabbitMQ、添加一个新的登录用户并设置密码

在Ubuntu下安装RabbitMQ、添加一个新的登录用户并设置密码 在Ubuntu下安装RabbitMQ可以按照以下步骤进行&#xff1a;步骤 1: 更新系统步骤 2: 安装Erlang步骤 3: 添加RabbitMQ仓库步骤 4: 更新APT索引并安装RabbitMQ步骤 5: 启动RabbitMQ服务步骤 6: 检查RabbitMQ状态步骤 7: …...

HTTPS通信和TCP通信有什么不一样

HTTPS通信和TCP通信的主要区别如下&#xff1a; ‌协议层次‌&#xff1a;HTTPS是应用层协议&#xff0c;建立在HTTP协议之上&#xff0c;并增加了SSL/TLS加密层&#xff1b;而TCP是传输层协议&#xff0c;提供可靠的数据传输服务。‌安全性‌&#xff1a;HTTPS通过SSL/TLS加密…...

Kafka 的一些问题,夺命15连问

kafka-中的组成员 kafka四大核心 生产者API 允许应用程序发布记录流至一个或者多个kafka的主题&#xff08;topics&#xff09;。 消费者API 允许应用程序订阅一个或者多个主题&#xff0c;并处理这些主题接收到的记录流 StreamsAPI 允许应用程序充当流处理器&#xff08;s…...

unity3d————延时函数

1.public void InvokeRepeating(string methodName, float time, float repeatRate); 延迟重复执行函数 InvokeRepeating 参数一&#xff1a;函数名字符串 参数二&#xff1a;第一次执行的延迟时间 参数三&#xff1a;之后每次执行的间隔时间 注意&#xff1a; 1-1.延时函数第…...

计算机学生自我提升方法——善用搜索引擎

计算机学生自我提升方法——善用搜索引擎 在信息爆炸的时代&#xff0c;计算机专业的学生如何有效地自我提升&#xff1f;答案可能就藏在一个简单却强大的工具——搜索引擎中。搜索引擎不仅是获取知识的入口&#xff0c;更是解决问题的利器。下面&#xff0c;我将分享一些善用…...

游戏引擎学习第一天

视频参考: https://www.bilibili.com/video/BV1zGDCYHErA/ 创建一个保存项目的路径 VS的安装略过&#xff0c;个人自行百度 1. vs 创建第一个CMAKE的窗口项目 game.cpp 修改如下的代码 到https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winmain 去…...

uni-app view循环绑定click和 v-if

<view class"layout-wrap-item" v-for"(item, index) in menuItems" :key"index" click"item.clickHandler" :v-if"showMenu(item)"></view> const xxx (id) > { }; // 定义菜单项数组 const menuItems …...

Redis 高并发分布式锁实战

目录 环境准备 一 . Redis 安装 二&#xff1a;Spring boot 项目准备 三&#xff1a;nginx 安装 四&#xff1a;Jmeter 下载和配置 案例实战 优化一&#xff1a;加 synchronized 锁 优化二&#xff1a;使用 redis 的 setnx 实现分布式锁 优化三&#xff1a;使用 Lua 脚本…...

关于elementui el-radio 赋值问题

今天遇到这样的问题&#xff1a; 点击的时候&#xff0c;同时选中 照抄官网&#xff01; 后来发现了问题&#xff1a; 也就是说如果你的版本太低&#xff0c;就不能用value&#xff0c;而得用label&#xff0c;于是修改 <el-radio-group v-model"searchTime"&g…...

2024-11-6----Android 11(全志713m)----- 关于添加 Selinux 权限

需求 节点: /sys/devices/platform/motor0/motor_ctrl上层 APP 使用 JNI 需要对该节点进行 echo 的操作,操作失败。 添加前的验证工作 adb 进去验证下,如下图所示: 发现权限不够。su 以后再操作是OK的,如下图: 添加前的修改 为防止报权限错误,直接给777,因为该…...

shodan5(泷羽sec)

声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…...

【Linux】Ansible集中化运维工具(详解)安装、常用模块、playbook脚本

文章目录 一、Ansible安装及远程控制1、关闭防火墙和SELinux2、安装ansible3、配置SSH无密码登录1、在管理机上生成一对密钥2、将公钥下发到远程主机3、保管密钥 4、主机目录 二、常用模块1、setup模块2、copy模块3、file模块4、shell模块5、script模块6、ping模块7、group模块…...

惠州石湾DELL T130服务器黄灯不开机案例

惠州石湾一个朋友反馈一台DELL PowerEdge T130 塔式服务器故障为 通电后无法开机&#xff0c;前面同时亮3个故障灯。闪电灯&#xff0c;电压灯&#xff0c;高温灯 1&#xff1a;这种情况建议大家更换一个同型号的电源进行故障排除。 2&#xff1a;朋友把该服务器硬件最小化测…...

⭐SmartControl: Enhancing ControlNet for Handling Rough Visual Conditions

目录 0 Abstract 1 Motivation 2 Related Work 2.1 Text-to-Image Diffusion Model 2.2 Controllable Text-to-Image Generation 2.3 ControlNet 2.4 Control Scale Exploration 3 Method 3.1 Framework 3.2 Control Scale Predictor 3.3 Unaligned Data Constructi…...

wordpress站外调用指定ID分类下的推荐内容

在WordPress中&#xff0c;如果你想从站外调用指定ID分类下的推荐内容&#xff0c;你可以使用WordPress REST API来实现。以下是一个基本的步骤指南&#xff1a; 1. 启用REST API 确保你的WordPress站点已经启用了REST API。大多数现代WordPress版本默认启用此功能。 2. 获取…...

网站速度慢的原因/免费推广的方式

有一段时间&#xff0c;我一直在思考什么是团队文化&#xff0c;包括几年前曾经被反复提及的工程师文化等等。 但我始终觉得文化和角色之间并没有那么强的关联关系&#xff0c;我觉得文化和人的性格的关联度更高一些。 如果从这个角度看的话&#xff0c;我觉得搞技术的大致可以…...

wordpress发文章/温州最好的seo

axya ^ x y axy 底数、指数和幂就呈现了如下可能: 知道底数和指数求结果的是幂&#xff0c;也就是说的乘方 知道幂和指数求底数就是开方 知道幂和底数求指数就是对数运算 定义 指数&#xff1a; yaxy a^x yax 对数 ylog⁡axy \log_a x yloga​x 举例 假设 a2a2a2 乘法…...

wordpress 禁止自动保存 插件/seo排名快速刷

学校的校园网每次重开电脑时都要重新打开浏览器进行网页登录&#xff0c;繁琐的操作比较麻烦&#xff0c;于是便写了个python的脚本进行自动登录&#xff0c;下面说下具体的操作过程&#xff1a;1. 方法说明博主采用的python的 requests库&#xff0c;发送post请求给登录网页&a…...

怎样做电商网站的财务分析/玄幻小说排行榜百度风云榜

变频调速技术在晶闸管出现后&#xff0c;就已经开始研究&#xff0c;但直到70年代才出现了变频器。这是因为变频器技术是完全随着电力电子器件、微电子技术、计算机技术和自动控制理论的发展而不断发展的。从最初的电力晶体管 &#xff08;GTR&#xff09; 、门极可关断晶闸管 …...

网站建设经费放哪个经济科目/网站seo分析

学校使用的是Inode客户端认证上网的。而且还只能使用iNodeSetup3.60-6210版本进行连接&#xff0c;之前使用Ubuntu 32位版本&#xff0c;可以完美地安装并能够连接到网站。由于我的机子是64位的机子&#xff0c;所以使用wubi的方式安装了Ubuntu desktop 12.10 amd64版本。安装系…...

个人新闻类网站模板/网络软文营销案例3篇

-1内存中是以补码的形式存放的&#xff0c;即二进制表示的 取反&#xff0c;再加1&#xff1b;如-1&#xff1a;最左边是符号位&#xff0c;1表示负数。1的二进制为000000000000001&#xff0c;取反为111111111111110&#xff0c;再加1即为111111111111111&#xff1b;所以整体…...