package com.ruoyi.stock.word; import com.ruoyi.basic.pojo.Product; import com.ruoyi.basic.service.IProductService; import com.ruoyi.stock.dto.StockInRecordDto; import org.apache.poi.xwpf.usermodel.*; import org.apache.poi.xwpf.usermodel.XWPFTable.XWPFBorderType; import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * 磅单Word文档生成器 */ @Component public class WeighbridgeDocGenerator { @Value("${file.upload-dir}") private String uploadDir; @Autowired private IProductService productService; /** * 生成磅单Word文档 * @param dto 入库记录DTO * @return 文件保存的绝对路径,生成失败返回null */ public String generateWeighbridgeDoc(StockInRecordDto dto) { // 参数校验 if (dto == null) { return null; } FileOutputStream out = null; XWPFDocument document = null; try { // 创建新文档 document = new XWPFDocument(); // 构建文档内容 buildDocumentContent(document, dto); // 构建文件保存路径(绝对路径) String absolutePath = buildAbsoluteFilePath(dto); File file = new File(absolutePath); // 确保目录存在 file.getParentFile().mkdirs(); // 写入文件 out = new FileOutputStream(file); document.write(out); out.flush(); // 返回绝对路径 return absolutePath; } catch (Exception e) { e.printStackTrace(); return null; } finally { // 关闭资源 try { if (out != null) { out.close(); } if (document != null) { document.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 构建文档内容 * @param document Word文档对象 * @param dto 入库记录DTO */ private void buildDocumentContent(XWPFDocument document, StockInRecordDto dto) { // 设置纸张大小为A5横向 CTSectPr sectPr = document.getDocument().getBody().isSetSectPr() ? document.getDocument().getBody().getSectPr() : document.getDocument().getBody().addNewSectPr(); CTPageSz pageSize = sectPr.isSetPgSz() ? sectPr.getPgSz() : sectPr.addNewPgSz(); pageSize.setW(BigInteger.valueOf(11906)); // A5横向宽度 210mm pageSize.setH(BigInteger.valueOf(8392)); // A5横向高度 148mm pageSize.setOrient(STPageOrientation.LANDSCAPE); // 标题 "磅码单" XWPFParagraph titlePara = document.createParagraph(); titlePara.setAlignment(ParagraphAlignment.CENTER); titlePara.setSpacingAfter(200); XWPFRun titleRun = titlePara.createRun(); titleRun.setText("磅码单"); titleRun.setBold(true); titleRun.setFontSize(28); titleRun.setFontFamily("黑体"); // 定义统一的页面宽度值(所有表格使用相同的总宽度) int pageWidth = 9000; // 统一的页面宽度值 // 创建一行两列表格来放置日期和计量单位 XWPFTable headerTable = document.createTable(1, 2); headerTable.setWidth("100%"); // 设置表格无边框 headerTable.setInsideHBorder(XWPFBorderType.NONE, 0, 0, "000000"); headerTable.setInsideVBorder(XWPFBorderType.NONE, 0, 0, "000000"); headerTable.setTopBorder(XWPFBorderType.NONE, 0, 0, "000000"); headerTable.setBottomBorder(XWPFBorderType.NONE, 0, 0, "000000"); headerTable.setLeftBorder(XWPFBorderType.NONE, 0, 0, "000000"); headerTable.setRightBorder(XWPFBorderType.NONE, 0, 0, "000000"); // 设置表头表格列宽:日期列50%,计量单位列50% CTTbl headerTbl = headerTable.getCTTbl(); CTTblGrid headerTblGrid = headerTbl.addNewTblGrid(); // 使用统一的页面宽度 int headerColWidth = pageWidth / 2; // 4500 CTTblGridCol col1 = headerTblGrid.addNewGridCol(); col1.setW(BigInteger.valueOf(headerColWidth)); CTTblGridCol col2 = headerTblGrid.addNewGridCol(); col2.setW(BigInteger.valueOf(headerColWidth)); // 设置行高 headerTable.getRow(0).setHeight(300); // 为表头表格的每个单元格设置宽度 for (int colIndex = 0; colIndex < 2; colIndex++) { XWPFTableCell cell = headerTable.getRow(0).getCell(colIndex); CTTcPr tcPr = cell.getCTTc().addNewTcPr(); CTTblWidth cellWidth = tcPr.addNewTcW(); cellWidth.setType(STTblWidth.DXA); cellWidth.setW(BigInteger.valueOf(headerColWidth)); } // 左侧单元格:日期(左对齐) XWPFTableCell dateCell = headerTable.getRow(0).getCell(0); dateCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); // 清除单元格中可能存在的默认段落 for (int i = dateCell.getParagraphs().size() - 1; i > 0; i--) { dateCell.removeParagraph(i); } XWPFParagraph datePara = dateCell.getParagraphs().get(0); datePara.setAlignment(ParagraphAlignment.LEFT); // 清除段落中可能存在的默认Run for (int i = datePara.getRuns().size() - 1; i > 0; i--) { datePara.removeRun(i); } XWPFRun dateRun = datePara.createRun(); // 格式化日期 String weighDate = ""; if (dto.getWeighingDate() != null) { try { weighDate = dto.getWeighingDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); } catch (Exception e) { weighDate = dto.getWeighingDate().toString(); if (weighDate.length() > 10) { weighDate = weighDate.substring(0, 10); } } } dateRun.setText("日 期:" + weighDate); dateRun.setFontSize(12); dateRun.setFontFamily("宋体"); // 计量单位(右对齐) XWPFTableCell unitCell = headerTable.getRow(0).getCell(1); unitCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); for (int i = unitCell.getParagraphs().size() - 1; i > 0; i--) { unitCell.removeParagraph(i); } XWPFParagraph unitPara = unitCell.getParagraphs().get(0); unitPara.setAlignment(ParagraphAlignment.RIGHT); for (int i = unitPara.getRuns().size() - 1; i > 0; i--) { unitPara.removeRun(i); } XWPFRun unitRun = unitPara.createRun(); unitRun.setText("计量单位:(吨)"); unitRun.setFontSize(12); unitRun.setFontFamily("宋体"); // 创建主表格 - 3行6列 XWPFTable table = document.createTable(3, 6); // 设置表格宽度 table.setWidth("100%"); // 设置表格边框加粗 table.setInsideHBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); table.setInsideVBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); table.setTopBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); table.setBottomBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); table.setLeftBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); table.setRightBorder(XWPFBorderType.SINGLE, 2, 0, "000000"); // 设置行高 table.getRow(0).setHeight(400); table.getRow(1).setHeight(400); table.getRow(2).setHeight(400); // 设置主表格列宽网格 - 使用相同的总宽度 pageWidth CTTbl mainTbl = table.getCTTbl(); CTTblGrid mainTblGrid = mainTbl.addNewTblGrid(); // 列宽比例 int[] colWidths = { (int)(pageWidth * 0.20), (int)(pageWidth * 0.24), (int)(pageWidth * 0.14), (int)(pageWidth * 0.14), (int)(pageWidth * 0.14), (int)(pageWidth * 0.14) }; // 确保总和等于pageWidth int sum = 0; for (int width : colWidths) { sum += width; } // 如果有误差,调整最后一列 if (sum != pageWidth) { colWidths[5] += (pageWidth - sum); } for (int width : colWidths) { CTTblGridCol col = mainTblGrid.addNewGridCol(); col.setW(BigInteger.valueOf(width)); } // 为每一行的每个单元格设置具体的宽度 for (int rowIndex = 0; rowIndex < 3; rowIndex++) { XWPFTableRow row = table.getRow(rowIndex); for (int colIndex = 0; colIndex < 6; colIndex++) { XWPFTableCell cell = row.getCell(colIndex); CTTcPr tcPr = cell.getCTTc().addNewTcPr(); CTTblWidth cellWidth = tcPr.addNewTcW(); cellWidth.setType(STTblWidth.DXA); cellWidth.setW(BigInteger.valueOf(colWidths[colIndex])); } } // 设置表头 - 使用黑体加粗 String[] headers = {"车号", "品名", "毛重", "皮重", "净重", "备注"}; XWPFTableRow headerRow = table.getRow(0); for (int i = 0; i < headers.length; i++) { XWPFTableCell cell = headerRow.getCell(i); cell.setText(headers[i]); cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); // 清除可能存在的多余段落 while (cell.getParagraphs().size() > 1) { cell.removeParagraph(1); } XWPFParagraph cellPara = cell.getParagraphs().get(0); cellPara.setAlignment(ParagraphAlignment.CENTER); while (cellPara.getRuns().size() > 1) { cellPara.removeRun(1); } XWPFRun cellRun = cellPara.getRuns().get(0); cellRun.setBold(true); cellRun.setFontSize(12); cellRun.setFontFamily("黑体"); } // 设置数据行 - 使用宋体 XWPFTableRow dataRow = table.getRow(1); String netWeight = dto.getNetWeight() != null ? dto.getNetWeight().toString() : ""; String grossWeight = dto.getGrossWeight() != null ? dto.getGrossWeight().toString() : ""; String tareWeight = dto.getTareWeight() != null ? dto.getTareWeight().toString() : ""; String[] data = { dto.getLicensePlateNo() != null ? dto.getLicensePlateNo() : "", getProductModelName(dto.getProductId()), grossWeight + "t", tareWeight + "t", netWeight + "t", dto.getRemark() != null ? dto.getRemark() : "" }; for (int i = 0; i < data.length; i++) { XWPFTableCell cell = dataRow.getCell(i); cell.setText(data[i]); cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); while (cell.getParagraphs().size() > 1) { cell.removeParagraph(1); } XWPFParagraph cellPara = cell.getParagraphs().get(0); cellPara.setAlignment(ParagraphAlignment.CENTER); while (cellPara.getRuns().size() > 1) { cellPara.removeRun(1); } XWPFRun cellRun = cellPara.getRuns().get(0); cellRun.setFontSize(12); cellRun.setFontFamily("宋体"); } // 合计行 - 第3行 XWPFTableRow totalRow = table.getRow(2); // 第1列显示"合计(大写)" XWPFTableCell totalLabelCell = totalRow.getCell(0); totalLabelCell.setText("合计(大写)"); totalLabelCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); // 清除可能存在的多余段落 while (totalLabelCell.getParagraphs().size() > 1) { totalLabelCell.removeParagraph(1); } XWPFParagraph totalLabelPara = totalLabelCell.getParagraphs().get(0); totalLabelPara.setAlignment(ParagraphAlignment.CENTER); // 清除可能存在的多余Run while (totalLabelPara.getRuns().size() > 1) { totalLabelPara.removeRun(1); } XWPFRun totalLabelRun = totalLabelPara.getRuns().get(0); totalLabelRun.setBold(true); totalLabelRun.setFontSize(12); totalLabelRun.setFontFamily("黑体"); // 将净重转换为大写 String netWeightChinese = ChineseNumberUtil.numberToChinese( dto.getNetWeight() != null ? dto.getNetWeight() : BigDecimal.ZERO ); XWPFTableCell startCell = totalRow.getCell(1); startCell.setText(netWeightChinese + "吨"); startCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); while (startCell.getParagraphs().size() > 1) { startCell.removeParagraph(1); } XWPFParagraph startPara = startCell.getParagraphs().get(0); startPara.setAlignment(ParagraphAlignment.CENTER); while (startPara.getRuns().size() > 1) { startPara.removeRun(1); } XWPFRun startRun = startPara.getRuns().get(0); startRun.setFontSize(12); startRun.setFontFamily("宋体"); // 标记第2列为合并起始 startCell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART); // 第3-6列合并 for (int i = 2; i < 6; i++) { XWPFTableCell cell = totalRow.getCell(i); cell.setText(""); cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE); } // 创建过磅员行(单独一行,左对齐) XWPFParagraph operatorPara = document.createParagraph(); operatorPara.setSpacingAfter(100); operatorPara.setAlignment(ParagraphAlignment.LEFT); XWPFRun operatorRun = operatorPara.createRun(); String operator = dto.getWeighingOperator() != null ? dto.getWeighingOperator() : ""; operatorRun.setText("过磅员:" + operator); operatorRun.setFontSize(12); operatorRun.setFontFamily("宋体"); } /** * 构建文件保存的绝对路径 */ private String buildAbsoluteFilePath(StockInRecordDto dto) { LocalDateTime now = LocalDateTime.now(); String year = String.valueOf(now.getYear()); String month = String.format("%02d", now.getMonthValue()); String day = String.format("%02d", now.getDayOfMonth()); // 文件名格式:磅单_车牌号_年月日.docx String fileName = String.format("磅单_%s_%s.docx", dto.getLicensePlateNo() != null ? dto.getLicensePlateNo() : "未知车牌", now.format(DateTimeFormatter.ofPattern("yyyyMMdd"))); return uploadDir + File.separator + year + File.separator + month + File.separator + day + File.separator + fileName; } /** * 获取产品型号名称 */ private String getProductModelName(Long productModelId) { if (productModelId == null) { return "未知产品"; } try { Product product = productService.getById(productModelId); return product != null ? product.getProductName() : "未知产品"; } catch (Exception e) { e.printStackTrace(); return "未知产品"; } } }