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

news2024/11/28 1:45:58

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 样式的转换
    @Override
    protected 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));
            }
        }
    }

    //设置表头样式
    @Override
    protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        int colIndex = cell.getColumnIndex();
        //同样,根据不同的列编号选择使用不同的内容样式
        if (headCellStyle == null) {
            return;
        }
        cell.setCellStyle(headCellStyle);
    }

    //设置内容样式
    @Override
    protected 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;
    }


    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public 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);

    @Override
    protected 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;
    }


    @Override
    public 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.coloradmin.cn/o/2235711.html

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

相关文章

brainpy 动力学编程基础

文章参考&#xff1a; 《神经计算建模实战——基于brainpy》 吴思 【brainpy学习笔记】基础知识2(动力学模型的编程基础)-CSDN博客 Brainpy手册 文章目录 积分器&#xff1a;定义ODE函数数值积分方法 更新函数和动力系统计算介绍什么是brainpy.DynamicalSystem&#xff1f;如…

高级图像处理工具

图像处理-高级 1、功能概览 随着社交媒体的普及和个人创作需求的增长&#xff0c;图像处理成为了日常生活中不可或缺的一部分。无论是专业的设计师还是爱好者&#xff0c;都需要一款强大的工具来帮助他们完成各种任务。今天&#xff0c;我们将介绍一款基于Python开发的高级图…

【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作

目录 一、安装Zookeeper 二、配置Zookeeper集群 三、Zookeeper服务的启动与关闭 四、Zookeeper的shell操作 前情提要&#xff1a;延续上篇【Hadoop和Hbase集群配置】继续配置Zookeeper&#xff0c;开启三台虚拟机Hadoop1、Hadoop2、Hadoop3&#xff0c;进入终端&#xff0c…

Transformer和BERT的区别

Transformer和BERT的区别比较表&#xff1a; 两者的位置编码&#xff1a; 为什么要对位置进行编码&#xff1f; Attention提取特征的时候&#xff0c;可以获取全局每个词对之间的关系&#xff0c;但是并没有显式保留时序信息&#xff0c;或者说位置信息。就算打乱序列中token…

Python爬虫如何处理验证码与登录

Python爬虫如何处理验证码与登录 Python 爬虫在抓取需要登录的网站数据时&#xff0c;通常会遇到两个主要问题&#xff1a;登录验证和验证码处理。这些机制是网站用来防止自动化程序过度抓取数据的主要手段。本文将详细讲解如何使用 Python 处理登录与验证码&#xff0c;以便进…

《深入浅出Apache Spark》系列②:Spark SQL原理精髓全解析

导读&#xff1a;SQL 诞生于 20 世纪 70 年代&#xff0c;至今已有半个世纪。SQL 语言具有语法简单&#xff0c;低学习门槛等特点&#xff0c;诞生之后迅速普及与流行开来。由于 SQL 具有易学易用的特点&#xff0c;使得开发人员容易掌握&#xff0c;企业若能在其计算机软件中支…

JS实现,防抖节流 + 闭包

防抖&#xff08;Debounce&#xff09; 防抖是指短时间内大量触发同一事件&#xff0c;只会在最后一次事件完成后延迟执行一次函数。 防抖的典型应用场景是输入框的搜索建议功能&#xff0c;用户输入时不需要每次输入都去查询&#xff0c;而是在用户停止输入一段时间后才进行…

安卓编程最方便的读写资料类SharedPreferences,多个APP共享

本文介绍Android平台进行数据存储的五大方式,分别如下: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解这五种方式的特点 第一种&#xff1a; 使用SharedPreferences存储数据 …

数据分析:转录组差异fgsea富集分析

文章目录 介绍加载R包数据链接导入数据数据预处理DE testing: 2BP vs no-BP比较limma-voomLoad steroid dataIn No-BP patientsIn 2BP patientsCompare gene expression vs bacterial mass其他系统信息介绍 转录组差异fgsea富集分析是一种基于基因集的富集分析方法,它关注的是…

Day13杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> res new Arra…

Avalonia11如何优雅的跨组件通信

