利用Java自定义格式,循环导出数据、图片到excel

发布于:2025-07-17 ⋅ 阅读:(19) ⋅ 点赞:(0)

1、自定义格式循环导出数据

我有一个需求,需要做一个标签,格式固定,但是数据从数据库中获取,手动一个一个写不现实,所以我考虑写个方法,自定义格式,循环的将数据导出到excel。
说明:

因为我要做的一个标签,占据excel 5行4列,所以在下面的代码中,会提到 4 5或者这两数的倍数关系,请注意这个情况

1.1.设置格式

1.1.1、居中样式

// 创建居中样式
    private static CellStyle createCenterStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        // 水平居中
        style.setAlignment(HorizontalAlignment.CENTER);
        // 垂直居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置边框
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        //字体
        Font font = workbook.createFont();
        font.setFontName("微软雅黑"); // 设置字体类型
        font.setFontHeightInPoints((short) 8); // 设置字体大小
        style.setFont(font);
        return style;
    }

1.1.2、应用样式到合并区域

    /**
     * 应用样式到合并区域
     * @param sheet 工作表
     * @param region 合并区域
     * @param style 样式
     */
    private static void applyStyleToMergedRegion(Sheet sheet, CellRangeAddress region, CellStyle style) {
        for (int row = region.getFirstRow(); row <= region.getLastRow(); row++) {
            Row sheetRow = sheet.getRow(row) != null ? sheet.getRow(row) : sheet.createRow(row);
            for (int col = region.getFirstColumn(); col <= region.getLastColumn(); col++) {
                Cell cell = sheetRow.getCell(col) != null ?
                        sheetRow.getCell(col) : sheetRow.createCell(col);
                cell.setCellStyle(style);
            }
        }
    }

1.1.3、合并单元格

	/**
     * 合并单元格并居中
     * @param sheet 工作表
     * @param centerStyle 样式
     * @param columnsNum 列数
     * @param lineNum 行数
     */
    private static void mergeAndCenterCells(Sheet sheet, CellStyle centerStyle,int columnsNum, int lineNum) {
        for (int i = 0; i < lineNum; i=i+5) {
            for (int j = 0; j < columnsNum; j=j+4) {
                //标签头部的合并区域  同一行 4列
                CellRangeAddress titleMerge = new CellRangeAddress(i, i, j, j+3);
                //图片合并区域 标签头部第2行到第5行 第1列到第2列
                CellRangeAddress photoMerge = new CellRangeAddress(i+1, i+4, j, j+1);
                //数据合并区域
                CellRangeAddress queMerge = new CellRangeAddress(i+3, i+3, j+2, j+3);
                CellRangeAddress timeMerge = new CellRangeAddress(i+4, i+4, j+2, j+3);
                //写入工作表
                sheet.addMergedRegion(titleMerge);
                sheet.addMergedRegion(photoMerge);
                sheet.addMergedRegion(queMerge);
                sheet.addMergedRegion(timeMerge);
                // 应用居中样式到合并区域
                applyStyleToMergedRegion(sheet, titleMerge, centerStyle);
                applyStyleToMergedRegion(sheet, photoMerge, centerStyle);
                applyStyleToMergedRegion(sheet, queMerge, centerStyle);
                applyStyleToMergedRegion(sheet, timeMerge, centerStyle);
            }
        }

    }

1.1.4、设置列宽

循环设置标签列宽,以适应图片和数据,大小 1 * 256 为1个字符。

 	/**
     * 设置列的宽度 以适应图片和数据
     * @param columnsNum 列数
     * @param sheet 工作表
     */
    private static void setColumnWidth(int columnsNum,Sheet sheet){
    //循环设置标签的列宽
        for (int i=0;i<columnsNum;i=i+4){
            sheet.setColumnWidth(i, 4 * 256); // 设置第一列宽度为4个字符
            sheet.setColumnWidth(i+1, 4 * 256); // 设置第二列宽度为4个字符
            sheet.setColumnWidth(i+2, 12 * 256); // 设置第三列宽度为12个字符
            sheet.setColumnWidth(i+3, 12 * 256); // 设置第四列宽度为12个字符
        }
    }

1.2、写入数据

1.2.1、创建标签头部

	/**
     * 创建标签头部
     * @param sheet 工作表
     * @param style 居中样式
     * @param mainTitleStr 头部数据
     * @param columnsNum 列数
     * @param lineNum 行数
     */
    private static void createHeader(Sheet sheet, CellStyle style,String mainTitleStr,int columnsNum, int lineNum) {
        for (int i = 0; i < lineNum; i=i+5) {// 隔5行设置一次
            Row headerRow = sheet.createRow(i);
            // 主标题(将被合并)
            for (int j = 0; j < columnsNum; j=j+4) {//隔4列设置一次
                Cell mainTitle = headerRow.createCell(j);
                mainTitle.setCellValue(mainTitleStr);
                mainTitle.setCellStyle(style);
            }
        }
    }

