liujie
1 天以前 16ee4f20dc68b9e6a19490c935aa17bb325f8f0d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
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);
        }
    }
}