目录
引言
Apache POI操作Excel的实用技巧
1.合并单元格操作
2.设置单元格样式
1. 创建样式对象
2. 设置边框
3. 设置底色
4. 设置对齐方式
5. 设置字体样式
6.设置自动换行
7. 应用样式到单元格
3. 定位和操作指定单元格
4.实现标签-值的形式
5.列宽设置
1. 设置单个列宽
2. 批量设置多列宽度
6.数据格式化
1. 设置数字格式
2. 设置日期格式
代码展示
1.POM依赖
2.实体类Mode
3.Controller层
4.Service层
获取源码
引言
在最近的MES系统开发中,我们需要导出BOM物料清单,并且客户对样式有较高要求。这就涉及到对POI库样式的精细调整,包括设置表格边框、合并单元格、设置单元格底色等常见操作。我通过实现一种模板,使得样式设计既美观又实用,并可以根据这个模板创建其他自定义格式。这一模板的主要功能包括:设置Excel表格的边框样式、添加背景色、合并单元格以及采用标签-值的展示形式(如“订单编号:BH000001”)。接下来,我将分享如何通过这种模板实现灵活的Excel导出功能,满足不同业务需求。
如下图是POM清单的一个实现模版:
下面让我们来先学习怎么通过POI库来实现表格边框,合并单元格,设置单元格底色,以及采用标签-值的展示形式这一系列操作!
Apache POI操作Excel的实用技巧
1.合并单元格操作
这个代码片段创建了一个Excel文件,生成了一个名为 "BOM物料清单" 的工作表,在第2行合并了从第1列到第10列的单元格(从第A列到第J列)最后,代码将生成的Excel文件转换为字节数组并返回。
// 使用 try-with-resources 语句,确保 Workbook 在使用完毕后会自动关闭
try (Workbook workbook = new XSSFWorkbook()) {
// 创建一个名为 "BOM物料清单" 的工作表
Sheet sheet = workbook.createSheet("BOM物料清单");
// 创建第2行,行号从0开始,因此创建第2行的行号是1
Row titleRow = sheet.createRow(1);
// 合并第2行的0列到9列的单元格,CellRangeAddress的参数:起始行、结束行、起始列、结束列
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
// 在合并后的区域中设置单元格内容
Cell titleCell = titleRow.createCell(0); // 在第1列创建单元格
titleCell.setCellValue("BOM物料清单"); // 设置显示内容,例如 "BOM物料清单"
// 创建输出流,用于将工作簿内容写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 将工作簿写入到输出流中
workbook.write(outputStream);
// 返回字节数组,outputStream.toByteArray() 返回Excel文件的二进制数据
return outputStream.toByteArray();
}
效果如下:
2.设置单元格样式
// 1. 创建样式对象
CellStyle style = workbook.createCellStyle();
// 2. 设置边框
style.setBorderTop(BorderStyle.THIN); // 上边框
style.setBorderBottom(BorderStyle.THIN); // 下边框
style.setBorderLeft(BorderStyle.THIN); // 左边框
style.setBorderRight(BorderStyle.THIN); // 右边框
// 3. 设置底色
style.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); // 设置颜色
style.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 设置填充模式
// 4. 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
// 5. 设置字体样式
Font font = workbook.createFont();
font.setBold(true); // 设置加粗
font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
font.setFontHeightInPoints((short) 12); // 设置字体大小
style.setFont(font); // 将字体应用到样式中
// 6. 设置自动换行
style.setWrapText(true); // 启用自动换行
// 7. 应用样式到单元格
cell.setCellStyle(style);
代码效果:
- 边框:单元格会有细线的上下左右边框。
- 底色:单元格背景为浅绿色。
- 对齐方式:单元格内容会居中对齐(水平和垂直)。
- 字体样式:字体加粗,颜色为红色,字体大小为12磅。
- 自动换行:如果单元格内容过长,内容会自动换行。
如下是对每一个小功能的详细解释:
1. 创建样式对象
createCellStyle(): 这个方法是创建一个新的 CellStyle
对象,所有的样式设置都会在这个对象上进行。CellStyle
可以定义单元格的外观,如边框、对齐方式、字体、填充颜色等。
2. 设置边框
setBorderTop
:设置单元格的上边框样式为 THIN
(细线)。BorderStyle
枚举提供了几种边框样式:THIN
(细线)、THICK
(粗线)、DOTTED
(点线)、DASHED
(虚线)等。
setBorderBottom
:设置单元格的下边框样式。
setBorderLeft
:设置单元格的左边框样式。
setBorderRight
:设置单元格的右边框样式。
3. 设置底色
setFillForegroundColor
:设置单元格的前景色(填充颜色)。在这里,IndexedColors.LIGHT_GREEN.getIndex()
获取了 IndexedColors
枚举中的 LIGHT_GREEN
(浅绿色)的颜色索引值,getIndex()
方法返回该颜色的数字标识。IndexedColors
枚举包括常见颜色,如 YELLOW
、RED
、BLUE
等。
setFillPattern(FillPatternType.SOLID_FOREGROUND)
:设置单元格的填充模式为 SOLID_FOREGROUND
,表示填充整个单元格背景色。FillPatternType
还可以选择其他模式,如 NO_FILL
(不填充)、SOLID_FOREGROUND
(完全填充)等。
颜色解释:
LIGHT_GREEN
是 IndexedColors
中的一个颜色常量,代表一种浅绿色。可以将单元格的背景色设置为浅绿色,增加表格的视觉效果,使其更具可读性和美观。
4. 设置对齐方式
setAlignment(HorizontalAlignment.CENTER)
:设置单元格内容在水平方向上的对齐方式。HorizontalAlignment.CENTER
表示水平居中对齐。其他选项包括 LEFT
(左对齐)和 RIGHT
(右对齐)。
setVerticalAlignment(VerticalAlignment.CENTER)
:设置单元格内容在垂直方向上的对齐方式。VerticalAlignment.CENTER
表示垂直居中对齐。其他选项包括 TOP
(顶部对齐)和 BOTTOM
(底部对齐)。
5. 设置字体样式
createFont()
:通过 workbook.createFont()
创建一个新的 Font
对象,用于设置字体样式。
setBold(true)
:设置字体加粗。
setColor(IndexedColors.RED.getIndex())
:设置字体颜色为红色。IndexedColors.RED.getIndex()
获取了 IndexedColors
中 RED
(红色)的颜色索引。除了 RED
,IndexedColors
还包含多种颜色,如 BLACK
、BLUE
、GREEN
等。
setFontHeightInPoints((short) 12)
:设置字体的大小为12磅(points)。你可以根据需求调整字体大小。
setFont(font)
:将字体样式应用到 CellStyle
中,使得字体的加粗、颜色和大小在单元格中生效。
6.设置自动换行
setWrapText(true)
:启用自动换行功能。当单元格内容过长时,文本会自动换行,以避免内容超出单元格边界。此设置特别有用,尤其是在表格中包含多行文字时。
7. 应用样式到单元格
setCellStyle(style)
:将之前定义的 style
应用到目标单元格 cell
上。所有在 style
中设置的样式(如边框、底色、字体、对齐方式等)都会在该单元格中生效。
3. 定位和操作指定单元格
通过行列号定位
行号和列号的下标都是从0开始,比如第一行的下标是0,第一列的下标是0
Row row = sheet.createRow(3); // 注意:行号从0开始,3代表第四行
Cell cell = row.createCell(0); // 列号也从0开始,0代表第一列
cell.setCellValue("单元格内容");
sheet.createRow(3)
:这将创建或返回Excel表格中的第4行(因为行号从0开始,3表示第4行)。row.createCell(0)
:这将创建或返回第4行的第1列单元格(列号从0开始,0表示第一列)。cell.setCellValue("单元格内容")
:为该单元格设置值为 "单元格内容"。
4.实现标签-值的形式
下面这段代码实现了在 Excel 表格中创建标签-值的形式,即每行包含一个标签单元格和一个值单元格。通常这种格式用于展示诸如 "订单编号"、"产品编号" 等信息,并且标签和对应的值是相邻的单元格。以下是代码的详细解释:
private void createLabelValuePair(Row row, int startCol,
String label, String value,
CellStyle labelStyle, CellStyle valueStyle) {
// 创建标签单元格
Cell labelCell = row.createCell(startCol);
labelCell.setCellValue(label);
labelCell.setCellStyle(labelStyle); // 标签使用一种样式(如带底色)
// 创建值单元格
Cell valueCell = row.createCell(startCol + 1);
valueCell.setCellValue(value);
valueCell.setCellStyle(valueStyle); // 值使用另一种样式(如不带底色)
}
参数说明:
Row row
:表示当前行对象。在该行上创建标签和值的单元格。int startCol
:表示开始列的列号。标签单元格的列号是从startCol
开始,值单元格紧随其后,列号为startCol + 1
。String label
:标签文本,例如 "订单编号"、"产品编号" 等。String value
:标签对应的值,例如 "BH00000002"、"CP00000002" 等。CellStyle labelStyle
:标签单元格的样式,可以设置字体、对齐方式、背景色等。CellStyle valueStyle
:值单元格的样式,用于设置不同于标签的样式。
功能:
- 该方法用于创建一对标签和值,两个单元格位于同一行(
row
)。标签单元格位于startCol
列,值单元格位于startCol + 1
列。 - 这两个单元格分别应用不同的样式(
labelStyle
和valueStyle
),从而使标签和值的显示效果有所区别。标签单元格可能使用不同的字体、颜色、背景等样式,而值单元格则可能有不同的格式。
使用示例:
Row row = sheet.createRow(3); // 注意:行号从0开始,3代表第四行
createLabelValuePair(row, 0, "订单编号", "BH00000002", headerStyle, valueStyle);
createLabelValuePair(row, 2, "产品编号", "CP00000002", headerStyle, valueStyle);
5.列宽设置
1. 设置单个列宽
代码通过 sheet.setColumnWidth()
方法来调整列宽。下面是详细的解释:
sheet.setColumnWidth(columnIndex, 15 * 256); // 15个字符宽度
解释:
sheet.setColumnWidth(columnIndex, 15 * 256)
:该方法设置 Excel 中指定列的宽度。columnIndex
:表示列的索引,Excel 中列的索引从 0 开始。例如,0
表示 A 列,1
表示 B 列,以此类推。15 * 256
:列宽的单位是 "字符宽度",但在 POI 中,单位是字符宽度的 1/256,因此需要乘以256
。15
表示列宽为 15 个字符宽度(即单元格中可以容纳15个字符长度)。256
是 POI 中列宽的缩放因子,表示字符宽度单位是 1/256 的一个字符宽度。所以15 * 256
表示列宽为 15 个字符的宽度。
在这个例子中,sheet.setColumnWidth(columnIndex, 15 * 256)
将设置列的宽度为15个字符的宽度,columnIndex
列号是动态指定的。
2. 批量设置多列宽度
private void setColumnWidths(Sheet sheet) {
int[] widths = {15, 15, 15, 12, 12, 12, 15, 12, 15, 12};
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, widths[i] * 256);
}
}
解释:
-
int[] widths = {15, 15, 15, 12, 12, 12, 15, 12, 15, 12};
:- 这是一个整型数组
widths
,它存储了每一列的宽度设置值。数组中的每个值代表对应列的宽度(单位是字符的数量)。 15, 15, 15
等表示前面列宽设置为 15 个字符,12, 12, 12
等表示后面列宽设置为 12 个字符。
- 这是一个整型数组
-
for (int i = 0; i < widths.length; i++) { ... }
:- 这是一个循环,用于遍历
widths
数组中的每个元素。 widths.length
返回数组的长度(此处为 10),所以循环会执行 10 次,每次处理一个列的宽度。
- 这是一个循环,用于遍历
-
sheet.setColumnWidth(i, widths[i] * 256);
:i
是列索引,因此i
从0
到9
(共10列)。widths[i]
获取数组中当前索引位置的宽度值,将这个值乘以256
以得到正确的列宽单位(字符宽度的 1/256)。sheet.setColumnWidth(i, widths[i] * 256)
将依次设置每一列的宽度,确保列宽符合指定的字符数。
6.数据格式化
这段代码展示了如何在 Apache POI 中设置 Excel 单元格的数据格式,具体包括数字格式和日期格式的设置。以下是对每一部分的详细解释:
1. 设置数字格式
CellStyle numberStyle = workbook.createCellStyle();
DataFormat format = workbook.createDataFormat();
numberStyle.setDataFormat(format.getFormat("#,##0.00"));
解释:
-
CellStyle numberStyle = workbook.createCellStyle();
:- 这行代码创建一个新的
CellStyle
对象(numberStyle
),该对象用于设置单元格的样式。 CellStyle
用于控制单元格的外观,比如字体、边框、对齐方式以及数据格式等。
- 这行代码创建一个新的
-
DataFormat format = workbook.createDataFormat();
:createDataFormat()
是Workbook
类的一个方法,用于创建一个DataFormat
对象。DataFormat
类用于定义单元格的数据格式(如日期、数字、货币等)。format
对象将用于设置不同类型的格式,允许将格式应用于CellStyle
中。
-
numberStyle.setDataFormat(format.getFormat("#,##0.00"));
:setDataFormat()
方法用于为CellStyle
设置数据格式。format.getFormat("#,##0.00")
使用DataFormat
对象来定义具体的数字格式。这段格式表示:#,##0.00
:这是数字的格式模式,表示数字应该使用千位分隔符(,
),并且保留两位小数(.00
)。例如,数字1234567.89
会显示为1,234,567.89
。#
是占位符,表示数字的每个位置(但如果没有值,它就不显示)。0
表示即使数字为零,也会显示该位置的零。.00
表示保留两位小数,即使实际数据没有小数部分,也会显示为0.00
。
2. 设置日期格式
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(format.getFormat("yyyy/mm/dd"));
解释:
-
CellStyle dateStyle = workbook.createCellStyle();
:- 这行代码创建一个新的
CellStyle
对象(dateStyle
),用于设置日期单元格的格式。 - 这个
CellStyle
对象将应用于日期格式的单元格。
- 这行代码创建一个新的
-
dateStyle.setDataFormat(format.getFormat("yyyy/mm/dd"));
:setDataFormat()
方法为dateStyle
设置数据格式。format.getFormat("yyyy/mm/dd")
使用DataFormat
对象来设置日期的格式。具体格式为:yyyy/mm/dd
:这是日期的格式模式,表示日期应该显示为年-月-日的格式。yyyy
表示四位年份(例如 2025)。mm
表示两位月份(例如 01 表示一月,12 表示十二月)。dd
表示两位日期(例如 01 表示第一天,31 表示最后一天)。
- 例如,日期
2025/01/17
会显示为2025/01/17
。
总结:
这段代码展示了如何为 Excel 单元格设置数字和日期格式,具体包括:
- 数字格式:将单元格内容设置为数字格式,使用千位分隔符并保留两位小数(例如:
1,234,567.89
)。 - 日期格式:将单元格内容设置为日期格式,显示为年-月-日(例如:
2025/01/17
)。
代码展示
1.POM依赖
POM依赖需要引入org.apache.poi
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
2.实体类Mode
BOM清单标题头
public class BOMHeader {
private String orderNumber; // 订单编号
private String productNumber; // 产品编号
private String productName; // 产品名称
private String specification; // 规格型号
private Integer orderQuantity; // 订单数量
private String planStartDate; // 计划投产日期
private Integer productionCycle; // 计划生产周期
private String planEndDate; // 计划截止日期
private String deliveryDate; // 订单交期
private Double totalCost; // 合计成本
public BOMHeader(String orderNumber, String productNumber, String productName, String specification, Integer orderQuantity, String planStartDate, Integer productionCycle, String planEndDate, String deliveryDate, Double totalCost) {
this.orderNumber = orderNumber;
this.productNumber = productNumber;
this.productName = productName;
this.specification = specification;
this.orderQuantity = orderQuantity;
this.planStartDate = planStartDate;
this.productionCycle = productionCycle;
this.planEndDate = planEndDate;
this.deliveryDate = deliveryDate;
this.totalCost = totalCost;
}
public BOMHeader() {
}
public String getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getProductNumber() {
return productNumber;
}
public void setProductNumber(String productNumber) {
this.productNumber = productNumber;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getSpecification() {
return specification;
}
public void setSpecification(String specification) {
this.specification = specification;
}
public Integer getOrderQuantity() {
return orderQuantity;
}
public void setOrderQuantity(Integer orderQuantity) {
this.orderQuantity = orderQuantity;
}
public String getPlanStartDate() {
return planStartDate;
}
public void setPlanStartDate(String planStartDate) {
this.planStartDate = planStartDate;
}
public Integer getProductionCycle() {
return productionCycle;
}
public void setProductionCycle(Integer productionCycle) {
this.productionCycle = productionCycle;
}
public String getPlanEndDate() {
return planEndDate;
}
public void setPlanEndDate(String planEndDate) {
this.planEndDate = planEndDate;
}
public String getDeliveryDate() {
return deliveryDate;
}
public void setDeliveryDate(String deliveryDate) {
this.deliveryDate = deliveryDate;
}
public Double getTotalCost() {
return totalCost;
}
public void setTotalCost(Double totalCost) {
this.totalCost = totalCost;
}
}
BOM清单列表
public class BOMItem {
private String materialCode; // 料号
private String name; // 品名
private String modelNumber; // 型号
private String unit; // 计量单位
private Integer quantity; // 用量
private String manufacturer; // 厂家/品牌
private String channel; // 途径/渠道
private Double unitPrice; // 单价
private Double total; // 合计
private String remarks; // 备注
public BOMItem(String materialCode, String name, String modelNumber, String unit, Integer quantity, String manufacturer, String channel, Double unitPrice, Double total, String remarks) {
this.materialCode = materialCode;
this.name = name;
this.modelNumber = modelNumber;
this.unit = unit;
this.quantity = quantity;
this.manufacturer = manufacturer;
this.channel = channel;
this.unitPrice = unitPrice;
this.total = total;
this.remarks = remarks;
}
public BOMItem() {
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getModelNumber() {
return modelNumber;
}
public void setModelNumber(String modelNumber) {
this.modelNumber = modelNumber;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public Double getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(Double unitPrice) {
this.unitPrice = unitPrice;
}
public Double getTotal() {
return total;
}
public void setTotal(Double total) {
this.total = total;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
}
3.Controller层
import com.e.toexcel.model.BOMHeader;
import com.e.toexcel.model.BOMItem;
import com.e.toexcel.service.BOMExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/bom")
public class BOMController {
@Autowired
private BOMExcelService bomExcelService;
@GetMapping("/export")
public ResponseEntity<byte[]> exportBOM() {
try {
// 创建示例数据
BOMHeader header = createSampleHeader();
List<BOMItem> items = createSampleItems();
// 生成Excel文件
byte[] excelContent = bomExcelService.generateBOMExcel(header, items);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 使用 URLEncoder 对文件名进行编码
String filename = URLEncoder.encode("BOM物料清单.xlsx", StandardCharsets.UTF_8.name());
// 替换空格编码
filename = filename.replaceAll("\\+", "%20");
// 设置 Content-Disposition 头
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + filename);
return ResponseEntity.ok()
.headers(headers)
.body(excelContent);
} catch (Exception e) {
e.printStackTrace(); // 添加日志输出以便调试
return ResponseEntity.internalServerError().build();
}
}
private BOMHeader createSampleHeader() {
return new BOMHeader(
"BH00000002",
"CP00000002",
"自动包装机",
"CS5506",
80000,
"2024/3/15",
25,
"2024/4/10",
"2024/4/20",
98560000.00
);
}
private List<BOMItem> createSampleItems() {
List<BOMItem> items = new ArrayList<>();
// 添加所有示例数据
items.add(createBOMItem("WL000101", "传送带", "ZJ000101", "个", 40000, "ABC品牌", "采购链接: yyyy", 120.00));
items.add(createBOMItem("WL000102", "电机", "ZJ000102", "个", 80000, "DEF品牌", "自制生产", 0.00));
items.add(createBOMItem("WL000103", "控制器", "ZJ000103", "个", 40000, "GHI品牌", "采购链接: yyyy", 350.00));
items.add(createBOMItem("WL000104", "感应器", "ZJ000104", "个", 160000, "JKL品牌", "采购链接: yyyy", 80.00));
items.add(createBOMItem("WL000105", "支架", "ZJ000105", "个", 80000, "MNO品牌", "自制生产", 0.00));
items.add(createBOMItem("WL000106", "螺丝套件", "ZJ000106", "个", 320000, "PQR品牌", "采购链接: yyyy", 25.00));
items.add(createBOMItem("WL000107", "线缆", "ZJ000107", "个", 240000, "STU品牌", "采购链接: yyyy", 45.00));
items.add(createBOMItem("WL000108", "外壳", "ZJ000108", "个", 80000, "VWX品牌", "自制生产", 0.00));
items.add(createBOMItem("WL000109", "显示屏", "ZJ000109", "个", 80000, "YZA品牌", "采购链接: yyyy", 280.00));
items.add(createBOMItem("WL000110", "按钮组", "ZJ000110", "个", 160000, "BCD品牌", "采购链接: yyyy", 95.00));
items.add(createBOMItem("WL000111", "密封圈", "ZJ000111", "个", 240000, "EFG品牌", "采购链接: yyyy", 35.00));
items.add(createBOMItem("WL000112", "铭牌", "ZJ000112", "个", 80000, "HIJ品牌", "采购链接: yyyy", 15.00));
items.add(createBOMItem("WL000113", "包装材料", "ZJ000113", "个", 80000, "KLM品牌", "采购链接: yyyy", 12.00));
return items;
}
private BOMItem createBOMItem(String code, String name, String model, String unit,
int quantity, String manufacturer, String channel, double price) {
double total = channel.equals("自制生产") ? 0.00 : price * quantity;
return new BOMItem(
code,
name,
model,
unit,
quantity,
manufacturer,
channel,
price,
total,
"" // remarks
);
}
}
4.Service层
package com.e.toexcel.service;
import com.e.toexcel.model.BOMHeader;
import com.e.toexcel.model.BOMItem;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@Service
public class BOMExcelService {
private CellStyle valueStyle; // 添加成员变量
private void createTitle(Sheet sheet, CellStyle style) {
Row titleRow = sheet.createRow(1);
// 为所有要合并的单元格创建样式
for (int i = 0; i < 10; i++) {
Cell cell = titleRow.createCell(i);
cell.setCellStyle(style);
// 只在第一个单元格设置值
if (i == 0) {
cell.setCellValue("BOM物料清单");
}
}
// 合并单元格
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
}
private void createBasicInfo(Sheet sheet, BOMHeader header, CellStyle style) {
// 第一行基本信息
Row row1 = sheet.createRow(3);
createHeaderCell(row1, 0, "订单编号", header.getOrderNumber(), style);
createHeaderCell(row1, 2, "产品编号", header.getProductNumber(), style);
createHeaderCell(row1, 4, "产品名称", header.getProductName(), style);
createHeaderCell(row1, 6, "规格型号", header.getSpecification(), style);
createHeaderCell(row1, 8, "订单数量", String.valueOf(header.getOrderQuantity()), style);
// 第二行基本信息
Row row2 = sheet.createRow(4);
createHeaderCell(row2, 0, "计划投产日期", header.getPlanStartDate(), style);
createHeaderCell(row2, 2, "计划生产周期", String.valueOf(header.getProductionCycle()), style);
createHeaderCell(row2, 4, "计划截止日期", header.getPlanEndDate(), style);
createHeaderCell(row2, 6, "订单交期", header.getDeliveryDate(), style);
createHeaderCell(row2, 8, "合计成本", String.format("%.2f", header.getTotalCost()), style);
}
private void createHeaderCell(Row row, int col, String label, String value, CellStyle headerStyle) {
Cell labelCell = row.createCell(col);
labelCell.setCellValue(label);
labelCell.setCellStyle(headerStyle);
Cell valueCell = row.createCell(col + 1);
valueCell.setCellValue(value);
valueCell.setCellStyle(this.valueStyle);
}
private void createTableHeader(Sheet sheet, CellStyle style) {
Row headerRow = sheet.createRow(6);
String[] headers = {"料号", "品名", "型号", "计量单位", "用量", "厂家/品牌", "途径/渠道", "单价", "合计", "备注"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(style);
}
}
private void fillData(Sheet sheet, List<BOMItem> items, CellStyle style) {
int rowNum = 7;
for (BOMItem item : items) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(item.getMaterialCode());
row.createCell(1).setCellValue(item.getName());
row.createCell(2).setCellValue(item.getModelNumber());
row.createCell(3).setCellValue(item.getUnit());
row.createCell(4).setCellValue(item.getQuantity());
row.createCell(5).setCellValue(item.getManufacturer());
row.createCell(6).setCellValue(item.getChannel());
row.createCell(7).setCellValue(item.getUnitPrice());
row.createCell(8).setCellValue(item.getTotal());
row.createCell(9).setCellValue(item.getRemarks());
for (int i = 0; i < 10; i++) {
row.getCell(i).setCellStyle(style);
}
}
}
private void createNote(Sheet sheet, CellStyle style) {
Row noteRow = sheet.createRow(sheet.getLastRowNum() + 1);
// 创建所有需要合并的单元格并设置样式
for (int i = 0; i < 10; i++) {
Cell cell = noteRow.createCell(i);
cell.setCellStyle(style);
// 只在第一个单元格设置值
if (i == 0) {
cell.setCellValue("说明:采购渠道请与采购部门进行确认,如物料采购困难,请及时与研发人员沟通更换其他替代品");
}
}
// 合并单元格
sheet.addMergedRegion(new CellRangeAddress(noteRow.getRowNum(), noteRow.getRowNum(), 0, 9));
}
private void setColumnWidths(Sheet sheet) {
int[] widths = {15, 15, 15, 12, 12, 12, 15, 12, 15, 12};
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, widths[i] * 256);
}
}
public byte[] generateBOMExcel(BOMHeader header, List<BOMItem> items) throws IOException {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("BOM物料清单");
// 创建样式
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle normalStyle = createNormalStyle(workbook);
this.valueStyle = createValueStyle(workbook); // 初始化值样式
// 设置标题
createTitle(sheet, headerStyle);
// 设置基本信息
createBasicInfo(sheet, header, headerStyle);
// 创建表头
createTableHeader(sheet, headerStyle);
// 填充数据
fillData(sheet, items, normalStyle);
// 添加说明
createNote(sheet, normalStyle);
// 调整列宽
setColumnWidths(sheet);
// 导出
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
// 该方法用于创建 Excel 表头的单元格样式
private CellStyle createHeaderStyle(Workbook workbook) {
// 创建一个新的单元格样式
CellStyle style = workbook.createCellStyle();
// 设置单元格的前景填充颜色为浅绿
style.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
// 设置填充模式为纯色填充
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置上边框为细线
style.setBorderTop(BorderStyle.THIN);
// 设置下边框为细线
style.setBorderBottom(BorderStyle.THIN);
// 设置左边框为细线
style.setBorderLeft(BorderStyle.THIN);
// 设置右边框为细线
style.setBorderRight(BorderStyle.THIN);
// 设置水平对齐方式为居中
style.setAlignment(HorizontalAlignment.CENTER);
// 设置垂直对齐方式为居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 返回创建好的单元格样式
return style;
}
// 该方法用于创建 Excel 普通单元格的样式
private CellStyle createNormalStyle(Workbook workbook) {
// 创建一个新的单元格样式
CellStyle style = workbook.createCellStyle();
// 设置上边框为细线
style.setBorderTop(BorderStyle.THIN);
// 设置下边框为细线
style.setBorderBottom(BorderStyle.THIN);
// 设置左边框为细线
style.setBorderLeft(BorderStyle.THIN);
// 设置右边框为细线
style.setBorderRight(BorderStyle.THIN);
// 设置水平对齐方式为居中
style.setAlignment(HorizontalAlignment.CENTER);
// 设置垂直对齐方式为居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 返回创建好的单元格样式
return style;
}
// 此方法用于创建 Excel 中存储值的单元格的样式
private CellStyle createValueStyle(Workbook workbook) {
// 创建一个新的单元格样式对象
CellStyle style = workbook.createCellStyle();
// 设置上边框为细线
style.setBorderTop(BorderStyle.THIN);
// 设置下边框为细线
style.setBorderBottom(BorderStyle.THIN);
// 设置左边框为细线
style.setBorderLeft(BorderStyle.THIN);
// 设置右边框为细线
style.setBorderRight(BorderStyle.THIN);
// 设置单元格内容的水平对齐方式为居中
style.setAlignment(HorizontalAlignment.CENTER);
// 设置单元格内容的垂直对齐方式为居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 将创建好的单元格样式对象返回
return style;
}
}
获取源码
源码地址:源码地址
写到这里也就结束了,如果你觉得此文章对你有所帮助的话就一键三连,在下谢谢您嘞!