1.2.2、写入标签内容

标签固定的内容可以在这里先写好,其他有变动的可以从其他渠道获取后再去拼接。

    /**
     * 设置内容格式及数据 固定不变的可以在这里写好 其他从数据库取到的再去拼接
     * @param sheet 工作表
     * @param style 样式
     * @param List 从数据库或其他途径取到的数据
     * @param columnsNum 列数
     * @param lineNum 行数
     */
    private static void createContent(Sheet sheet, CellStyle style,List<Map<Object,Object>> List,int columnsNum, int lineNum) {
        int lineTemp=0;// 临时的 行数
        int columnsTemp=0;// 临时的 列数

        for (Map<Object,Object> map:List) {
            //搞成二位数据 直接固定格式 后续方便固定格式 写入excel表格
            Object[][] data = {
                    {"型号:"+map.get("name"), "夹具类型:"+ map.get("type")},
                    {"精度:符合标准", "编号:"+map.get("num")},
                    {"确认人:xxx □    xxx □   "},
                    {"点检有效期:"+ DateUtil.year(new Date())+"       - "+ DateUtil.year(new Date())+"         "},
            };
            //遍历二维数组
            for (int i = 0; i < data.length; i++) {
                int temp=lineTemp+i + 1;//要生成指定行 行数  +1 是因为数据在标签头部下面写入
                //这么做的目的是 为了避免重新生成一行,导致之前的数据被删除
                Row row=sheet.getRow(temp);//获得之前生成的行
                if (row==null){//如果之前未生成行 那将需要去生成
                    row = sheet.createRow(temp);
                }
                int cellTemp=columnsTemp+2;//指定哪一列 +2是因为前面两列是设置图片
                for (int j = 0; j < data[i].length; j++) {
                    Cell value = row.createCell(cellTemp);
                    row.createCell(cellTemp).setCellValue(data[i][j].toString());//写入内容
                    value.setCellStyle(style);//设置格式
                    cellTemp+=1;//下一列
                }
            }
            //为了不超过设置的行数 和列数 做出如下判断
            if (columnsTemp<columnsNum && (columnsTemp+4)!=columnsNum){
                columnsTemp=columnsTemp+4;
            }else {
                columnsTemp=0;
                if (lineTemp<lineNum && (lineTemp+5)!=lineNum){
                    lineTemp=lineTemp+5;
                }else{
                    lineTemp=0;
                }
            }
        }
    }

2、自定义格式循环导出图片

在每个标签中,我都要插入一个二维码,位置都是固定的,所以就自定义图片的位置,并且循环插入不同的二维码。

2.1、设置格式并插入图片

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * 图片保存到excel的类
 */
public class ImageForExcel {

