利用Java自定义格式,循环导出数据、图片到excel
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生成,然后再去修改成我想要的效果,如果有侵权的地方,还请联系本人。
本文有很多可以修改优化的地方,如果各位同学有好的见解可以评论留言讨论。
如果代码有异常,或者有其他疑惑、可以评论区留言。