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);
-
如果希望表体内容能够居中显示,只需简单地使用ExcelStyleTool.getBeautyCellStyleStrategyCenter()方法。这只是一个基础框架,可以根据实际需求自由调整颜色、字体等样式。为此,可以在ExcelStyleTool类中新建一个方法,并在该方法中创建一个新的BeautyStyleStrategy对象进行返回。
-
如果还希望实现每行颜色间隔不同的效果,只需在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的。这个环境之前学过,是一个用来进行强化学习的线上环境。而这篇文章的工作就是要给一些任务加上中间的奖励信号。这种训练环境的优点就是动态,与静态的数据集(比如说我自己的工作)不同,因此…...
Docker 常用命令详解(详细版)
Docker 是一个开源的容器化平台,它使得开发人员可以打包应用程序及其所有依赖项,并在任何环境中运行。Docker 提供了简单而强大的命令行工具来管理容器、镜像、网络等。本文将详细介绍 Docker 的常用命令及其使用方法。 1. 安装 Docker 在使用 Docker …...
【网络安全 | 甲方安全建设】分布式系统、Redis分布式锁及Redisson看门狗机制
未经许可,不得转载。 文章目录 分布式系统分布式系统的核心特性分布式系统的典型架构分布式锁概念Redis 分布式锁原理互斥性锁释放锁的唯一性具体实现Redisson分布式锁分布式系统 分布式系统是一种由多台计算机(节点)组成的系统,这些节点通过网络相互连接并协同工作,共同…...
「QT」几何数据类 之 QLineF 浮点型直线类
✨博客主页何曾参静谧的博客📌文章专栏「QT」QT5程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…...
Treeland 技术揭秘,如何使得 DDE 纵享丝滑?
近日,deepin(深度)社区亮相COSCon24 第九届中国开源年会开源市集,且社区资深桌面研发工程师张丁元为大家来了《Treeland,DDE进步的阶梯》技术分享。 就着这个机会,今天就让我们一起来聊聊如何在追求华丽动…...
快速了解SpringBoot 统一功能处理
拦截器 什么是拦截器: 拦截器是Spring框架提供的重要功能之一,主要进行拦截用户请求,在指定方法前后,根据业务需求,执行预先设定的代码。 也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以…...
C++区分数组的引用和引用的数组
void f(int (&arr)[10]) {//正确} void f1(int &arr[10]) {//不允许使用引用的数组} [](数组下标运算符)的优先级高于&(取地址运算符)。所有表达式&arr[i]等价于&(arr[i]) 引用的数组 一个包含引用的数组&…...
【harbor】离线安装2.9.0-arm64架构服务制作和升级部署
harbor官网地址:Harbor 参考文档可以看这里:部署 harbor 2.10.1 arm64 - 简书。 前提环境准备: 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…...
ESLint 使用教程(五):ESLint 和 Prettier 的结合使用与冲突解决
前言 在现代前端开发中,代码质量与代码风格的统一是两个非常重要的方面。良好的代码质量能减少 bug 的产生,而统一的代码风格则能提高团队协作的效率。为了实现这两个目标,我们通常会使用一些工具。 为了保证代码的可读性和维护性࿰…...
uniApp之uni-file-picker使用踩坑
标题党~也不算坑吧 就是初体验 上传是需要存储一下子的,我以为uniApp是自己免费开的服务给大家中转使用,就没管这个事,但是官网是这么说的: 就我是怎么发现的,使用了一段时间后,上传的图片都裂了ÿ…...
【C语言】。末尼
用js写一个冒泡排序 当然可以。在JavaScript中,你可以使用递归的方式来实现冒泡排序算法。这是一个简单的示例: function bubbleSort(arr) { let len arr.length; if(len < 1){ return arr; // 如果数组只有一个元素或为空…...
【鉴权】深入解析 Token:身份认证的核心技术
目录 引言一、Token 的定义与概念1.1 Token 是什么?1.2 无状态性与自包含性1.3 Token 的工作流程1.3.1 基本工作流程1.3.2 工作流程图示 二、Token 的常见用途2.1 用户身份验证2.2 授权控制2.3 防止跨站请求伪造(CSRF)2.4 跨域认证 三、Token…...
FastReport将停止 .NET Framework 上的 WebReport 更新
从2024/ 12 /1 日起,Fastreport将停止发布更新和提供对 FastReport.Web (.NET Framework) 的技术支持。该库一直是使用 Online Designer 的许多项目中报告的核心。这些更改意味着 FastReport.Web (Legacy) 库(FastReport.Net包的一部分)将不再…...
面试:TCP、UDP如何解决丢包问题
文章目录 一、TCP丢包原因、解决办法1.1 TCP为什么会丢包1.2 TCP传输协议如何解决丢包问题1.3 其他丢包情况(拓展)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可以按照以下步骤进行:步骤 1: 更新系统步骤 2: 安装Erlang步骤 3: 添加RabbitMQ仓库步骤 4: 更新APT索引并安装RabbitMQ步骤 5: 启动RabbitMQ服务步骤 6: 检查RabbitMQ状态步骤 7: …...
HTTPS通信和TCP通信有什么不一样
HTTPS通信和TCP通信的主要区别如下: 协议层次:HTTPS是应用层协议,建立在HTTP协议之上,并增加了SSL/TLS加密层;而TCP是传输层协议,提供可靠的数据传输服务。安全性:HTTPS通过SSL/TLS加密…...
Kafka 的一些问题,夺命15连问
kafka-中的组成员 kafka四大核心 生产者API 允许应用程序发布记录流至一个或者多个kafka的主题(topics)。 消费者API 允许应用程序订阅一个或者多个主题,并处理这些主题接收到的记录流 StreamsAPI 允许应用程序充当流处理器(s…...
unity3d————延时函数
1.public void InvokeRepeating(string methodName, float time, float repeatRate); 延迟重复执行函数 InvokeRepeating 参数一:函数名字符串 参数二:第一次执行的延迟时间 参数三:之后每次执行的间隔时间 注意: 1-1.延时函数第…...
计算机学生自我提升方法——善用搜索引擎
计算机学生自我提升方法——善用搜索引擎 在信息爆炸的时代,计算机专业的学生如何有效地自我提升?答案可能就藏在一个简单却强大的工具——搜索引擎中。搜索引擎不仅是获取知识的入口,更是解决问题的利器。下面,我将分享一些善用…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
