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行的行号是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 枚举包括常见颜色,如 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 {
    @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;
    }
} 

获取源码

源码地址:源码地址

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言进阶习题【1】指针和数组(2)——字符数组

3. 字符数组练习 sizeof 只关注占用内存空间的大小&#xff0c;单位是字节&#xff0c;不关心内存中存放的是什么 sizeof 是操作符 strlen是求字符串长度的&#xff0c;统计的是\0之前出现的字符个数&#xff0c;一定要找到\0才算结束&#xff0c;所以可能存在越界访问的 strle…

嵌入式工程师必学(67):SWD仿真接口(for ARM)的使用方法

概述: JTAG JTAG代表联合测试行动小组(定义JTAG标准的小组),旨在作为测试板的一种方式。JTAG允许用户与微控制器的各个部分进行对话。在许多情况下,这涉及一组指令或对电路板进行编程。JTAG标准定义了5个引脚: TCK: Test Clock TMS: Test Mode Select TDI: Test Data-…

代理模式实现

一、概念&#xff1a;代理模式属于结构型设计模式。客户端不能直接访问一个对象&#xff0c;可以通过代理的第三者来间接访问该对象&#xff0c;代理对象控制着对于原对象的访问&#xff0c;并允许在客户端访问对象的前后进行一些扩展和处理&#xff1b;这种设置模式称为代理模…

华为HuaweiCloudStack(一)介绍与架构

本文简单介绍了华为HCS私有云解决方案&#xff0c;并从下至上介绍HCS的整体架构&#xff0c;部署架构、部署方式等内容。 目录 HCS简介 HCS架构 纵向结构 ?管理平台类型 HCS节点类型 FusionSphere OpenStack CPS ServiceOM SC 运营面 OC 运维面 HCS部署架构 regi…

【视觉惯性SLAM:十七、ORB-SLAM3 中的跟踪流程】

17.1 跟踪流程流程图 ORB-SLAM3 的跟踪模块是整个系统的重要组成部分&#xff0c;负责实时确定相机在三维空间中的姿态位置&#xff0c;并保持关键帧之间的连续性。其基本目标是将输入的视频流与已有地图数据进行对齐&#xff0c;完成位姿估计和地图更新。 流程图概述 一个…

【机器学习实战入门项目】MNIST数字分类机器学习项目

Python 深度学习项目&#xff1a;手写数字识别 为了使机器更加智能&#xff0c;开发者们正在深入研究机器学习和深度学习技术。人类通过不断练习和重复来学习执行某项任务&#xff0c;从而记住如何完成这些任务。然后&#xff0c;大脑中的神经元会自动触发&#xff0c;他们能够…

Python Pyside6 加Sqlite3 写一个 通用 进销存 系统 初型

图: 说明: 进销存管理系统说明文档 功能模块 1. 首页 显示关键业务数据商品总数供应商总数本月采购金额本月销售金额显示预警信息库存不足预警待付款采购单待收款销售单2. 商品管理 商品信息维护商品编码(唯一标识)商品名称规格型号单位分类进货价销售价库存数量预警…

Ubuntu安装K8S

第一步&#xff1a; 安装docker Install Docker #注意docker是早期的名称已经过时了&#xff0c;因此请使用如下命令&#xff0c;一步到位安装docker-ce。 第二步&#xff1a;设置K8S源&#xff1a; &#xff08;大陆使用aliyun源&#xff0c;大陆外使用google源&#xff09;…

Linux图形界面详解以及替换桌面程序方法

说明&#xff1a;本文章主要说明Linux图形界面的启动流程&#xff0c;以及使用自己的图形化应用替换桌面程序的方法&#xff0c;类似与安卓启动会启动Launcher&#xff0c;使用自己程序替换Launcher一样&#xff0c;实现应用独占系统&#xff0c;或者设计自己的桌面程序。 一、…

第4章 Kafka核心API——Kafka客户端操作