背景&#xff1a; 官网只介绍了推荐适用ReactiveUI&#xff0c;没有过多的案例介绍&#xff0c;对于初入桌面应用开发的小白极其不友好。 本文介绍在Avalonia应用中通过ReactiveUI中的MessageBus进行跨组件通信. 假设需求案例&#xff1a; MainWindowViewModel中发送消息&a…

【开发实战】彻底了解 ThreadLocal

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主 ⛪️ 个人社区:个人社区 💞 个人主页:个人主页 🙉 专栏地址: ✅ Java 中级 🙉八股文专题:剑指大厂,手撕 J…

基于开源 AI 智能名片、S2B2C 商城小程序的用户获取成本优化分析

摘要&#xff1a;本文围绕用户获取成本&#xff08;CAC&#xff09;这一关键指标展开深入剖析&#xff0c;详细阐述其计算方式&#xff0c;并紧密结合开源 AI 智能名片与 S2B2C 商城小程序的独特性质&#xff0c;从多个维度探讨如何通过挖掘新的获客渠道、巧妙运用私域流量池等…

KV260 - PYNQ 主目录 - U盘挂载

目录 1. 简介 2. 具体操作 2.1 查看 USB 设备 2.2 查看 U 盘设备节点 2.3 挂载 U 盘到指定目录 2.4 查看挂载状态 2.5 卸载 U 盘 3. 总结 1. 简介 在 KV260 使用 Jupyter Lab 可以非常方便开发各种应用。有时不方便在 PC 端连接 U 盘&#xff0c;那么可以把 U 盘连在 …

金媒婚恋相亲系统10.4择爱开源旗舰版支持微信小程和抖音小程序上架

最近大家应该注意到了&#xff0c;金媒婚恋相亲系统已经更新至最新的10.4版本了&#xff01;本人作为商业用户也已经更新至最新的旗舰版了&#xff0c;更新的内容是啥&#xff01;这个官方都有列出&#xff0c;一个方面就是更新了多端的登录逻辑和UI 和后台CRM及很多细节的优化…

用环形数组实现队列(多种高级方法,由浅入深)

同普通数组实现的队列相比&#xff0c;普通数组的头结点和尾节点都是固定的&#xff0c;在进行移除的时候如果移除了一个节点&#xff0c;后面所有节点都需要进行移除操作&#xff0c;需要的时间复杂度更高 在环形数组中&#xff0c;确定了头尾指针的环形数组很好地解决了这一…

【毫米波雷达(七)】自动驾驶汽车中的精准定位——RTK定位技术

一、什么是RTK&#xff1f; RTK&#xff0c;英文全名叫做Real-time kinematic&#xff0c;也就是实时动态。这是一个简称&#xff0c;全称其实应该是RTK&#xff08;Real-time kinematic&#xff0c;实时动态&#xff09;载波相位差分技术。 二、RTK的组装 如上图所示&#x…

小北的字节跳动青训营与调用模型:调用模型:OpenAI API vs 微调开源Llama2/ChatGLM(持续更新中~~~)

前言 最近&#xff0c;字节跳动的青训营再次扬帆起航&#xff0c;作为第二次参与其中的小北&#xff0c;深感荣幸能借此机会为那些尚未了解青训营的友友们带来一些详细介绍。青训营不仅是一个技术学习与成长的摇篮&#xff0c;更是一个连接未来与梦想的桥梁~ 小北的青训营 X M…

通过DNS服务器架构解释DNS请求过程

在前面的章节&#xff0c;这里&#xff0c;基于PCAP数据包和RFC文档详细介绍了DNS请求和响应的每个字段的含义。但是在现实的网络世界中&#xff0c;DNS请求和响应的数据包是怎么流动的&#xff0c;会经过哪些设备。本文将着重说明一下目前网络空间中DNS请求和响应的流动过程。…

Netty实现WebSocket Server是否开启压缩深度分析

是否开启压缩会直接影响与客户端是否能够成功握手。 一、具体分析 通常客户端发起与Websocket连接一般是以下形式。 1&#xff09;包含6个必要的Header Request Headers Sec-WebSocket-Version: 13 Sec-WebSocket-Key: Nlpc0kiHFjRom5/62lj8bA Connection: Upgrade Upgrade…