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 枚举包括常见颜色,如 YELLOWREDBLUE 等。

setFillPattern(FillPatternType.SOLID_FOREGROUND):设置单元格的填充模式为 SOLID_FOREGROUND,表示填充整个单元格背景色。FillPatternType 还可以选择其他模式,如 NO_FILL(不填充)SOLID_FOREGROUND(完全填充)等。

颜色解释:

LIGHT_GREENIndexedColors 中的一个颜色常量,代表一种浅绿色。可以将单元格的背景色设置为浅绿色,增加表格的视觉效果,使其更具可读性和美观。

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() 获取了 IndexedColorsRED(红色)的颜色索引。除了 REDIndexedColors 还包含多种颜色,如 BLACKBLUEGREEN 等。

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 列。
  • 这两个单元格分别应用不同的样式(labelStylevalueStyle),从而使标签和值的显示效果有所区别。标签单元格可能使用不同的字体、颜色、背景等样式,而值单元格则可能有不同的格式。

使用示例:

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 是列索引,因此 i09(共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. 数字格式:将单元格内容设置为数字格式,使用千位分隔符并保留两位小数(例如:1,234,567.89)。
  2. 日期格式:将单元格内容设置为日期格式,显示为年-月-日(例如: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;}
} 

获取源码

源码地址:源码地址

写到这里也就结束了,如果你觉得此文章对你有所帮助的话就一键三连,在下谢谢您嘞!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/893093.shtml

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

相关文章

[每周一更]-(第132期):AI工具集对比

文章目录 1.问答互动类&#xff08;31个&#xff09;2.图像类**简要对比说明**&#xff1a; **总结**&#xff1a; 3.代码类WindsurfCursor AIGithub Copilot 4.大模型**AlphaFold 的独特性与优势****AlphaFold 的局限性****主要大模型对比** AI的核心目标是通过模拟人类智能来…

PortSwigger靶场练习---第二关-查找和利用未使用的 API 端点

第二关&#xff1a;Finding and exploiting an unused API endpoint 实验&#xff1a;查找和利用未使用的 API 端点 PortSwigger靶场地址&#xff1a; Dashboard | Web Security Academy - PortSwigger 题目&#xff1a; 官方提示&#xff1a; 在 Burp 的浏览器中&#xff0c…

python_在钉钉群@人员发送消息

python_在钉钉群人员发送消息 1、第一种 企业内部机器人群聊实现人接入指南&#xff0c;适用于群机器人接收消息&#xff0c;处理完一系列的动作之后&#xff0c;将消息返回给发消息的人员&#xff0c;同时该人员。 需要在企微后台新建一个自建应用&#xff0c;在自建应用里…

天机学堂5-XxlJobRedis

文章目录 梳理前面的实现&#xff1a;Feign点赞改进 day07-积分系统bitmap相关命令签到增加签到记录计算本月已连续签到的天数查询签到记录 积分表设计签到-->发送RabbitMQ消息&#xff0c;保存积分对应的消费者&#xff1a;**消费消息 用于保存积分**增加积分查询个人今日积…

【Block总结】Conv2Former的Block,结合卷积网络和Transformer的优点|即插即用

背景与动机 Conv2Former是一种新型的卷积神经网络&#xff08;ConvNet&#xff09;&#xff0c;旨在结合卷积网络和Transformer的优点&#xff0c;以提升视觉识别任务的性能。传统的卷积神经网络在处理局部特征提取方面表现优异&#xff0c;但在建模全局信息和长距离依赖关系时…

智能化植物病害检测:使用深度学习与图像识别技术的应用

植物病害一直是农业生产中亟待解决的问题&#xff0c;它不仅会影响作物的产量和质量&#xff0c;还可能威胁到生态环境的稳定。随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;尤其是深度学习和图像识别技术的应用&#xff0c;智能化植物病害检测已经成为一…

【TCP】rfc文档

tcp协议相关rfc有哪些 TCP&#xff08;传输控制协议&#xff09;是一个复杂的协议&#xff0c;其设计和实现涉及多个RFC文档。以下是一些与TCP协议密切相关的RFC文档列表&#xff0c;按照时间顺序排列&#xff0c;涵盖了从基础定义到高级特性和优化的各个方面&#xff1a; 基…

机器学习皮马印第安人糖尿病数据集预测报告

目录 1.项目选题与设计方案 1.1项目选题 1.2设计方案 2.功能实现 2.1 主要功能介绍 2.2 开发环境及平台介绍 2.3 实现过程 2.3.1数据分析 2.3.2算法设计 2.3.3 python代码 3.个人心得体会 1.项目选题与设计方案 1.1项目选题 我国的糖尿病患者初诊时约&#xff18;&a…

