From 926e065fb0b4424d0d51023c234a92433bac61c8 Mon Sep 17 00:00:00 2001
From: Pu Zhibing <393733352@qq.com>
Date: 星期五, 01 八月 2025 11:53:45 +0800
Subject: [PATCH] Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/QianYunTong

---
 UserQYTTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/pdf/TripSheetGenerator.java |  337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 337 insertions(+), 0 deletions(-)

diff --git a/UserQYTTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/pdf/TripSheetGenerator.java b/UserQYTTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/pdf/TripSheetGenerator.java
new file mode 100644
index 0000000..23c7f29
--- /dev/null
+++ b/UserQYTTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/pdf/TripSheetGenerator.java
@@ -0,0 +1,337 @@
+package com.stylefeng.guns.modular.system.pdf;
+
+import com.itextpdf.text.*;
+import com.itextpdf.text.pdf.BaseFont;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
+import com.stylefeng.guns.modular.system.model.vo.TripOrderVo;
+import com.stylefeng.guns.modular.system.warpper.OrderWarpper;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.ServletContextAware;
+
+import java.io.*;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.UUID;
+
+@Component
+public class TripSheetGenerator {
+
+    @Value("${trip.sheet.filePath}")
+    private String pdfDir;
+
+    // 内置中文备用字体
+    private static final String FALLBACK_FONT = "STSong-Light";
+    private static final String FALLBACK_ENCODING = "UniGB-UCS2-H";
+
+    // 完整日期时间格式
+    private static final DateTimeFormatter DATE_TIME_FORMATTER =
+            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    public String generatePdf(List<TripOrderVo> orders) throws DocumentException, IOException {
+        if (orders == null || orders.isEmpty()) {
+            throw new IllegalArgumentException("订单列表不能为空");
+        }
+
+        String fileName = "行程单_" + UUID.randomUUID() + ".pdf";
+        String filePath = pdfDir + fileName;
+        File file = new File(filePath);
+        FileUtils.forceMkdirParent(file);
+
+        Document document = new Document(PageSize.A4);
+        PdfWriter.getInstance(document, new FileOutputStream(file));
+        document.open();
+
+        // 新布局:Logo+标题 → 主标题 → 信息行 → 表格
+        addLogoAndTitle(document);
+        addMainTitle(document);
+        addTripInfo(document, orders);
+        addOrderTable(document, orders);
+
+        document.close();
+        return filePath;
+    }
+
+    private void addLogoAndTitle(Document document) throws DocumentException {
+        try {
+            document.setMargins(10, 50, 50, 50); // 左=0,右=50,上=50,下=50(单位:pt)
+            Resource logoResource = new ClassPathResource("static/img/logo.png");
+            if (logoResource.exists()) {
+                Image logo = Image.getInstance(logoResource.getURL());
+                logo.scaleToFit(80, 40); // 保持logo原有尺寸(宽100pt,高40pt)
+
+                // 1. 调整表格列宽:左列刚好容纳logo,右列仅够放下文字(缩小间距)
+                // 列宽比例:左列100pt(logo宽度),右列80pt(文字宽度,足够放下“贵人家园”)
+                PdfPTable layoutTable = new PdfPTable(new float[]{81f, 100f}); // 左列(Logo),右列100f(文字)
+                layoutTable.setWidthPercentage(35);
+                layoutTable.setSpacingAfter(15f); // 与下方主标题的间距不变
+                layoutTable.setHorizontalAlignment(Element.ALIGN_LEFT); // 整体左对齐,避免居中导致的空隙
+
+                // 2. 左单元格(logo):消除内边距,紧贴右侧
+                PdfPCell logoCell = new PdfPCell(logo);
+                logoCell.setBorder(Rectangle.NO_BORDER); // 无边框
+                logoCell.setPadding(0); // 去除内边距(关键:默认padding会导致空隙)
+                logoCell.setVerticalAlignment(Element.ALIGN_MIDDLE); // 垂直居中对齐
+                logoCell.setHorizontalAlignment(Element.ALIGN_RIGHT); //logo居中
+                layoutTable.addCell(logoCell);
+
+                // 3. 右单元格(文字):消除内边距,紧贴左侧
+                Font titleFont = getChineseFont(18, Font.BOLD);
+                Paragraph titlePara = new Paragraph("贵人家园", titleFont);
+                titlePara.setAlignment(Element.ALIGN_LEFT); // 文字左对齐,贴近logo
+                titlePara.setSpacingBefore(0);
+                titlePara.setSpacingAfter(0);
+
+                PdfPCell titleCell = new PdfPCell(titlePara);
+                titleCell.setBorder(Rectangle.NO_BORDER); // 无边框
+                titleCell.setPadding(0); // 去除内边距(关键)
+                titleCell.setVerticalAlignment(Element.ALIGN_MIDDLE); // 垂直居中对齐
+                titleCell.setHorizontalAlignment(Element.ALIGN_LEFT); // 文字靠左,贴近logo
+                layoutTable.addCell(titleCell);
+
+                document.add(layoutTable);
+                // 关键3:恢复页面默认边距(避免影响后续内容)
+                document.setMargins(50, 50, 50, 50);
+            } else {
+                // 降级处理(无logo时)
+                Font titleFont = getChineseFont(18, Font.BOLD);
+                Paragraph fallbackTitle = new Paragraph("贵人家园", titleFont);
+                fallbackTitle.setAlignment(Element.ALIGN_LEFT);
+                fallbackTitle.setSpacingAfter(15f);
+                document.add(fallbackTitle);
+            }
+        } catch (Exception e) {
+            System.err.println("Logo加载失败: " + e.getMessage());
+            // 降级处理
+            Font titleFont = getChineseFont(18, Font.BOLD);
+            Paragraph fallbackTitle = new Paragraph("贵人家园", titleFont);
+            fallbackTitle.setAlignment(Element.ALIGN_LEFT);
+            fallbackTitle.setSpacingAfter(15f);
+            document.add(fallbackTitle);
+        }
+    }
+
+    // 新增主标题方法(原 addTitle 逻辑调整)
+    private void addMainTitle(Document document) throws DocumentException {
+        Font titleFont = getChineseFont(18, Font.BOLD);
+        Paragraph mainTitle = new Paragraph("贵人家园—打车—行程单", titleFont);
+        mainTitle.setAlignment(Element.ALIGN_CENTER);
+        mainTitle.setSpacingBefore(5f);
+        mainTitle.setSpacingAfter(15f);
+        document.add(mainTitle);
+    }
+
+
+
+    // 修改信息行构建逻辑(以第一行为例)
+    private void addTripInfo(Document document, List<TripOrderVo> orders) throws DocumentException {
+        if (orders.isEmpty()) return;
+        TripOrderVo first = orders.get(0);
+        TripOrderVo last = orders.size() > 1 ? orders.get(orders.size() - 1) : first;
+
+        Font infoFont = getChineseFont(10, Font.NORMAL);
+        // 申请时间现在的时间
+        String applyTime = DATE_TIME_FORMATTER.format(LocalDateTime.now());
+// 首先定义SimpleDateFormat(可以是类的静态成员)
+        SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        String tripTimeStart = first.getBoardingTime() != null
+                ? DATE_FORMATTER.format(first.getBoardingTime()) : "N/A";
+        String tripTimeEnd = last.getBoardingTime() != null
+                ? DATE_FORMATTER.format(last.getBoardingTime()) : "N/A";
+
+        String tripTime = tripTimeStart + " 至 " + tripTimeEnd;
+
+        // 总金额计算(修复:先定义 totalText 并拼接内容)
+        double totalAmount = orders.stream()
+                .mapToDouble(o -> o.getPayMoney() != null ? o.getPayMoney() : 0)
+                .sum();
+        String totalPrefix = "共计" + orders.size() + "单行程,合计";
+        String totalMoney = String.format("%.2f元", totalAmount);
+        String totalText = totalPrefix + totalMoney; // 正确定义 totalText
+
+
+        // ========== 第一行:申请时间 + 行程时间(均带分隔条) ==========
+        // 关键:通过表格列宽控制分隔条宽度(8pt)
+        PdfPTable line1Table = new PdfPTable(4);
+        line1Table.setWidthPercentage(100);
+        line1Table.setSpacingAfter(5f);
+        // 列宽比例:分隔条列固定为8pt,内容列按比例分配
+        line1Table.setWidths(new float[]{4, 200, 4, 374});
+
+        // 列1:申请时间前的蓝色分隔条(宽度由表格列宽控制)
+        line1Table.addCell(createBlueSeparatorCell());
+
+        // 列2:申请时间内容
+        Paragraph applyPara = new Paragraph();
+        applyPara.add(new Chunk("申请时间:", infoFont));
+        applyPara.add(new Chunk(applyTime, infoFont));
+        line1Table.addCell(createContentCell(applyPara));
+
+        // 列3:行程时间前的蓝色分隔条(与列1宽度一致)
+        line1Table.addCell(createBlueSeparatorCell());
+
+        // 列4:行程时间内容
+        Paragraph tripPara = new Paragraph();
+        tripPara.add(new Chunk("行程时间:", infoFont));
+        tripPara.add(new Chunk(tripTime, infoFont));
+        line1Table.addCell(createContentCell(tripPara));
+
+        document.add(line1Table);
+
+
+        // ========== 第二行:手机号 + 共计订单(均带分隔条) ==========
+        PdfPTable line2Table = new PdfPTable(4);
+        line2Table.setWidthPercentage(100);
+        line2Table.setSpacingAfter(15f);
+        // 与第一行保持完全相同的列宽比例(确保分隔条对齐)
+        line2Table.setWidths(new float[]{4, 200, 4, 374});
+
+        // 列1:手机号前的蓝色分隔条
+        line2Table.addCell(createBlueSeparatorCell());
+
+        // 列2:手机号内容
+        Paragraph phonePara = new Paragraph();
+        phonePara.add(new Chunk("行程人手机号:", infoFont));
+        phonePara.add(new Chunk(first.getPassengersPhone() != null ? first.getPassengersPhone() : "N/A", infoFont));
+        line2Table.addCell(createContentCell(phonePara));
+
+        // 列3:共计订单前的蓝色分隔条
+        line2Table.addCell(createBlueSeparatorCell());
+
+        // 列4:共计订单内容
+        Paragraph totalPara = new Paragraph();
+        totalPara.add(new Chunk(totalPrefix, infoFont));
+        Font totalFont = getChineseFont(10, Font.BOLD);
+        totalFont.setColor(BaseColor.RED);
+        totalPara.add(new Chunk(totalMoney, totalFont));
+        line2Table.addCell(createContentCell(totalPara));
+
+        document.add(line2Table);
+    }
+
+    /**
+     * 蓝色分隔条单元格(宽度由所在表格的列宽控制,无需单独设置)
+     */
+    private PdfPCell createBlueSeparatorCell() {
+        PdfPCell separatorCell = new PdfPCell();
+        // 关键:不设置宽度,完全由表格的 setWidths() 控制
+        separatorCell.setBackgroundColor(BaseColor.BLUE); // 蓝色背景
+        separatorCell.setBorder(Rectangle.NO_BORDER); // 无边框
+        separatorCell.setMinimumHeight(8); // 匹配文字行高
+        separatorCell.setPadding(0); // 去除内边距,确保分隔条紧凑
+        return separatorCell;
+    }
+
+    /**
+     * 内容单元格样式
+     */
+    private PdfPCell createContentCell(Paragraph content) {
+        PdfPCell cell = new PdfPCell(content);
+        cell.setBorder(Rectangle.NO_BORDER);
+        cell.setPaddingLeft(5); // 内容与分隔条的间距
+        cell.setHorizontalAlignment(Element.ALIGN_LEFT);
+        return cell;
+    }
+    private void addOrderTable(Document document, List<TripOrderVo> orders) throws DocumentException {
+        // 调整列宽:第1列(序号)加宽至1.5f
+        float[] columnWidths = {1.5f, 2f, 2f, 3f, 2f, 3f, 3f, 2f};
+        PdfPTable table = new PdfPTable(columnWidths);
+        table.setWidthPercentage(100);
+        table.setSpacingBefore(10f); // 与上方信息行的间距
+
+        // 表头样式(蓝色背景,白色文字,居中)
+        Font headerFont = getChineseFont(10, Font.BOLD);
+        headerFont.setColor(BaseColor.WHITE);
+        BaseColor headerBg = new BaseColor(59, 130, 246);
+        String[] headers = {"序号","服务商","车型","上车时间","城市","起点","终点","金额"};
+
+        for (String header : headers) {
+            PdfPCell cell = new PdfPCell(new Paragraph(header, headerFont));
+            cell.setBackgroundColor(headerBg);
+            cell.setPadding(5);
+            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+            table.addCell(cell);
+        }
+           SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 格式可根据需求调整
+        // 单元格样式(居中对齐,统一处理)
+        Font cellFont = getChineseFont(9, Font.NORMAL);
+        for (int i = 0; i < orders.size(); i++) {
+            TripOrderVo order = orders.get(i);
+            // 序号列:强制居中,内容为 i+1
+            addCenteredCell(table, String.valueOf(i + 1), cellFont);
+            addCenteredCell(table, order.getCompanyName(), cellFont);
+            addCenteredCell(table, order.getServerCarModel(), cellFont);
+            addCenteredCell(table,
+                    order.getBoardingTime() != null
+                            ? DATE_FORMATTER.format(order.getBoardingTime())  // Date类型用SimpleDateFormat的format方法
+                            : "N/A",
+                    cellFont);
+            addCenteredCell(table, order.getCity(), cellFont);
+            addCenteredCell(table, order.getStartAddress(), cellFont);
+            addCenteredCell(table, order.getEndAddress(), cellFont);
+
+            // 金额列:右对齐
+            PdfPCell moneyCell = new PdfPCell(
+                    new Paragraph(String.format("%.2f元",
+                            order.getPayMoney() != null ? order.getPayMoney() : 0),
+                            cellFont)
+            );
+            moneyCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
+            moneyCell.setPadding(5);
+            table.addCell(moneyCell);
+        }
+
+        document.add(table);
+    }
+
+    // 辅助方法:创建居中对齐的单元格
+    private void addCenteredCell(PdfPTable table, String text, Font font) {
+        PdfPCell cell = new PdfPCell(new Paragraph(text != null ? text : "", font));
+        cell.setPadding(5);
+        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+        table.addCell(cell);
+    }
+
+    private void addTableCell(PdfPTable table, String text, Font font) {
+        PdfPCell cell = new PdfPCell(new Paragraph(text != null ? text : "", font));
+        cell.setPadding(5);
+        table.addCell(cell);
+    }
+
+    /**
+     * 获取中文字体,优先自定义字体,fallback到 CJK 内置
+     */
+    private Font getChineseFont(float size, int style) {
+        // 自定义字体
+        try {
+            Resource res = new ClassPathResource("static/fonts/AlibabaPuHuiTi-3-105-Heavy.ttf");
+            if (res.exists()) {
+                BaseFont bf = BaseFont.createFont(
+                        res.getURL().toString(), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+                return new Font(bf, size, style);
+            }
+        } catch (Exception e) {
+            System.err.println("加载自定义字体失败: " + e.getMessage());
+        }
+        // 内置 CJK 字体
+        try {
+            BaseFont bf = BaseFont.createFont(
+                    FALLBACK_FONT, FALLBACK_ENCODING, BaseFont.EMBEDDED);
+            return new Font(bf, size, style);
+        } catch (Exception e) {
+            System.err.println("加载备用字体失败: " + e.getMessage());
+            return new Font(Font.FontFamily.HELVETICA, size, style);
+        }
+    }
+}

--
Gitblit v1.7.1