wkhtmltopdf (opens new window) 是用C实现的生成pdf的开源软件。可以基于网址或html生成对应的PDF文件。 对OSX, linux, windows平台都提供了对应的支持。

snappy (opens new window)是一个对wkhtmltopdf封装的类库,使用非常简单。

而这里介绍的 laravel-snappy (opens new window) 则又是对snappy的封装,只不过方便集成到Laravel框架中。

在使用 laravel-snappy 之前我建议先浏览下 wkhtmltopdf官方文档 (opens new window) wkhtmltopdf下载后之后就是一个bin二进制文件,提供了非常多的参数。

这里介绍下怎么在Laravel6中使用laravel-snappy并生成pdf文件

  1. 首先下载安装wkhtmltopdf,以Ubuntu为例,来到https://wkhtmltopdf.org/downloads.html下载对应的版本
# 下载安装包
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.
bionic_amd64.deb
# 安装
sudo dpkg -i wkhtmltox_0.12.6-1.bionic_amd64.deb
# 如果报错,如缺少依赖,可以执行下面这个
sudo apt-get -f install

# 检查是否安装成功 
which wkhtmltopdf
# 输出
/usr/local/bin/wkhtmltopdf
which wkhtmltoimage
# 输出
/usr/local/bin/wkhtmltoimage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 按照 laravel-snappy 教程,添加Facade,生成config/snappy.php

  2. 新建resources/views/pos/receipt-pdf.blade.php模板, 这里面有一些变量需要Controller传给视图,需要注意字体和图片的引用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>RECEIPT</title>
    <style type="text/css">
        .container-fluid {
            width: 100%;
            padding-right: 15px;
            padding-left: 15px;
            margin-right: auto;
            margin-left: auto;
        }
        @font-face {
            font-family: 'Microsoft YaHei';
            src: url('file://{{ public_path('fonts/msyh.ttc') }}') format('truetype');
            font-weight: normal;
            font-style: normal;
        }
        body {
            font-family: 'Microsoft YaHei';
            margin: 20px 40px;
            color: #222222;
        }
        thead {
            display: table-header-group;
        }
        tfoot {
            display: table-row-group;
        }
        tr {
            page-break-inside: avoid;
        }
        .mt-5 {
            margin-top: 3rem;
        }
        .mb-5 {
            margin-bottom: 3rem;
        }
        .font-bolder {
            font-weight: bolder;
        }
        .text-center {
            text-align: center;
        }
        .bottom-none {
            border-bottom: none !important;
        }
        .float-left {
            float: left !important;
        }
        .float-right {
            float: right !important;
        }
        .text-gray {
            color: #95aac9;
        }
        .text-danger {
            color: #dc3545 !important;
        }
        .table-receipt td {
            padding: 5px;
        }
        .table-items tr > td {
            border-bottom: 1px solid #dee2e6;
            padding: 15px;
        }
        .table-items tr > td:first-child {
            padding-left: 0;
        }
        .table-items tr > td:last-child {
            padding-right: 0;
        }
        tr.header {
            color: #95aac9;
        }
        tr.header > td {
            padding-top: 25px
        }
    </style>
</head>
<body>
<div class="container-fluid">
    <div class="text-center">
        <img height="auto" src="{{ public_path('images/logo-black.png') }}"/>
        <h1>Receipt from {{data_get($companyInfo, 'comName')}}</h1>
        <p class="text-gray" style="font-size: 18pt">Transaction ID: {{ $transactionID }}</p>
    </div>
    <table border="0" cellpadding="0" cellspacing="0" style="width: 100%" class="table-receipt">
        <thead>
        <tr class="header">
            <td align="left">INVOICED FROM</td>
            <td align="right">INVOICED TO</td>
        </tr>
        </thead>
        <tbody>
        <tr class="font-bolder">
            <td>{{ data_get($companyInfo, 'comName')}}</td>
            <td align="right">Guest</td>
        </tr>
        <tr>
            <td>{{ data_get($companyInfo, 'comAddress1')}}</td>
            <td align="right">--</td>
        </tr>
        <tr>
            <td>{{ data_get($companyInfo, 'comAddress2')}}</td>
            <td align="right">--</td>
        </tr>
        <tr class="header">
            <td>INVOICE ID</td>
            <td align="right">PAYMENT AT</td>
        </tr>
        <tr class="font-bolder">
            <td>{{ $transactionID }}</td>
            <td align="right">{{ date('m-d-Y') }} at {{ date('H:i:s') }}</td>
        </tr>
        <tr class="header">
            <td>PAGE</td>
            <td align="right">PAYMENT METHOD</td>
        </tr>
        <tr class="font-bolder">
            <td>1 of 1</td>
            <td align="right">{{ data_get($payMethod, 'label') }}</td>
        </tr>
        </tbody>
    </table>
    <hr class="mt-5" style="border: 1px solid #95aac9;opacity: 10%;">
    <h3 class="text-gray text-center">THANK YOU FOR YOUR ORDER!</h3>
    <h3 class="text-gray text-center">WE HOPE TO SEE YOU AGAIN SOON!</h3>
</div>
</body>
</html>
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
  1. Controller中添加生成pdf方法 sendReceiptEmail是发送发票到客户邮箱中,需要先调用exportToPDF生成pdf格式的发票,然后作为邮箱附件发送出去。
    public function exportToPDF($pdfData) {
        $blade = 'pos.receipt-pdf';
        $pdf = \App::make('snappy.pdf.wrapper');
        // 传给pdf视图模板的变量,有可能是从数据库或session中获取,这里不展开
        $body = \View::make($blade, $pdfData)->render();
        $pdf->loadHTML($body)->setPaper('a4');
        // 这里的参数其实是wkhtmltopdf的,文档里都可以查到,由于我们引用了本地图片和字体,需要开启本地访问文件权限
        $pdf->setOption('enable-local-file-access', true);
        $pdf->setOption('margin-top', '5mm');
        $pdf->setOption('margin-bottom', '5mm');
        $pdf->setOption('margin-left', '5mm');
        $pdf->setOption('margin-right', '5mm');
        return $pdf;
    }

    public function sendReceiptEmail(Request $request) {
        // 保存pdf的临时路径
        $pdfPath = storage_path('app/order-receipt/tmp/tmp.pdf');
        if (file_exists($pdfPath)) {
            \File::delete($pdfPath);
        }
        
        // 先获取pdf需要的数据
        $pdfData = $this->processPdfData($request);
        
        // 生成pdf
        $pdf = $this->exportToPDF($pdfData);

        // 保存成功,作为附件发送出去
        if ($pdf->save($pdfPath)) {
            Mail::to('mafeifan@qq.com')
                ->send(new PosOrderReceipt());
        }
    }

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

app/Mail/PosOrderReceipt.php

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class PosOrderReceipt extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {

    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        $location = storage_path('app/order-receipt/tmp/tmp.pdf');
        return $this
            ->view('emails.pos.order-receipt', [
            ])
            ->attach($location, [
                'as' => 'receipt.pdf',
                'mime' => 'application/pdf']);
    }
}

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

# 注意事项

  1. 如果不开启enable-local-file-access引用本地文件时会报类似Blocked access to file /DemoProjectPath/public/images/logo-black.png↵的错误
  2. 而且路径storage_path('app/order-receipt/tmp/tmp.pdf')也需要有访问权限,可使用chmod 755解决

# 参考

https://segmentfault.com/a/1190000018988358

https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

上次更新: 2023/10/7 13:56:16