diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/template/物资采购联系单模版.docx b/xinnengyuan/ruoyi-admin/src/main/resources/template/物资采购联系单模版.docx index 892087ac..f0785a2a 100644 Binary files a/xinnengyuan/ruoyi-admin/src/main/resources/template/物资采购联系单模版.docx and b/xinnengyuan/ruoyi-admin/src/main/resources/template/物资采购联系单模版.docx differ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/BusPurchaseDocController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/BusPurchaseDocController.java index 929c0080..7baa4ae5 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/BusPurchaseDocController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/BusPurchaseDocController.java @@ -91,6 +91,18 @@ public class BusPurchaseDocController extends BaseController { return R.ok(busPurchaseDocService.queryById(id)); } + /** + * 获取物资-采购联系单详细信息图片 + * + * @param id 主键 + */ + @SaCheckPermission("cailiaoshebei:purchaseDoc:querPic") + @GetMapping("/pic/{id}") + public R getPic(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok("操作成功", busPurchaseDocService.queryPicBase64ById(id)); + } + /** * 新增物资-采购联系单 */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/constant.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/constant.java index 5964bab9..224fcc33 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/constant.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/controller/constant.java @@ -1,5 +1,10 @@ package org.dromara.cailiaoshebei.controller; +import org.dromara.cailiaoshebei.domain.BusPurchaseDoc; +import org.dromara.common.core.utils.DateUtils; + +import java.text.SimpleDateFormat; + /** * @Author 铁憨憨 * @Date 2025/8/8 11:15 @@ -10,5 +15,22 @@ public class constant { public static final String MaterialsPlans = "materialsPlans"; //计划 public static final String BatchRequirements = "batchRequirements"; //需求 public static final String EquipmentOrdering = "equipmentOrdering"; //订货 + public static final String PURCHASE_DOC_FILE_URL = "docs/purchase/doc/"; // 采购联系单文件路径 public static final String PURCHASE_DOC_TEMPLATE_PATH = "template/物资采购联系单模版.docx"; // 采购联系单文件路径 + + /** + * 获取物资采购联系单文件名 + */ + public static String getBusPurchaseDocFileUrl(BusPurchaseDoc purchaseDoc) { + String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(purchaseDoc.getUpdateTime()); + return String.format("%s%s/%s", PURCHASE_DOC_FILE_URL, purchaseDoc.getId(), timestamp); + } + + /** + * 获取物资采购联系单文件名 + */ + public static String getBusPurchaseDocFileName(BusPurchaseDoc purchaseDoc) { + String createDate = DateUtils.formatDate(purchaseDoc.getCreateTime()); + return String.format("物资采购联系单(%s).docx", createDate); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/IBusPurchaseDocService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/IBusPurchaseDocService.java index 1a03bb74..6ff7307c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/IBusPurchaseDocService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/IBusPurchaseDocService.java @@ -76,10 +76,25 @@ public interface IBusPurchaseDocService extends IService { */ void create(Long id, HashMap map); + /** + * 创建Word + * + * @param purchaseDoc 采购联系单对象 + */ + void createWord(BusPurchaseDoc purchaseDoc); + /** * 根据主键导出Word * * @param id 主键id */ void exportWordById(Long id, HttpServletResponse response); + + /** + * 根据主键查询详情图片base64 + * + * @param id 主键id + * @return 详情图片base64 + */ + String queryPicBase64ById(Long id); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/impl/BusPurchaseDocServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/impl/BusPurchaseDocServiceImpl.java index 3fa9fa6d..f5cc1834 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/impl/BusPurchaseDocServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/cailiaoshebei/service/impl/BusPurchaseDocServiceImpl.java @@ -10,10 +10,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; -import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.docx4j.openpackaging.exceptions.Docx4JException; import org.dromara.cailiaoshebei.controller.constant; import org.dromara.cailiaoshebei.domain.BusMaterialbatchdemandplan; import org.dromara.cailiaoshebei.domain.BusPlanDocAssociation; @@ -36,21 +36,24 @@ import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.oss.exception.OssException; +import org.dromara.common.utils.documentOperations.WordToPdfToImg; +import org.dromara.project.domain.BusProject; +import org.dromara.project.service.IBusProjectService; import org.springframework.beans.BeanUtils; import org.springframework.context.event.EventListener; -import org.springframework.http.MediaType; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.math.BigDecimal; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -75,6 +78,9 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl items = new ArrayList<>(); + List planDocAssociationList = planDocAssociationService.lambdaQuery() + .eq(BusPlanDocAssociation::getDocId, purchaseDoc.getId()) + .list(); + if (CollUtil.isNotEmpty(planDocAssociationList)) { + List planIds = planDocAssociationList.stream().map(BusPlanDocAssociation::getPlanId).toList(); + items = materialbatchdemandplanService.listByIds(planIds); + } + BusPurchaseDocWordDto data = this.getReplacementDto(purchaseDoc, items); + // 生成文件 + try (InputStream is = getClass().getClassLoader().getResourceAsStream(constant.PURCHASE_DOC_TEMPLATE_PATH)) { + if (is == null) { + throw new ServiceException("模板文件不存在"); + } + LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy(); + Configure config = Configure.builder().bind("items", hackLoopTableRenderPolicy).build(); + XWPFTemplate template = XWPFTemplate.compile(is, config); + template.render(data); + // 创建目标目录 + if (!Files.exists(targetDir)) { + Files.createDirectories(targetDir); + } + // 组合目标文件名 + String fileName = constant.getBusPurchaseDocFileName(purchaseDoc); + // 保存修改后的文件 + try (FileOutputStream fos = new FileOutputStream(targetDir.resolve(fileName).toFile())) { + template.write(fos); + } + template.close(); + } catch (IOException e) { + throw new OssException("生成Word文件失败,错误信息: " + e.getMessage()); + } + } + } + /** * 根据主键导出Word * @@ -271,38 +333,33 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl items = new ArrayList<>(); - List planDocAssociationList = planDocAssociationService.lambdaQuery() - .eq(BusPlanDocAssociation::getDocId, id) - .list(); - if (CollUtil.isNotEmpty(planDocAssociationList)) { - List planIds = planDocAssociationList.stream().map(BusPlanDocAssociation::getPlanId).toList(); - items = materialbatchdemandplanService.listByIds(planIds); - } - BusPurchaseDocWordDto data = this.getReplacementDto(purchaseDoc, items); - // 生成文件 - try (InputStream is = getClass().getClassLoader().getResourceAsStream(constant.PURCHASE_DOC_TEMPLATE_PATH)) { - if (is == null) { - throw new ServiceException("模板文件不存在"); - } - LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy(); - Configure config = Configure.builder().bind("items", hackLoopTableRenderPolicy).build(); - XWPFTemplate template = XWPFTemplate.compile(is, config); - template.render(data); + // 创建Word + this.createWord(purchaseDoc); + } - // 设置响应头,通知浏览器下载 Word 文件 - String fileName = URLEncoder.encode("物料领料单_" + id + ".docx", StandardCharsets.UTF_8); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); - response.setHeader("Content-Disposition", "attachment; filename=" + fileName); - try (ServletOutputStream out = response.getOutputStream()) { - template.write(out); // 将文件写入响应流 - out.flush(); - } - template.close(); - } catch (IOException e) { - throw new OssException("生成Word文件失败,错误信息: " + e.getMessage()); + /** + * 根据主键查询详情图片base64 + * + * @param id 主键id + * @return 详情图片base64 + */ + @Override + public String queryPicBase64ById(Long id) { + BusPurchaseDoc purchaseDoc = this.getById(id); + if (purchaseDoc == null) { + throw new ServiceException("物料领料单不存在"); } + this.createWord(purchaseDoc); + String filePath = constant.getBusPurchaseDocFileUrl(purchaseDoc) + "/"; + String fileName = constant.getBusPurchaseDocFileName(purchaseDoc); + File file = new File(filePath + fileName); + String base64; + try (FileInputStream inputStream = new FileInputStream(file)) { + base64 = wordToPdfToImg.wordToImgBase64(inputStream); + } catch (IOException | Docx4JException e) { + throw new ServiceException("获取物资采购联系单详情失败,错误信息: " + e.getMessage()); + } + return "data:image/png;base64," + base64; } /** @@ -315,11 +372,14 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl items) { BusPurchaseDocWordDto dto = new BusPurchaseDocWordDto(); BeanUtils.copyProperties(purchaseDoc, dto); + // 获取项目名称 + BusProject project = projectService.getById(purchaseDoc.getProjectId()); + dto.setProjectName(project != null ? project.getProjectName() : ""); // 日期转换 LocalDate arrivalDate = purchaseDoc.getArrivalDate(); - dto.setArrivalDate(arrivalDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日"))); + dto.setArrivalDate(arrivalDate != null ? arrivalDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")) : ""); LocalDate signingDate = purchaseDoc.getSigningDate(); - dto.setSigningDate(signingDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日"))); + dto.setSigningDate(signingDate != null ? signingDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")) : ""); // 明细项信息 if (CollUtil.isNotEmpty(items)) { List dtoItems = new ArrayList<>(); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/documentOperations/WordToPdfToImg.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/documentOperations/WordToPdfToImg.java index a0891668..e01e27db 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/documentOperations/WordToPdfToImg.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/documentOperations/WordToPdfToImg.java @@ -15,11 +15,9 @@ import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import static org.dromara.common.constant.MinioPathConstant.ContactNotice; @@ -33,16 +31,18 @@ public class WordToPdfToImg { /** * wordToImg 根据file生成缩略图(路径) * - * @param file 文件 + * @param file 文件 + * @param filePath 文件路径 + * @param fileName 文件名 */ - public String wordToImg(MultipartFile file) throws Docx4JException, IOException { - String mergedPath = "output/merged.png"; + public String wordToImg(InputStream file, String filePath, String fileName) throws Docx4JException, IOException { + String mergedPath = filePath + "/" + fileName + ".png"; // ✅ 1. Word → PDF(内存) ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); // 直接走路径 // WordprocessingMLPackage wordML = WordprocessingMLPackage.load(new File(docxPath)); // 直接走文件,word不落地 - WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file.getInputStream()); + WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file); Docx4J.toPDF(wordML, pdfOut); // ✅ 2. PDF → 各页 PNG List pages = new ArrayList<>(); @@ -73,6 +73,62 @@ public class WordToPdfToImg { return mergedPath; } + /** + * wordToImg 根据 InputStream 生成缩略图(Base64) + * + * @param file word 文件流 + * @return base64 图片字符串(png 格式) + */ + public String wordToImgBase64(InputStream file) throws Docx4JException, IOException { + // ✅ 1. Word → PDF(内存) + ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); + WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file); + Docx4J.toPDF(wordML, pdfOut); + + // ✅ 2. PDF → 各页 PNG + List pages = new ArrayList<>(); + try (PDDocument pdf = PDDocument.load(new ByteArrayInputStream(pdfOut.toByteArray()))) { + PDFRenderer renderer = new PDFRenderer(pdf); + for (int i = 0; i < pdf.getNumberOfPages(); i++) { + BufferedImage img = renderer.renderImageWithDPI(i, 144); // 144 DPI,清晰度可调 + pages.add(img); + } + } + + // ✅ 3. 合成所有页为一张长图 + int totalHeight = pages.stream().mapToInt(BufferedImage::getHeight).sum(); + int maxWidth = pages.stream().mapToInt(BufferedImage::getWidth).max().orElse(0); + BufferedImage merged = new BufferedImage(maxWidth, totalHeight, BufferedImage.TYPE_INT_RGB); + + Graphics2D g = merged.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, maxWidth, totalHeight); + + int y = 0; + for (BufferedImage img : pages) { + g.drawImage(img, 0, y, null); + y += img.getHeight(); + } + g.dispose(); + + // ✅ 4. 转换为 Base64 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(merged, "png", out); + byte[] bytes = out.toByteArray(); + + return Base64.getEncoder().encodeToString(bytes); + } + + /** + * wordToImg 根据file生成缩略图(路径) + * + * @param file 文件 + * @param filePath 文件路径 + * @param fileName 文件名 + */ + public String wordToImg(MultipartFile file, String filePath, String fileName) throws Docx4JException, IOException { + return this.wordToImg(file.getInputStream(), filePath, fileName); + } /** * convertWordToImage 根据MultipartFile转成缩略图 并上传至minio