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