Spring Boot + Apache POI 实现 Excel 导出:BOM物料清单生成器(支持中文文件名、样式美化、数据合并)
目录
引言
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行的行号是1Row 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 {@Autowiredprivate 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;}
}
获取源码
源码地址:源码地址
写到这里也就结束了,如果你觉得此文章对你有所帮助的话就一键三连,在下谢谢您嘞!
相关文章:

Spring Boot + Apache POI 实现 Excel 导出:BOM物料清单生成器(支持中文文件名、样式美化、数据合并)
目录 引言 Apache POI操作Excel的实用技巧 1.合并单元格操作 2.设置单元格样式 1. 创建样式对象 2. 设置边框 3. 设置底色 4. 设置对齐方式 5. 设置字体样式 6.设置自动换行 7. 应用样式到单元格 3. 定位和操作指定单元格 4.实现标签-值的形式 5.列宽设置 1. 设…...
ReactiveSwift 简单使用
记录 ReactiveSwift 简单使用 导入 ReactiveSwift 库创建 TestViewModel 文件 enum JKTypeType: Int {case cloudcase devicecase weater }// 通过监听属性变化 class TestViewModel: NSObject {lazy var recordType: Property<JKTypeType> {return Property(recordTy…...

CSS 的基础知识及应用
前言 CSS(层叠样式表)是网页设计和开发中不可或缺的一部分。它用于描述网页的视觉表现,使页面不仅实现功能,还能提供吸引人的用户体验。本文将介绍 CSS 的基本概念、语法、选择器及其在提升网页美观性方面的重要性。 什么是 CSS&…...

【Web】2025西湖论剑·中国杭州网络安全安全技能大赛题解(全)
目录 Rank-l Rank-U sqli or not Rank-l username存在报错回显,发现可以打SSTI 本地起一个服务,折半查找fuzz黑名单,不断扔给fenjing去迭代改payload from flask import Flask, request, render_template_stringapp Flask(__name__)app…...

能源物联网数据采集设备 串口服务器功能参数介绍
摘要 随着物联网技术的快速发展,各种传统设备的联网需求愈发迫切。串口服务器作为一种桥接传统串口设备与现代网络的关键设备,在工业控制、智能电网、交通运输等域发挥了重要作用。本文以APort100串口服务器为例,探讨串口服务器在现代物联…...

在线json格式化工具
在线json格式化工具,包括中文和英文版本,无需登录,无需费用,用完就走。 官网地址: https://json.openai2025.com 效果如下:...

OSPF的LSA的学习研究
OSPF常见1、2、3、4、5、7类LSA的研究 1、拓扑如图,按照地址表配置,激活OSPF划分相关区域并宣告相关网段 2、1类LSA,每台运行了OSPF的路由器都会产生,描述了路由器的直连接口状况和cost 可以看到R1产生了一条router lsa࿰…...
1166 Summit (25)
A summit (峰会) is a meeting of heads of state or government. Arranging the rest areas for the summit is not a simple job. The ideal arrangement of one area is to invite those heads so that everyone is a direct friend of everyone. Now given a set of tenta…...
AUTOSAR从入门到精通-【自动驾驶】高精地图(四)
目录 前言 现状概述 算法原理 高精地图的构成 A.基础地图层 B.几何地图层 C.语义地图层 D.道路连接层 E.先验地图层 F.实时地图数据 高精地图的构建及维护 移动建图系统 高精地图维护 A.地图变换检测 B.地图数据更新 3. 众包建图 3.1 众包建图的优劣势 3.2 众包起源:M…...

MySQL8数据库全攻略:版本特性、下载、安装、卸载与管理工具详解
大家好,我是袁庭新。 MySQL作为企业项目中的主流数据库,其5.x和8.x版本尤为常用。本文将详细介绍MySQL 8.x的特性、下载、安装、服务管理、卸载及管理工具,旨在帮助用户更好地掌握和使用MySQL数据库。 1.MySQL版本及下载 企业项目中使用的…...

网络安全---CMS指纹信息实战
CMS简介 CMS(Content Management System)指的是内容管理系统,如WordPress、Joomla等。CMS系统非常常见,几乎所有大型网站都使用CMS来管理其网站的内容。由于常见CMS的漏洞较多,因此黑客将不断尝试利用这些漏洞攻击CMS…...

基于C#实现对象序列化的3种方案
大家好!我是付工。 在上位机开发过程中,我们可能经常要实现一个数据对象的持久化,将对象保存到具体的文件中,今天给大家介绍常用的3种方案,大家根据实际情况,选择适合的方案。 一、准备工作 在介绍这4种…...
蓝桥杯真题 - 公因数匹配 - 题解
题目链接:https://www.lanqiao.cn/problems/3525/learning/ 个人评价:难度 2 星(满星:5) 前置知识:调和级数 整体思路 题目描述不严谨,没说在无解的情况下要输出什么(比如 n n n …...

使用 Java 实现基于 DFA 算法的敏感词检测
使用 Java 实现基于 DFA 算法的敏感词检测 1. 引言 敏感词检测在内容审核、信息过滤等领域有着广泛的应用。本文将介绍如何使用 DFA(Deterministic Finite Automaton,确定有限状态自动机) 算法,在 Java 中实现高效的敏感词检测。…...

Jenkins-Pipeline简述
一. 什么是Jenkins pipeline: pipeline在jenkins中是一套插件,主要功能在于,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂发布流程。Pipeline的实现方式是一套Groovy DSL,任何发布流程…...

Linux操作命令之云计算基础命令
一、图形化界面/文本模式 ctrlaltF2-6 图形切换到文本 ctrlalt 鼠标跳出虚拟机 ctrlaltF1 文本切换到图形 shift ctrl "" 扩大 ctrl "-" 缩小 shift ctrl "n" 新终端 shift ctrl "t" 新标签 alt 1,…...
【postgres】sqlite格式如何导入postgres数据库
step1 在ubuntu系统安装pgloader(centos系统难以直接通过yum安装,如果源码安装的话,会比较费劲) step2,执行如下python脚本 from pathlib import Path import subprocess dataset_dir Path(/app/sqlite_to_pg/chas…...

阀井可燃气体监测仪,开启地下管网安全新篇章-旭华智能
在城市的脉络中,地下管网犹如隐秘的动脉,支撑着现代生活的运转。而在这庞大网络的关键节点上,阀井扮演着不可或缺的角色。然而,由于其密闭性和复杂性,阀井内部一旦发生可燃气体泄漏,将对公共安全构成严重威…...

《offer 来了:Java 面试核心知识点精讲 -- 原理篇》
在 Java 面试的战场上,只知皮毛可不行,面试官们越来越看重对原理的理解。今天就给大家分享一本能让你在面试中脱颖而出的 “武林秘籍”——《offer 来了:Java 面试核心知识点精讲 -- 原理篇》。 本书详细介绍了Java架构师在BAT和移动互联网公…...

搭建一个基于Spring Boot的数码分享网站
搭建一个基于Spring Boot的数码分享网站可以涵盖多个功能模块,例如用户管理、数码产品分享、评论、点赞、收藏、搜索等。以下是一个简化的步骤指南,帮助你快速搭建一个基础的数码分享平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个Spring …...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...