[add] 物资采购联系单详情图片
This commit is contained in:
Binary file not shown.
@ -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<String> getPic(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok("操作成功", busPurchaseDocService.queryPicBase64ById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增物资-采购联系单
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -76,10 +76,25 @@ public interface IBusPurchaseDocService extends IService<BusPurchaseDoc> {
|
||||
*/
|
||||
void create(Long id, HashMap<Long, BigDecimal> 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);
|
||||
}
|
||||
|
@ -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<BusPurchaseDocMapper,
|
||||
|
||||
private final IBusMrpBaseService mrpBaseService;
|
||||
|
||||
private final IBusProjectService projectService;
|
||||
|
||||
private final WordToPdfToImg wordToPdfToImg;
|
||||
|
||||
/**
|
||||
* 查询物资-采购联系单
|
||||
@ -260,6 +266,62 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl<BusPurchaseDocMapper,
|
||||
planDocAssociationService.saveBatch(busPlanDocAssociations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Word
|
||||
*
|
||||
* @param purchaseDoc 采购联系单对象
|
||||
*/
|
||||
@Override
|
||||
public void createWord(BusPurchaseDoc purchaseDoc) {
|
||||
Path targetDir = Paths.get(constant.getBusPurchaseDocFileUrl(purchaseDoc));
|
||||
// 如果存在目录则直接返回,不存在则生成文件并返回
|
||||
if (!Files.exists(targetDir)) {
|
||||
// 清理旧文件
|
||||
String baseUrl = constant.PURCHASE_DOC_FILE_URL + purchaseDoc.getId();
|
||||
try {
|
||||
Path dirPath = Paths.get(baseUrl);
|
||||
if (Files.exists(dirPath)) {
|
||||
FileUtils.deleteDirectory(dirPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("文件目录:{},清理失败", baseUrl, e);
|
||||
}
|
||||
// 准备数据
|
||||
List<BusMaterialbatchdemandplan> items = new ArrayList<>();
|
||||
List<BusPlanDocAssociation> planDocAssociationList = planDocAssociationService.lambdaQuery()
|
||||
.eq(BusPlanDocAssociation::getDocId, purchaseDoc.getId())
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(planDocAssociationList)) {
|
||||
List<Long> 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<BusPurchaseDocMapper,
|
||||
if (purchaseDoc == null) {
|
||||
throw new ServiceException("物料领料单不存在");
|
||||
}
|
||||
// 准备数据
|
||||
List<BusMaterialbatchdemandplan> items = new ArrayList<>();
|
||||
List<BusPlanDocAssociation> planDocAssociationList = planDocAssociationService.lambdaQuery()
|
||||
.eq(BusPlanDocAssociation::getDocId, id)
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(planDocAssociationList)) {
|
||||
List<Long> 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<BusPurchaseDocMapper,
|
||||
private BusPurchaseDocWordDto getReplacementDto(BusPurchaseDoc purchaseDoc, List<BusMaterialbatchdemandplan> 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<BusMaterialbatchdemandplanWordDto> dtoItems = new ArrayList<>();
|
||||
|
@ -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<BufferedImage> 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<BufferedImage> 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
|
||||
|
Reference in New Issue
Block a user