Skip to content

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

snappy是一个对wkhtmltopdf封装的类库,使用非常简单。

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

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

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

  1. 首先下载安装wkhtmltopdf,以Ubuntu为例,来到https://wkhtmltopdf.org/downloads.html下载对应的版本
shell
# 下载安装包
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. 按照 laravel-snappy 教程,添加Facade,生成config/snappy.php

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

html
<!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. Controller中添加生成pdf方法 sendReceiptEmail是发送发票到客户邮箱中,需要先调用exportToPDF生成pdf格式的发票,然后作为邮箱附件发送出去。
php
    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());
        }
    }

app/Mail/PosOrderReceipt.php

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. 如果不开启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