diff --git a/nflg-mobilebroken-quotation/pom.xml b/nflg-mobilebroken-quotation/pom.xml
index bbaebef2..ab6fe668 100644
--- a/nflg-mobilebroken-quotation/pom.xml
+++ b/nflg-mobilebroken-quotation/pom.xml
@@ -49,38 +49,6 @@
org.springframework.boot
spring-boot-starter-validation
-
- org.springframework.boot
- spring-boot-starter-thymeleaf
-
-
- ognl
- ognl
- 3.1.28
-
-
- org.xhtmlrenderer
- flying-saucer-pdf
- 9.3.1
-
-
- bcprov-jdk14
- org.bouncycastle
-
-
-
-
-
- com.itextpdf
- itextpdf
- 5.5.13.3
-
-
-
- com.itextpdf
- itext-asian
- 5.2.0
-
cn.dev33
sa-token-spring-boot-starter
@@ -120,6 +88,11 @@
2.27.3
test
+
+ com.github.librepdf
+ openpdf
+ 1.4.2
+
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/QuotationApplication.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/QuotationApplication.java
index 765ce1a2..23f7fa58 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/QuotationApplication.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/QuotationApplication.java
@@ -1,6 +1,13 @@
package com.nflg.mobilebroken.quotation;
import cn.dev33.satoken.SaManager;
+import cn.hutool.core.util.RandomUtil;
+import com.lowagie.text.*;
+import com.lowagie.text.Font;
+import com.lowagie.text.Image;
+import com.lowagie.text.Rectangle;
+import com.lowagie.text.pdf.*;
+import com.nflg.mobilebroken.quotation.pdf.HeaderFooterEvent;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
@@ -9,6 +16,16 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
+import java.awt.*;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+
@SpringBootApplication
@MapperScan("com.nflg.mobilebroken.repository.mapper")
@ComponentScan(basePackages = {"com.nflg.mobilebroken.repository.service", "com.nflg.mobilebroken.quotation"
@@ -18,9 +35,277 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
public class QuotationApplication {
- public static void main(String[] args) {
- SpringApplication.run(QuotationApplication.class, args);
- log.info("启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
+// public static void main(String[] args) {
+// SpringApplication.run(QuotationApplication.class, args);
+// log.info("启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
+// }
+
+ public static void main(String[] args) throws Exception {
+ Document document = new Document(PageSize.A4);
+ PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("test.pdf"));
+ writer.setPageEvent(new HeaderFooterEvent("images/head.png"));
+ document.open();
+ Rectangle pageSize = document.getPageSize();
+ BaseFont bfChinese = getChineseFont();
+ Font normalFont = new Font(bfChinese, 12);
+ //第一页
+ PdfPTable footerTable = new PdfPTable(1);
+ footerTable.setTotalWidth(pageSize.getWidth() - document.leftMargin() - document.rightMargin());
+ footerTable.setLockedWidth(true);
+
+ Chunk chunk1 = new Chunk("NFLG Crusher and Screen Quotation", new Font(bfChinese, 28, Font.BOLD, new Color(0x3F, 0x3F, 0x3F)));
+ chunk1.setUnderline(new Color(0xC0, 0x00, 0x00), 5f, 0f, -15f, 0f, PdfContentByte.LINE_CAP_BUTT);
+ PdfPCell cell1 = new PdfPCell(new Phrase(chunk1));
+ cell1.setHorizontalAlignment(Element.ALIGN_RIGHT);
+ cell1.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell1.setMinimumHeight(50);
+ cell1.setPaddingTop(70);
+ cell1.setBorder(Rectangle.NO_BORDER);
+ footerTable.addCell(cell1);
+
+ PdfPCell cell2 = new PdfPCell(new Phrase("Quotation Date:2026.3.20", new Font(bfChinese, 15, Font.BOLD)));
+ cell2.setHorizontalAlignment(Element.ALIGN_RIGHT);
+ cell2.setPaddingTop(40);
+ cell2.setBorder(Rectangle.NO_BORDER);
+ footerTable.addCell(cell2);
+
+ Phrase phrase = new Phrase();
+ Chunk chunk2 = new Chunk("Validity:", new Font(bfChinese, 15, Font.BOLD));
+ phrase.add(chunk2);
+ Chunk chunk3 = new Chunk("30 days from the date of quotation", new Font(bfChinese, 15, Font.BOLD, new Color(0xC0, 0x00, 0x00)));
+ phrase.add(chunk3);
+ PdfPCell cell3 = new PdfPCell(phrase);
+ cell3.setHorizontalAlignment(Element.ALIGN_RIGHT);
+ cell3.setPaddingTop(3);
+ cell3.setBorder(Rectangle.NO_BORDER);
+ footerTable.addCell(cell3);
+
+ PdfPTable outerTable = new PdfPTable(4);
+ outerTable.setWidthPercentage(100);
+ outerTable.setWidths(new float[]{1f, 1f, 1f, 1f});
+ String[] imagePaths = {"https://cabinet-tool.oss-cn-hangzhou.aliyuncs.com/admin/20260519/1/NNEZ/40232e65-ef66-4ca3-85e2-031c311c751b.png"
+ , "https://cabinet-tool.oss-cn-hangzhou.aliyuncs.com/admin/20260519/1/qxmh/490a3895-1bdb-41b1-b3c0-face91de4555.png"
+ , "https://cabinet-tool.oss-cn-hangzhou.aliyuncs.com/admin/20260519/1/TMlE/c932d08d-454f-4c5f-9d8b-cd028d61867e.png"
+ , "https://cabinet-tool.oss-cn-hangzhou.aliyuncs.com/admin/20260519/1/wR5X/23253d87-2403-4a6d-87ef-be75427c3caa.png"};
+ String[] texts = {"Aggregate", "Construction Waste", "Heavy-duty screen", "Mine / Quarry"};
+ for (int i = 0; i < 4; i++) {
+ PdfPCell cell = new PdfPCell();
+ cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ cell.setVerticalAlignment(Element.ALIGN_TOP);
+ if (i != 0) {
+ cell.setPaddingLeft(5);
+ }
+ cell.setBorder(Rectangle.NO_BORDER);
+ cell.setBorderColor(Color.WHITE);
+ cell.setBorderWidth(5f);
+ Image image = Image.getInstance(downloadImage(imagePaths[i]));
+ image.scaleToFit(125f, 1000f);
+ image.setAlignment(Element.ALIGN_CENTER);
+ Paragraph caption = new Paragraph(texts[i], new Font(bfChinese, 12, Font.BOLD));
+ caption.setAlignment(Element.ALIGN_CENTER);
+ caption.setSpacingBefore(4f);
+ cell.addElement(image);
+ cell.addElement(caption);
+ outerTable.addCell(cell);
+ }
+ PdfPCell cell4 = new PdfPCell(outerTable);
+ cell4.setPaddingTop(250);
+ cell4.setBorder(Rectangle.NO_BORDER);
+ footerTable.addCell(cell4);
+
+ PdfPCell cell5 = new PdfPCell(new Phrase(new Chunk("ONE MORE OPTION FOR THE WORLD", new Font(bfChinese, 20, Font.BOLD, new Color(0xC0, 0x00, 0x00)))));
+ cell5.setPaddingTop(80);
+ cell5.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell5.setHorizontalAlignment(Element.ALIGN_CENTER);
+ cell5.setBorder(Rectangle.NO_BORDER);
+ footerTable.addCell(cell5);
+
+// footerTable.writeSelectedRows(0, -1, document.leftMargin(), pageSize.getHeight() - document.bottomMargin() - 50, writer.getDirectContent());
+ document.add(footerTable);
+ document.add(Chunk.NEXTPAGE);
+ //开始正文
+ Font labelFont = new Font(bfChinese, 12, Font.BOLD);
+ Font valueFont = new Font(bfChinese, 12, Font.NORMAL);
+ PdfPTable table = new PdfPTable(4);
+ table.setWidthPercentage(100);
+ table.setWidths(new float[]{15f, 35f, 15f, 35f});
+ PdfPCell label1_1 = createLabelCell("客户:", labelFont);
+ table.addCell(label1_1);
+ PdfPCell value1_1 = createValueCell("KIMI", valueFont);
+ table.addCell(value1_1);
+ PdfPCell label1_2 = createLabelCell("供应商:", labelFont);
+ table.addCell(label1_2);
+ PdfPCell value1_2 = createValueCell("福建南方路面机械股份有限公司", valueFont);
+ table.addCell(value1_2);
+ PdfPCell label2_1 = createLabelCell("联系人:", labelFont);
+ table.addCell(label2_1);
+ PdfPCell value2_1 = createValueCell("IGOR", valueFont);
+ table.addCell(value2_1);
+ PdfPCell label2_2 = createLabelCell("联系人:", labelFont);
+ table.addCell(label2_2);
+ PdfPCell value2_2 = createValueCell("陈保朝", valueFont);
+ table.addCell(value2_2);
+ PdfPCell label3_1 = createLabelCell("联系方式:", labelFont);
+ table.addCell(label3_1);
+ PdfPCell value3_1 = createValueCell("555-12345678", valueFont);
+ table.addCell(value3_1);
+ PdfPCell label3_2 = createLabelCell("联系方式:", labelFont);
+ table.addCell(label3_2);
+ PdfPCell value3_2 = createValueCell("15188888888", valueFont);
+ table.addCell(value3_2);
+ PdfPCell label4_1 = createLabelCell("邮箱:", labelFont);
+ table.addCell(label4_1);
+ PdfPCell value4_1 = createValueCell("igor@kmi.com.ru", valueFont);
+ table.addCell(value4_1);
+ PdfPCell label4_2 = createLabelCell("邮箱:", labelFont);
+ table.addCell(label4_2);
+ PdfPCell value4_2 = createValueCell("baochao.chen@nflg.com", valueFont);
+ table.addCell(value4_2);
+ PdfPCell label5_1 = createLabelCell("国家/地区:", labelFont);
+ table.addCell(label5_1);
+ PdfPCell value5_1 = createValueCell("俄罗斯", valueFont);
+ table.addCell(value5_1);
+ PdfPCell label5_2 = createLabelCell("报价单号:", labelFont);
+ table.addCell(label5_2);
+ PdfPCell value5_2 = createValueCell("CHNUI2026202512654", valueFont);
+ table.addCell(value5_2);
+ document.add(table);
+ Paragraph spacer = new Paragraph();
+ spacer.setSpacingBefore(40f);
+ document.add(spacer);
+ //报价清单
+ PdfPTable tableBJQD = new PdfPTable(5);
+ tableBJQD.setWidthPercentage(100);
+ tableBJQD.setWidths(new float[]{10f, 40f, 20f, 15f, 15f});
+ PdfPCell headBJQD = createBJQDCell("报价清单", 5, normalFont);
+ tableBJQD.addCell(headBJQD);
+ PdfPCell cellBJQDH1 = createBJQDCell("序号", 1, normalFont);
+ tableBJQD.addCell(cellBJQDH1);
+ PdfPCell cellBJQDH2 = createBJQDCell("设备名称", 1, normalFont);
+ tableBJQD.addCell(cellBJQDH2);
+ PdfPCell cellBJQDH3 = createBJQDCell("设备型号", 1, normalFont);
+ tableBJQD.addCell(cellBJQDH3);
+ PdfPCell cellBJQDH4 = createBJQDCell("数量/套", 1, normalFont);
+ tableBJQD.addCell(cellBJQDH4);
+ PdfPCell cellBJQDH5 = createBJQDCell("价格", 1, normalFont);
+ tableBJQD.addCell(cellBJQDH5);
+ int num = 3;
+ for (int i = 1; i <= num; i++) {
+ PdfPCell cellR1 = createBJQDCell(String.valueOf(i), 1, normalFont);
+ tableBJQD.addCell(cellR1);
+ PdfPCell cellR2 = createBJQDCell("设备名称" + i, 1, normalFont);
+ tableBJQD.addCell(cellR2);
+ PdfPCell cellR3 = createBJQDCell("设备型号" + i, 1, normalFont);
+ tableBJQD.addCell(cellR3);
+ PdfPCell cellR4 = createBJQDCell("1", 1, normalFont);
+ tableBJQD.addCell(cellR4);
+ PdfPCell cellR5 = createBJQDCell(RandomUtil.randomNumbers(7), 1, normalFont);
+ tableBJQD.addCell(cellR5);
+ }
+ PdfPCell cellZBFF = createBJQDCell("质保服务", 4, normalFont);
+ tableBJQD.addCell(cellZBFF);
+ PdfPCell cellZBFF1 = createBJQDCell(RandomUtil.randomNumbers(3), 1, normalFont);
+ tableBJQD.addCell(cellZBFF1);
+ PdfPCell cellJJFF = createBJQDCell("交机服务", 4, normalFont);
+ tableBJQD.addCell(cellJJFF);
+ PdfPCell cellJJFF1 = createBJQDCell(RandomUtil.randomNumbers(3), 1, normalFont);
+ tableBJQD.addCell(cellJJFF1);
+ PdfPCell cellBJ = createBJQDCell("备件", 4, normalFont);
+ tableBJQD.addCell(cellBJ);
+ PdfPCell cellBJ1 = createBJQDCell(RandomUtil.randomNumbers(3), 1, normalFont);
+ tableBJQD.addCell(cellBJ1);
+ PdfPCell cellBZ = createBJQDCell(RandomUtil.randomString(1000), 5, normalFont);
+ cellBZ.setHorizontalAlignment(Element.ALIGN_LEFT);
+ tableBJQD.addCell(cellBZ);
+ document.add(tableBJQD);
+ document.add(Chunk.NEXTPAGE);
+ Paragraph sdad = new Paragraph();
+ sdad.add(new Chunk("This is a new page.", new Font(bfChinese, 12, Font.NORMAL)));
+ document.add(sdad);
+
+ document.close();
}
+ private static PdfPCell createBJQDCell(String text, int colSpan, Font font) {
+ PdfPCell cell = new PdfPCell(new Phrase(text, font));
+ cell.setColspan(colSpan);
+ cell.setBorderWidth(0.5f);
+ cell.setBorderColor(Color.BLACK);
+ cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell.setPadding(10f);
+ return cell;
+ }
+
+ private static PdfPCell createLabelCell(String text, Font font) {
+ PdfPCell cell = new PdfPCell(new Phrase(text, font));
+ cell.setBorder(Rectangle.NO_BORDER); // 去掉默认粗边框
+ cell.setHorizontalAlignment(Element.ALIGN_RIGHT); // 标签靠右对齐
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE); // 垂直居中
+ cell.setPaddingBottom(8f); // 行间距
+ cell.setPaddingRight(8f); // 与后面值的间距
+ // 可选:给底部加一条浅灰色的线,区分每行
+ cell.setBorderColorBottom(new Color(220, 220, 220));
+ cell.setBorderWidthBottom(0.5f);
+ return cell;
+ }
+
+ private static PdfPCell createValueCell(String text, Font font) {
+ PdfPCell cell = new PdfPCell(new Phrase(text, font));
+ cell.setBorder(Rectangle.NO_BORDER);
+ cell.setHorizontalAlignment(Element.ALIGN_LEFT); // 值靠左对齐
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell.setMinimumHeight(8f);
+ // 底部同样加浅灰色线
+ cell.setBorderColorBottom(new Color(220, 220, 220));
+ cell.setBorderWidthBottom(0.5f);
+ return cell;
+ }
+
+ private static byte[] downloadImage(String urlStr) {
+ try {
+ HttpClient client = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(10)) // 连接超时 10秒
+ .followRedirects(HttpClient.Redirect.NORMAL) // 自动处理重定向
+ .build();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(urlStr))
+ .timeout(Duration.ofSeconds(15)) // 读取超时 15秒
+ // 某些图片服务器有防盗链,可以模拟浏览器 User-Agent
+ .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
+
+ if (response.statusCode() == 200) {
+ return response.body();
+ } else {
+ System.err.println("下载图片失败,HTTP 状态码: " + response.statusCode());
+ return null;
+ }
+ } catch (Exception e) {
+ System.err.println("下载图片异常: " + e.getMessage());
+ return null;
+ }
+ }
+
+ private static BaseFont getChineseFont() {
+ try {
+ // 1. 从 classpath 获取字体流
+ String fontPath = "fonts/simhei.ttf"; // 对应 resources/fonts/simsun.ttf
+ InputStream inputStream = QuotationApplication.class.getClassLoader().getResourceAsStream(fontPath);
+ if (inputStream == null) {
+ throw new RuntimeException("找不到字体文件: " + fontPath);
+ }
+ // 2. 将流转换为 byte 数组 (Java 9+ 写法)
+ byte[] fontBytes = inputStream.readAllBytes();
+ inputStream.close();
+ // 3. 使用 byte 数组创建 BaseFont
+ // 参数说明:字体名称(随便起), 编码, 嵌入PDF, 缓存, 字体字节流, PFB字节流(null)
+ return BaseFont.createFont("simhei.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
+ } catch (Exception e) {
+ throw new RuntimeException("加载中文字体失败", e);
+ }
+ }
}
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/admin/AdminShoppingController.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/admin/AdminShoppingController.java
index b63bb56d..a8d02557 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/admin/AdminShoppingController.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/admin/AdminShoppingController.java
@@ -3,16 +3,10 @@ package com.nflg.mobilebroken.quotation.controller.admin;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.itextpdf.text.pdf.BaseFont;
-import com.nflg.mobilebroken.common.constant.STATE;
-import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.request.QuotationAdminSearchRequest;
-import com.nflg.mobilebroken.common.pojo.request.QuotationSearchRequest;
import com.nflg.mobilebroken.common.pojo.vo.QuotationSearchVO;
-import com.nflg.mobilebroken.common.util.AppUserUtil;
import com.nflg.mobilebroken.quotation.controller.ControllerBase;
import com.nflg.mobilebroken.quotation.pojo.vo.ShoppingOrderAdjustModelPartVO;
import com.nflg.mobilebroken.quotation.pojo.vo.ShoppingOrderAdjustModelVO;
@@ -20,19 +14,14 @@ import com.nflg.mobilebroken.quotation.pojo.vo.ShoppingOrderAdjustVO;
import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*;
import org.springframework.core.io.ClassPathResource;
-import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
-import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
-import java.io.OutputStream;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/app/ShoppingController.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/app/ShoppingController.java
index c5176bb0..b0fc8a38 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/app/ShoppingController.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/controller/app/ShoppingController.java
@@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.lowagie.text.pdf.BaseFont;
import com.nflg.mobilebroken.common.constant.Constant;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
@@ -45,6 +46,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
+import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -256,6 +258,7 @@ public class ShoppingController extends ControllerBase {
.stream()
.map(kv -> new ShoppingCartPartGroupVO()
.setGroupName(kv.getKey())
+ .setReplaceable(kv.getValue().size() > 1)
.setItems(kv.getValue()
.stream()
.sorted(Comparator.comparing(ShoppingCartPartVO::getType).reversed())
@@ -287,6 +290,7 @@ public class ShoppingController extends ControllerBase {
.stream()
.map(kv -> new ShoppingCartPartGroupVO()
.setGroupName(kv.getKey())
+ .setReplaceable(kv.getValue().size() > 1)
.setItems(kv.getValue()
.stream()
.sorted(Comparator.comparing(ShoppingCartPartVO::getType).reversed())
@@ -851,38 +855,7 @@ public class ShoppingController extends ControllerBase {
public ResponseEntity exportToPdf(HttpServletResponse response, @RequestParam @NotNull(message = "报价单id不能为空") Long id) {
// QuotationShoppingOrder order = shoppingOrderService.getById(id);
// VUtils.trueThrowBusinessError(Objects.isNull(order)).throwMessage("未找到报价单");
-// Map order = new HashMap<>();
-// order.put("no", "DFENNIKFWE562D");
-// Map variables = new HashMap<>();
-// variables.put("info", order);
-// // 渲染HTML
-// TemplateEngine templateEngine = new TemplateEngine();
-// ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
-// resolver.setPrefix("/templates/");
-// resolver.setSuffix(".html");
-// templateEngine.setTemplateResolver(resolver);
-//
-// Context context = new Context();
-// context.setVariables(variables);
-// String html = templateEngine.process("pdf.html", context);
-//
-// response.setContentType(MediaType.APPLICATION_PDF_VALUE);
-// String encode = URLEncoder.encode("aaaa" + ".pdf", StandardCharsets.UTF_8);
-// response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + encode);
-// // 生成PDF
-// try {
-// ITextRenderer renderer = new ITextRenderer();
-// renderer.getFontResolver().addFont("fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
-// URL baseUrl = new ClassPathResource("templates/").getURL();
-// renderer.setDocumentFromString(html, baseUrl.toString());
-// renderer.layout();
-// try (OutputStream outputStream = response.getOutputStream()) {
-// renderer.createPDF(outputStream);
-// }
-// } catch (Exception e) {
-// log.error("生成pdf出错", e);
-// throw new NflgException(STATE.BusinessError, "生成pdf出错");
-// }
+
org.springframework.core.io.Resource resource = new ClassPathResource("templates/demo.pdf");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
@@ -890,6 +863,25 @@ public class ShoppingController extends ControllerBase {
.body(resource);
}
+ public BaseFont getChineseFont() {
+ try {
+ // 1. 从 classpath 获取字体流
+ String fontPath = "fonts/simsun.ttc"; // 对应 resources/fonts/simsun.ttf
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fontPath);
+ if (inputStream == null) {
+ throw new RuntimeException("找不到字体文件: " + fontPath);
+ }
+ // 2. 将流转换为 byte 数组 (Java 9+ 写法)
+ byte[] fontBytes = inputStream.readAllBytes();
+ inputStream.close();
+ // 3. 使用 byte 数组创建 BaseFont
+ // 参数说明:字体名称(随便起), 编码, 嵌入PDF, 缓存, 字体字节流, PFB字节流(null)
+ return BaseFont.createFont("simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
+ } catch (Exception e) {
+ throw new RuntimeException("加载中文字体失败", e);
+ }
+ }
+
/**
* 设置查看密码
*/
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pdf/HeaderFooterEvent.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pdf/HeaderFooterEvent.java
new file mode 100644
index 00000000..70c58ebc
--- /dev/null
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pdf/HeaderFooterEvent.java
@@ -0,0 +1,112 @@
+package com.nflg.mobilebroken.quotation.pdf;
+
+import com.lowagie.text.*;
+import com.lowagie.text.Font;
+import com.lowagie.text.Image;
+import com.lowagie.text.Rectangle;
+import com.lowagie.text.pdf.*;
+import com.nflg.mobilebroken.quotation.QuotationApplication;
+import lombok.extern.slf4j.Slf4j;
+
+import java.awt.*;
+import java.io.IOException;
+import java.io.InputStream;
+
+@Slf4j
+public class HeaderFooterEvent extends PdfPageEventHelper {
+
+ private Image headerImage;
+
+ public HeaderFooterEvent(String classpathLocation) {
+ try {
+ // 1. 通过当前类的 ClassLoader 获取资源流
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(classpathLocation);
+
+ if (inputStream == null) {
+ System.err.println("找不到资源文件: " + classpathLocation);
+ return;
+ }
+
+ // 2. 将 InputStream 转换为 byte 数组
+ // 这是兼容 JAR 包运行的最关键步骤,iText 无法直接使用 InputStream
+ byte[] imageBytes = inputStream.readAllBytes();
+
+ inputStream.close();
+
+ // 3. 使用 byte 数组创建 Image 对象
+ headerImage = Image.getInstance(imageBytes);
+
+ } catch (Exception e) {
+ log.error("Error loading header image:", e);
+ }
+ }
+
+ // 每一页结束时调用,绘制页眉和页脚
+ @Override
+ public void onEndPage(PdfWriter writer, Document document) {
+ drawHeader(writer, document);
+ try {
+ drawFooter(writer, document);
+ } catch (IOException e) {
+ log.error("Error drawing footer:", e);
+ }
+ }
+
+ private void drawHeader(PdfWriter writer, Document document) {
+ if (headerImage == null) return;
+ Rectangle pageSize = document.getPageSize();
+ headerImage.scaleToFit(pageSize.getWidth(), 30f);
+ headerImage.setAbsolutePosition(0, pageSize.getHeight() - headerImage.getScaledHeight());
+ // 将图片写入该页的画布底层
+ PdfContentByte canvas = writer.getDirectContentUnder();
+ canvas.addImage(headerImage);
+ document.setMargins(20, 20, headerImage.getScaledHeight()+10, 30);
+ }
+
+ private void drawFooter(PdfWriter writer, Document document) throws IOException {
+ Rectangle pageSize = document.getPageSize();
+ PdfPTable footerTable = new PdfPTable(1);
+ footerTable.setTotalWidth(pageSize.getWidth());
+ footerTable.setLockedWidth(true);
+
+ BaseFont baseFont = getChineseFont();
+ Font font = new Font(baseFont, 12, Font.NORMAL, Color.WHITE);
+ Phrase footerPhrase = new Phrase();
+ footerPhrase.add(new Chunk("https://www.nflg.com", font));
+ footerPhrase.add(new Chunk(" | ", font));
+ footerPhrase.add(new Chunk("400-887-7788", font));
+ footerPhrase.add(new Chunk(" | ", font));
+ footerPhrase.add(new Chunk("0595-2290555", font));
+
+ PdfPCell footerCell = new PdfPCell(footerPhrase);
+ footerCell.setFixedHeight(25f);
+ footerCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ footerCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ footerCell.setBackgroundColor(new Color(40, 40, 40));
+
+ footerTable.addCell(footerCell);
+
+ // 写入页脚 (绝对定位)
+ // X = 左边距, Y = 下边距 - 一点偏移(让页脚显示在下边距区域内)
+ footerTable.writeSelectedRows(0, -1, 0, 25, writer.getDirectContent());
+ }
+
+ private static BaseFont getChineseFont() {
+ try {
+ // 1. 从 classpath 获取字体流
+ String fontPath = "fonts/simhei.ttf"; // 对应 resources/fonts/simsun.ttf
+ InputStream inputStream = QuotationApplication.class.getClassLoader().getResourceAsStream(fontPath);
+ if (inputStream == null) {
+ throw new RuntimeException("找不到字体文件: " + fontPath);
+ }
+ // 2. 将流转换为 byte 数组 (Java 9+ 写法)
+ byte[] fontBytes = inputStream.readAllBytes();
+ inputStream.close();
+ // 3. 使用 byte 数组创建 BaseFont
+ // 参数说明:字体名称(随便起), 编码, 嵌入PDF, 缓存, 字体字节流, PFB字节流(null)
+ return BaseFont.createFont("simhei.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
+ } catch (Exception e) {
+ throw new RuntimeException("加载中文字体失败", e);
+ }
+ }
+}
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/request/ShoppingSaveRequest.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/request/ShoppingSaveRequest.java
index 89aae78f..8b15e1be 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/request/ShoppingSaveRequest.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/request/ShoppingSaveRequest.java
@@ -106,6 +106,11 @@ public class ShoppingSaveRequest {
*/
private BigDecimal discount;
+ /**
+ * 质保服务id(字典WarrantyStandards项id)
+ */
+ private Long warrantyServiceDicId;
+
/**
* 质保服务描述
*/
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartGroupVO.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartGroupVO.java
index 9b267941..b992a561 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartGroupVO.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartGroupVO.java
@@ -14,5 +14,10 @@ public class ShoppingCartPartGroupVO {
*/
private String groupName;
+ /**
+ * 是否可替换
+ */
+ private boolean replaceable;
+
private List items;
}
diff --git a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartVO.java b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartVO.java
index 2498211f..d11cc3ad 100644
--- a/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartVO.java
+++ b/nflg-mobilebroken-quotation/src/main/java/com/nflg/mobilebroken/quotation/pojo/vo/ShoppingCartPartVO.java
@@ -1,5 +1,6 @@
package com.nflg.mobilebroken.quotation.pojo.vo;
+import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -59,5 +60,12 @@ public class ShoppingCartPartVO {
@JsonIgnore
private String groupName;
+ public String getGroupName() {
+ if (StrUtil.isNotBlank(groupName)){
+ return groupName;
+ }
+ return String.valueOf(id);
+ }
+
private List children;
}
diff --git a/nflg-mobilebroken-quotation/src/main/resources/fonts/simhei.ttf b/nflg-mobilebroken-quotation/src/main/resources/fonts/simhei.ttf
new file mode 100644
index 00000000..1f1bdf85
Binary files /dev/null and b/nflg-mobilebroken-quotation/src/main/resources/fonts/simhei.ttf differ
diff --git a/nflg-mobilebroken-quotation/src/main/resources/images/head.png b/nflg-mobilebroken-quotation/src/main/resources/images/head.png
new file mode 100644
index 00000000..25abc1c1
Binary files /dev/null and b/nflg-mobilebroken-quotation/src/main/resources/images/head.png differ
diff --git a/nflg-mobilebroken-quotation/src/main/resources/templates/demo.pdf b/nflg-mobilebroken-quotation/src/main/resources/templates/demo.pdf
deleted file mode 100644
index 9113e18f..00000000
Binary files a/nflg-mobilebroken-quotation/src/main/resources/templates/demo.pdf and /dev/null differ
diff --git a/nflg-mobilebroken-quotation/src/main/resources/templates/pdf.html b/nflg-mobilebroken-quotation/src/main/resources/templates/pdf.html
deleted file mode 100644
index 62ae8398..00000000
--- a/nflg-mobilebroken-quotation/src/main/resources/templates/pdf.html
+++ /dev/null
@@ -1,176 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
CLOSE TO OUR CUSTOMERS
-
报价主体
-
-
-
报价人代码
-
报价人
-
报价人电话
-
报价人邮箱地址
-
报价日期
-
-
-
- 报价有效期
- 报价有效期
-
-
-
- 报价单号
- 报价单号
-
-
- 总价
- 总价
-
-
-
包含下列费用
-
- 标准配置价格
-
- 选配价格
-
- 其他配置价格
-
- 附加服务费用
-
- 随机备件价格
-
- 运费
-
-
- 付款方式
- 付款方式
-
-
-
机型
-

-
设备简介
-
-
-
配置详情
-
标准配置
-
-
-
-
-
-
可选配置
-
-
-
-
-
其他配置
-
油漆要求
-
其它要求说明
-
随机配件
-
-
- | 物料编号 |
- 备件名称 |
- 备件数量 |
- 备件单价 |
- 备件总金额 |
-
-
- |
- |
- |
- |
- |
-
-
-
交机服务
-
-
- | 工程师人数 |
- 服务天数 |
- 单人天费用 |
- 服务总费用 |
-
-
- |
- |
- |
- |
-
-
-
-
-
\ No newline at end of file
diff --git a/nflg-mobilebroken-quotation/src/test/java/PdfTest.java b/nflg-mobilebroken-quotation/src/test/java/PdfTest.java
new file mode 100644
index 00000000..42dcdb30
--- /dev/null
+++ b/nflg-mobilebroken-quotation/src/test/java/PdfTest.java
@@ -0,0 +1,53 @@
+import com.lowagie.text.*;
+import com.lowagie.text.Font;
+import com.lowagie.text.pdf.BaseFont;
+import com.lowagie.text.pdf.PdfPCell;
+import com.lowagie.text.pdf.PdfPTable;
+import com.lowagie.text.pdf.PdfWriter;
+import org.junit.Test;
+
+import java.awt.*;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class PdfTest {
+
+ @Test
+ public void test1() throws Exception {
+ // 1. 创建文档
+ Document document = new Document(PageSize.A4);
+ PdfWriter.getInstance(document, new FileOutputStream("test.pdf"));
+ document.open();
+ // 2. 设置中文字体 (非常重要,否则中文不显示)
+ // 推荐使用本地 .ttf 字体文件,兼容性最好
+ BaseFont bfChinese = BaseFont.createFont("C:/Windows/Fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+ Font textFont = new Font(bfChinese, 12, Font.NORMAL); // 普通文字字体
+ Font headFont = new Font(bfChinese, 12, Font.BOLD); // 表头加粗字体
+ // 3. 创建表格
+ // 参数表示列数的相对宽度比例(3:2:5)
+ float[] columnWidths = {3f, 2f, 5f};
+ PdfPTable table = new PdfPTable(columnWidths);
+ table.setWidthPercentage(100); // 表格占页面宽度的 100%
+
+ }
+
+ /**
+ * 封装创建单元格的方法
+ */
+ private static PdfPCell createCell(String text, Font font, Color bgColor, int alignment) {
+ // 将文本包装在 Paragraph 中,方便设置对齐方式
+ Paragraph para = new Paragraph(text, font);
+ para.setAlignment(alignment);
+ PdfPCell cell = new PdfPCell(para);
+ // 设置背景色
+ if (bgColor != null) {
+ cell.setBackgroundColor(bgColor);
+ }
+ // 设置内边距
+ cell.setPadding(8);
+ // 设置垂直居中
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ return cell;
+ }
+}
diff --git a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/QuotationShoppingCart.java b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/QuotationShoppingCart.java
index 1d79932d..da4cb4c2 100644
--- a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/QuotationShoppingCart.java
+++ b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/QuotationShoppingCart.java
@@ -136,6 +136,11 @@ public class QuotationShoppingCart implements Serializable {
*/
private Integer status;
+ /**
+ * 质保服务id(字典WarrantyStandards项id)
+ */
+ private Long warrantyServiceDicId;
+
/**
* 质保服务描述
*/
diff --git a/test.pdf b/test.pdf
new file mode 100644
index 00000000..83c44c5a
Binary files /dev/null and b/test.pdf differ