如何在React JS前端下载Laravel后端生成的PDF文件
Laravel后端PDF在React前端的下载方案与最佳实践
问题背景
我有一个Laravel应用,已实现通过指定端点生成PDF的功能,后端逻辑正常,可直接访问对应路由获取PDF。现在需要在React前端实现PDF下载功能,想了解对接Laravel场景下的标准方法与最佳实践。
后端现有代码
Laravel控制器代码
public function mostReservationReport (Request $request) { $year = $request->input('year'); $customer = DB::table('reservasi as r') ->join('customer as c', 'r.id_customer', '=', 'c.id') ->join('transaksi as t', 'r.id', '=', 't.id_reservasi') ->select('c.nama', DB::raw('COUNT(r.id) as jumlah'), DB::raw('SUM(t.total_pembayaran) as total')) ->whereYear('t.created_at', $year) ->groupBy('c.nama') ->orderByDesc('jumlah') ->limit(5) ->get(); $now = now(); $no = 1; $total = 0; foreach($customer as $data){ $data->no = $no; $no++; $total += $data->total; } $pdf = PDF::loadView('top-five-customer', compact('year', 'customer', 'now', 'total')); return $pdf->stream('top-five-customer.pdf'); }
Blade模板代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Top 5 customer with the most reservasions</title> <link href="https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.css" rel="stylesheet" type="text/css" media="all"/> <script src="https://cdn.tailwindcss.com"></script> <style> .table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%; } .td, .th { border: 1px solid #dddddd; text-align: left; padding: 8px; } .table-borderless { border-collapse: collapse; border-spacing: 0; width: 100%; border: none !important; } .td-borderless { border: none !important; } .th-borderless { border: none !important; } .text-right { text-align: right; } .text-center { text-align: center; } .text-bold { font-weight: bold; } .w-10 { width: 10%; } .w-20 { width: 20%; } .w-30 { width: 30%; } .w-60 { width: 60%; } .w-80 { width: 400px; } .w-90 { width: 500px; } .w-full { width: 100%; } .center { text-align: center; } .center img { margin: auto; display: block; } </style> </head> <body> <div class="center"> <img src="{{ public_path('storage/gah.jpg') }}" alt="logo"> <h6>Jl. P. Mangkubumi No.18, Yogyakarta 55233</h6> <hr style="margin-top: 40px;margin-bottom: 10px;"/> <h4 style="text-align:center; font-weight: 900;">TOP 5 CUSTOMER WITH MOST RESERVATIONS REPORT</h5> <p style="text-align:left">Year : {{$year}}</p> <div className="overflow-x-auto" style="margin-top: 10px;"> <table class="table w-full"> <thead> <tr> <th class="th">No</th> <th class="th">Customer's Name</th> <th class="th">Reservation Count</th> <th class="th">Payment Total</th> </tr> </thead> <tbody> @foreach($customer as $item) <tr> <th class="th">{{$item->no}}</th> <td class="td">{{$item->nama}}</td> <td class="td">{{$item->jumlah}}</td> <td class="td">Rp {{ number_format($item->total, 2,'.', ',') }}</td> </tr> @endforeach <tr> <td></td> <td></td> <td class="text-bold">Total</td> <td class="text-bold">Rp {{ number_format($total, 2,'.', ',') }}</td> </tr> </tbody> </table> </div> <br/> <table class="table"> <tbody> <tr> <td class="w-80"></td> <td class="w-10">Dicetak tanggal {{$now}}</th> </tr> </tbody> </table> <br/> </div> </body> </html>
前端实现方案
方案1:直接使用a标签跳转(最简GET请求场景)
如果PDF接口是GET请求,且不需要携带复杂参数或认证Token,直接用a标签跳转是最简便的方式。
function DownloadPDFButton() { const year = 2024; // 可根据业务逻辑动态获取 return ( <a href={`${process.env.REACT_APP_API_URL}/api/reports/most-reservation?year=${year}`} target="_blank" rel="noopener noreferrer" download="top-five-customer.pdf" > 下载PDF报告 </a> ); }
提示:若希望浏览器直接触发下载而非预览,可将后端代码中的return $pdf->stream('top-five-customer.pdf');改为return $pdf->download('top-five-customer.pdf');,该方法会自动添加Content-Disposition: attachment响应头。
方案2:使用Fetch请求Blob(适合带Token/POST请求场景)
如果需要携带认证Token,或用POST请求传递参数,需通过Fetch获取Blob对象后手动触发下载。
async function downloadPDF() { const year = 2024; try { const response = await fetch(`${process.env.REACT_APP_API_URL}/api/reports/most-reservation`, { method: 'POST', // 匹配后端路由定义的请求方法 headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` // 携带认证Token }, body: JSON.stringify({ year }) // POST请求参数 }); if (!response.ok) throw new Error('请求失败'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); // 创建临时a标签触发下载 const a = document.createElement('a'); a.href = url; a.download = 'top-five-customer.pdf'; document.body.appendChild(a); a.click(); // 清理资源避免内存泄漏 window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { console.error('下载失败:', error); alert('PDF下载失败,请稍后重试'); } } // 按钮触发下载 function DownloadPDFButton() { return <button onClick={downloadPDF}>下载PDF报告</button>; }
方案3:使用第三方库简化操作(推荐file-saver)
file-saver封装了Blob下载逻辑,代码更简洁易读。
先安装依赖:
npm install file-saver # 或 yarn add file-saver
然后实现下载功能:
import { saveAs } from 'file-saver'; async function downloadPDF() { const year = 2024; try { const response = await fetch(`${process.env.REACT_APP_API_URL}/api/reports/most-reservation`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ year }) }); if (!response.ok) throw new Error('请求失败'); const blob = await response.blob(); saveAs(blob, 'top-five-customer.pdf'); } catch (error) { console.error('下载失败:', error); alert('PDF下载失败,请稍后重试'); } } function DownloadPDFButton() { return <button onClick={downloadPDF}>下载PDF报告</button>; }
最佳实践
后端响应优化
- 优先使用
download()方法生成PDF,确保响应头包含Content-Disposition: attachment; filename="xxx.pdf",强制浏览器触发下载。 - 明确设置Content-Type:
return $pdf->download('top-five-customer.pdf')->header('Content-Type', 'application/pdf');
- 优先使用
前端处理注意事项
- 跨域配置:确保Laravel后端配置了CORS规则(可使用
barryvdh/laravel-cors包),允许前端域名发起请求。 - 错误处理:必须捕获请求失败的异常,给用户友好的提示信息。
- 资源清理:使用
URL.revokeObjectURL()释放Blob资源,避免内存泄漏。 - 权限验证:若接口需要认证,务必在请求头中携带Token,确保权限校验通过。
- 跨域配置:确保Laravel后端配置了CORS规则(可使用
参数传递规范
- GET请求适合简单参数(如年份),直接拼在URL中即可。
- POST请求适合复杂参数,通过请求体传递,同时后端需对应修改路由和请求方法。
内容的提问来源于stack exchange,提问作者Jumbo Mambo
相关产品推荐
相关产品推荐