HTML5+Canvas实现的鼠标跟随自定义发光线条源码

源码介绍 HTML5Canvas实现的鼠标跟随自定义发光线条特效源码非常炫酷&#xff0c;在黑色的背景中&#xff0c;鼠标滑过即产生彩色变换的发光线条效果&#xff0c;且线条周围散发出火花飞射四溅的粒子光点特效。 效果预览 源码如下 <!DOCTYPE html PUBLIC "-//W3C//D…

Dart语言

基础篇&#xff1a; 第 1 部分&#xff1a;Dart 的基础语法 1.1 Dart 程序的结构 一个简单的 Dart 程序通常包含一个 main() 函数&#xff0c;这是程序的入口点。所有 Dart 程序都会从 main() 函数开始执行。 void main() {print(Hello, Dart!); } void 表示 main() 函数没…

通过idea创建的springmvc工程需要的配置

在创建的spring mvc工程中&#xff0c;使用idea开发之前需要配置文件包括porm.xml、web.xml、springmvc.xml 1、porm.xml 工程以来的spring库&#xff0c;主要包括spring-aop、spring-web、spring-webmvc&#xff0c;示例配置如下&#xff1a; <project xmlns"http:/…

Ansible实战:如何正确选择 command 和shell模块?

在使用Ansible进行自动化运维时&#xff0c;command 和 shell 模块是我们执行命令的好帮手。虽然它们看起来很相似&#xff0c;但在功能特性和适用场景上其实有着明显的不同。正确选择合适的模块不仅能够提高任务的效率&#xff0c;还能帮助我们规避一些潜在的风险。在这篇文章…

【STM32-学习笔记-4-】PWM、输入捕获(PWMI)

文章目录 1、PWMPWM配置 2、输入捕获配置3、编码器 1、PWM PWM配置 配置时基单元配置输出比较单元配置输出PWM波的端口 #include "stm32f10x.h" // Device headervoid PWM_Init(void) { //**配置输出PWM波的端口**********************************…

学习微信小程序的下拉列表控件-picker

1、创建一个空白工程 2、index.wxml中写上picker布局&#xff1a; <!--index.wxml--> <view class"container"><picker mode"selector" range"{{array}}" bindchange"bindPickerChange"><view class"pick…

【神经网络基础】

目录 一、神经网络的构成 1.1什么是神经网络&#xff1f; 1.2 激活函数 1.2.1 Sigmoid 1.2.2 Tanh 1.2.3 ReLU 1.2.4 softmax 1.2.5 其他激活函数 1.2.6 选择激活函数 1.3 参数初始化 1.4 模型构建 二、损失函数 2.1 分类问题 2.1.1多分类&#xff08;多分类交叉…

STM32 学习笔记【补充】(十)硬件I2C读写MPU6050

该系列为笔者在学习STM32过程&#xff08;主线是江科大的视频&#xff09;中的记录与发散思考。 初学难免有所纰漏、错误&#xff0c;还望大家不吝指正&#xff0c;感谢~ 一、I2C 外设简介 I2C&#xff08;Inter-Integrated Circuit&#xff09;是一种多主多从的串行通信协议…

.Net Core webapi 实现JWT认证

文章目录 需求准备创建JWT配置创建JWTService注册JWT创建中间件读取jwt的token在需要的接口上添加属性启动认证启动swagger的授权认证使用 需求 实现一个记录某个用户所有操作的功能 准备 创建你的webapi项目从nuget下载安装JWT资源包根据你的项目使用.net版本下载对应的jwt…

leetcode203-移除链表元素

leetcode203 什么是链表 之前不懂链表的数据结构&#xff0c;一看到链表的题目就看不明白 链表是通过next指针来将每个节点连接起来的&#xff0c;题目中给的链表是单向链表&#xff0c;有两个值&#xff0c;一个val表示值&#xff0c;一个next&#xff1a;表示连接的下一个…

在21世纪的我用C语言探寻世界本质——字符函数和字符串函数(2)

人无完人&#xff0c;持之以恒&#xff0c;方能见真我&#xff01;&#xff01;&#xff01; 共同进步&#xff01;&#xff01; 文章目录 一、strncpy函数的使用二、strncat函数的使用三、strncmp函数的使用四、strstr的使用和模拟实现五、strtok函数的使用六、strerror和pe…

stack_queue的底层,模拟实现,deque和priority_queue详解

文章目录 适配器Stack的模拟实现Queue的模拟实现vector和list的对比dequedeque的框架deque的底层 priority_queuepriority_queue的使用priority_queue的底层仿函数的使用仿函数的作用priority_queue模拟实现 适配器 适配器是一种模式&#xff0c;这种模式将类的接口转化为用户希…