Laravel snappy
之前使用過python+wkhtmltopdf來匯出PDF,wkhtmltopdf確實是很強大的工具,有很多的頁面定製選項,而且會自動幫你把網上的圖片抓取下來,渲染到PDF上。這次想在Laravel-admin中實現匯出PDF的功能,於是找到了Laravel snappy這個擴充套件包,它是對https://github.com/KnpLabs/snappy這個專案的封裝,好巧的是,它也是通過呼叫wkhtmltopdf程式來生成PDF的。
安裝與配置
// 安裝擴充套件包
composer require barryvdh/laravel-snappy
// 將wkhtmltopdf作為composer依賴
// 對於64位系統,使用:
composer require h4cc/wkhtmltopdf-amd64 0.12.x
composer require h4cc/wkhtmltoimage-amd64 0.12.x
對於homestead開發環境,還要執行:
cp vendor/h4cc/wkhtmltoimage-amd64/bin/wkhtmltoimage-amd64 /usr/local/bin/
cp vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64 /usr/local/bin/
chmod +x /usr/local/bin/wkhtmltoimage-amd64
chmod +x /usr/local/bin/wkhtmltopdf-amd64
安裝完後,在app.config
中alias
鍵設定facade別名(可選):
'PDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
'SnappyImage' => Barryvdh\Snappy\Facades\SnappyImage::class,
最後釋出資原始檔:
php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"
在.env
檔案中新增:
WKHTML_PDF_BINARY=/usr/local/bin/wkhtmltopdf-amd64
WKHTML_IMG_BINARY=/usr/local/bin/wkhtmltoimage-amd64
然後在snappy.php
配置檔案中做如下配置:
'pdf' => [
'enabled' => true,
'binary' => env('WKHTML_PDF_BINARY', 'vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64'),
'timeout' => 3600,
'options' => [],
'env' => [],
],
'image' => [
'enabled' => true,
'binary' => env('WKHTML_IMG_BINARY', 'vendor/h4cc/wkhtmltoimage-amd64/bin/wkhtmltoimage-amd64'),
'timeout' => 3600,
'options' => [],
'env' => [],
],
使用
通過載入渲染blade模板生成PDF:
$pdf = PDF::loadView('pdf.invoice', $data); //pdf.invoice是你的blade模板
return $pdf->download('invoice.pdf');
通過外部連結生成:
return PDF::loadFile('http://www.github.com')->inline('github.pdf');
通過html生成,並做各種設定,並儲存之:
PDF::loadHTML($html)->setPaper('a4')->setOrientation('landscape')->setOption('margin-bottom', 0)->save('myfile.pdf')
// 更多選項可檢視wkhtmltopdf的手冊:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt
Laravel-admin匯出功能改造
Laravel-admin預設的匯出格式是csv,這裡將把它改造成想要的PDF格式。
Laravel-admin匯出原理簡單分析
檢視匯出按鈕,可得到這三個匯出入口格式大概如下:
http://hostname/posts?_export_=all // 匯出全部
http://hostname/posts?_export_=page%3A1 // 匯出當前頁
http://hostname/posts?_export_=selected%3A1 // 匯出選定的行
其有對應的控制器方法應該是index
,從這裡追查開去,可以找到/vendor/encore/laravel-admin/src/Grid.php
中有:
public function render()
{
$this->handleExportRequest(true);
try {
$this->build();
} catch (\Exception $e) {
return Handler::renderException($e);
}
$this->callRenderingCallback();
return view($this->view, $this->variables())->render();
}
如果url中有帶_export=…引數,將會執行$this->handleExportRequest(true);
這裡面的程式碼:
protected function handleExportRequest($forceExport = false)
{
if (!$scope = request(Exporter::$queryName)) {
return;
}
// clear output buffer.
if (ob_get_length()) {
ob_end_clean();
}
$this->disablePagination();
if ($forceExport) {
$this->getExporter($scope)->export(); // 這裡將呼叫某個類的export方法
}
}
最關鍵的是export
方法,我們將新建一個繼承AbstractExporter
類的類,實現我們自己想要的匯出邏輯。另外,看getExporter
方法:
protected function getExporter($scope)
{
return (new Exporter($this))->resolve($this->exporter)->withScope($scope);
}
我們還可以在子類中改寫withScope
進行一些引數設定、攔截。
開始改造匯出功能
瞭解了基本的原理,再參考下Laravel-admin的文件,我們就可以著手改下匯出功能了。
首先,建立一個擴充套件,如app/Admin/Extensions/PdfExporter.php
,程式碼實現如下:
<?php
namespace App\Admin\Extensions;
use Encore\Admin\Grid\Exporters\AbstractExporter;
use Encore\Admin\Grid\Exporter;
use PDF;
class PdfExporter extends AbstractExporter
{
protected $lackOfUserId = false;
public function withScope($scope){
// 你自己的一些處理邏輯,比如:
/*if ($scope == Exporter::SCOPE_ALL) {
if(request()->has('user_id')) {
$this->grid->model()->where('user_id', request()->user_id);
} else {
$this->lackOfUserId = true;
}
return $this;
}*/
return parent::withScope($scope);
}
public function export()
{
// 具體的匯出邏輯,比如:
if($this->lackOfUserId) {
$headers = [
'Content-Encoding' => 'UTF-8',
'Content-Type' => 'text/html;charset=UTF-8',
];
response('請先篩選出使用者', 200, $headers)->send();
exit();
}
$author = $this->grid->model()->getOriginalModel()->first()->user->user_name;
$this->grid->model()->orderBy('add_time', 'desc');
// 按年-月分組資料
$data = collect($this->getData())->groupBy(function ($post) {
return Carbon::parse(date('Y-m-d',$post['add_time']))->format('Y-m');
})->toArray();
// 渲染資料到blade模板
$output = PDF::loadView('pdf.weibo', compact('data'))->setOption('footer-center', '[page]')->output();
$headers = [
'Content-Type' => 'application/pdf',
'Content-Disposition' => "attachment; filename=$author.pdf",
];
// 匯出檔案,
response(rtrim($output, "\n"), 200, $headers)->send();
exit;
}
}
接著,在app/Admin/bootstrap.php
中註冊擴充套件:
Exporter::extend('pdf-exporter', PdfExporter::class);
最後,對應的在Grid
方法中使用:
protected function grid()
{
// 其他邏輯...
// 新增匯出PDF的擴充套件
$grid->exporter('pdf-exporter');
return $grid;
}
這樣,點選匯出按鈕的時候,就可以下載PDF了。
注意事項
- blade模板中的css、js地址必須是完整的url地址,所以
mix('css/app.css')
應該改為asset('css/app.css')
- 圖片地址最好使用http協議代替https,比較不容易出錯
最後,貼個效果圖吧:
本作品採用《CC 協議》,轉載必須註明作者和本文連結