Kafka客户端操作 一. 客户端操作1. AdminClient API 一. 客户端操作 1. AdminClient API

使用 Java 和 FreeMarker 实现自动生成供货清单,动态生成 Word 文档,简化文档处理流程。

在上一篇博客中主要是使用SpringBootApache POI实现了BOM物料清单Excel表格导出&#xff0c;详见以下博客&#xff1a; Spring Boot Apache POI 实现 Exc&#xff08;&#xff09;el 导出&#xff1a;BOM物料清单生成器&#xff08;支持中文文件名、样式美化、数据合并&#…

python编程-OpenCV(图像读写-图像处理-图像滤波-角点检测-边缘检测)边缘检测

OpenCV中边缘检测四种常用算子&#xff1a; &#xff08;1&#xff09;Sobel算子 Sobel算子是一种基于梯度的边缘检测算法。它通过对图像进行卷积操作来计算图像的梯度&#xff0c;并将梯度的大小作为边缘的强度。它使用两个3x3的卷积核&#xff0c;分别用于计…

R语言绘图

多组火山图 数据准备&#xff1a; 将CSV文件同一在一个路径下&#xff0c;用代码合并 确保文件列名正确 library(fs) library(dplyr) library(tidyr) library(stringr) library(ggplot2) library(ggfun) library(ggrepel)# 获取文件列表 file_paths <- dir_ls(path &quo…

IDEA2023版中TODO的使用

介绍&#xff1a;TODO其实本质上还是注释&#xff0c;只不过加上了TODO这几个字符&#xff0c;可以让使用者快速找到。 注意&#xff1a;在类、接口等文件中&#xff0c;注释是使用// 即&#xff1a;// TODO 注释内容 在配置文件中&#xff0c;注释是使用# 即&#xff1a;# TO…

Maven的多模块架构设计诺依的多模块

Maven的多模块架构设计 多模块架构设计&#xff0c;本文采用 诺依的多模块架构设计分析

【JsonViewer】Json格式化

使用 Notepad 对 Json 数据进行格式化处理&#xff0c;使数据在结构上更清晰 1.在线安装 安装之后&#xff0c;重启应用&#xff0c;在插件菜单栏即可看到 JsonViewer 选项&#xff0c;在 Notepad 中放入 Json 数据&#xff0c;点击 Format Json 进行数据格式化 2.离线安装 …

【漏洞预警】FortiOS 和 FortiProxy 身份认证绕过漏洞(CVE-2024-55591)

文章目录 一、产品简介二、漏洞描述三、影响版本四、漏洞检测方法五、解决方案 一、产品简介 FortiOS是Fortinet公司核心的网络安全操作系统&#xff0c;广泛应用于FortiGate下一代防火墙&#xff0c;为用户提供防火墙、VPN、入侵防御、应用控制等多种安全功能。 FortiProxy则…

企业分类相似度筛选实战:基于规则与向量方法的对比分析

文章目录 企业表相似类别筛选实战项目背景介绍效果展示基于规则的效果基于向量相似的效果 说明相关文章推荐 企业表相似类别筛选实战 项目背景 在当下RAG&#xff08;检索增强生成&#xff09;技术应用不断发展的背景下&#xff0c;掌握文本相似算法不仅能够助力信息检索&…

Ubuntu打开文件夹不显示文件

1.情况介绍 使用ubuntu打开文件夹不显示文件夹里面的内容&#xff0c;而是直接打开了资源查看器。 2.解决办法 命令行安装nautilus sudo apt-get install nautilus

Go-知识 版本演进

Go-知识 版本演进 Go release notesr56(2011/03/16)r57(2011/05/03)Gofix 工具语言包工具小修订 r58(2011/06/29)语言包工具小修订 r59(2011/08/01)语言包工具 r60(2011/09/07)语言包工具 [go1 2012-03-28](https://golang.google.cn/doc/devel/release#go1)[go1.1 2013-05-13]…