    /**
     * 设置图片保存到excel的位置
     * @param workbook excel
     * @param sheet 工作表
     * @param photoPath 插入图片的路径 一个list集合
     * @param columnsNum 列数
     * @param lineNum 行数
     */
    public static void setPhoto(Workbook workbook, Sheet sheet, List<String> photoPath, int columnsNum, int lineNum){
        int index=0;
        for (int i = 0; i < lineNum; i=i+5) {
            for (int j = 0; j < columnsNum; j=j+4) {
                // 读取图片文件
                try (InputStream is = new FileInputStream(photoPath.get(index))) {
                    // 将图片转换为字节数组
                    byte[] bytes = IOUtils.toByteArray(is);
                    // 添加图片到工作簿
                    int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
                    // 创建绘图工具
                    CreationHelper helper = workbook.getCreationHelper();
                    Drawing<?> drawing = sheet.createDrawingPatriarch();
                    // 创建锚点,设置图片位置
                    ClientAnchor anchor = helper.createClientAnchor();
                    anchor.setCol1(j); // 图片起始列(0-based)
                    anchor.setRow1(i+1); // 图片起始行(0-based)
                    anchor.setCol2(j+2); // 图片结束列(0-based)
                    anchor.setRow2(i+5); // 图片结束行(0-based)
                    // 创建图片并设置锚点
                    Picture pict = drawing.createPicture(anchor, pictureIdx);
                    index=index+1;
                    // 可选:调整单元格大小以适应图片
//            sheet.setColumnWidth(1, 4 * 256); // 调整列宽
//            sheet.setColumnWidth(0, 4 * 256); // 调整列宽
            /*Row row = sheet.createRow(3);
            row.setHeightInPoints(100); // 调整行高
            */
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3、格式和数据写入统一封装

将格式设计和写入数据的方法同意封装成一个方法,方便调用。

/**
     * 统一封装的方法
     * @param mainTitleStr 标签头部
     * @param exportPath 导出的路径 二维码图片获取的路径
     * @param columnsNum 标签的总列数
     * @param lineNum 标签的总行数
     * @param data 变动的数据
     * @return
     * @throws Exception
     */
    public static String exportTitle(String mainTitleStr, String exportPath,
                                     int columnsNum, int lineNum, List<Map<Object,Object>> data) throws Exception {
         String excelPath=exportPath+"标签WZ.xlsx";
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("标签");//工作表名
        // 1、设置列宽(单位是1/256个字符宽度)
        setColumnWidth(columnsNum,sheet);
        // 设置行高(单位是点,1点=1/20像素)
//        Row row = sheet.createRow(0);
//        row.setHeightInPoints(30); // 设置行高为30点
        // 2. 创建居中样式
        CellStyle centerStyle = createCenterStyle(workbook);

        // 3. 设置表头数据
        createHeader(sheet, centerStyle,mainTitleStr,columnsNum,lineNum);

        // 4. 设置内容数据
        createContent(sheet, centerStyle,data,columnsNum,lineNum);

        // 5. 设置合并区域并应用居中样式
        mergeAndCenterCells(sheet, centerStyle,columnsNum,lineNum);
        //将图片插入excel
        List<String> photoPath=new ArrayList<>();
        for (Map<Object,Object> map:data) {
            photoPath.add(exportPath+map.get("mold").toString()+".png");
        }
        ImageForExcel.setPhoto(workbook,sheet,photoPath,columnsNum,lineNum);
        // 7. 保存文件
        try (FileOutputStream out = new FileOutputStream(excelPath)) {
            workbook.write(out);
        }
        return excelPath;
    }

4、测试方法和效果

文字数据是我自己编的,如果大家要从数据库或者其他来源获取,改动data即可。
二维码图片是我利用二维码生成方法生成的,如果大家有兴趣,可以看看我上一篇的博客。点击这里: 二维码生成方法

4.1、测试方法

这个是有循环生成多个标签的功能。
所以列数 行数 需要根据自己的需求。
如不需要循环,只需将行列数设置为一个标签所需要占据的数即可。
目前我的测试方法是,一个标签占据5行4列,下面设定是有4个标签。
如果对代码中 4 5 或者 4 5 倍数 ,又或者其他数据有疑问的话,可以看看这里

import com.codermy.myspringsecurityplus.utils.qrUtils.TitleForExcel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BiaoQian {

    public static void main(String[] args) throws Exception {

        //1、表头
        String mainTitleStr="夹具确认表 DOC-190-AA0004-01D";
        //2、excel存放的位置
        String exportPath="D:/java/codeSpace/testFile/";
        /*
        这个是有生成多个的功能 所以
        列数 行数 需要根据自己的需求
        如不需要循环,只需将行列数设置为一个标签所需要占据的数即可
        目前我的数据是,一个标签占据5行4列,下面设定是循环写入4个标签
        如果对代码中 4 5  或者 4 5 倍数 ,又或者其他数据有疑问的话,可以看看这里
        */
        //3、列数
        int columnsNum=8;
        //4、行数
        int lineNum=10;
        //5、数据   可以从数据库中得到 然后再按照我们需要的数据做处理
        // 下面是我自己模拟的一些数据
        List<Map<Object,Object>> data=new ArrayList<>();
        String name="C40888";
        String type="WZ";
        //这个 (i < 5) ,需要对应上面的行列数 有四个标签
        for (int i = 1; i < 5; i++) {
            Map<Object,Object> map=new HashMap<>();
            map.put("name",name);
            map.put("type",type);
            map.put("num",i);
            map.put("mold",name+"-"+type+"-"+i);
            data.add(map);
        }
        String result=TitleForExcel.exportTitle(mainTitleStr,exportPath,columnsNum,lineNum,data);
        System.out.println(result);
    }

}

4.2、运行效果

由于图片有二维码,会被和谐,所以我在二维码上搞了贴纸,实际导出效果没有贴纸
在这里插入图片描述


以上就是本篇文章的全部内容,部分代码是利用AI生成,然后再去修改成我想要的效果,如果有侵权的地方,还请联系本人。
本文有很多可以修改优化的地方,如果各位同学有好的见解可以评论留言讨论。
如果代码有异常,或者有其他疑惑、可以评论区留言。


网站公告

今日签到

点亮在社区的每一天
去签到