系列文章:
1.利用 laravel 12 做一個類似Google文件的簡單範例
2.在本機Laravel 12 中實作 Google OAuth2 登入
3.如何在 Laravel 12 使用 Gmail SMTP 寄信
4.在本機Laravel 12 中實作 FaceBook OAuth2 登入
5.在本機 laravel 12 ,上傳很多PDF,上傳之後可以自己排序,合併之後下載-方法一
6.在本機 laravel 12 ,上傳很多PDF,上傳之後可以自己排序,合併之後下載-方法二
1.利用 laravel 12 做一個類似Google文件的簡單範例
2.在本機Laravel 12 中實作 Google OAuth2 登入
3.如何在 Laravel 12 使用 Gmail SMTP 寄信
4.在本機Laravel 12 中實作 FaceBook OAuth2 登入
5.在本機 laravel 12 ,上傳很多PDF,上傳之後可以自己排序,合併之後下載-方法一
6.在本機 laravel 12 ,上傳很多PDF,上傳之後可以自己排序,合併之後下載-方法二
一、功能摘要
1.多檔 PDF 上傳
2.拖曳排序
3.依排序順序合併 PDF
4.合併後直接下載
5.完全本機運作
二、專案結構
laravel12PdfMerge/
├── app/
│ └── Http/
│ └── Controllers/
│ └── PdfMergeController.php
├── resources/
│ └── views/
│ └── pdf-merge.blade.php
├── routes/
│ └── web.php
├── storage/
│ └── app/
│ └── pdfs/
│ └── .gitkeep
三、核心技術選擇
項目 技術
Framework Laravel 12
PDF 處理 FPDI
拖曳排序 SortableJS
儲存 Laravel Storage
前端 Blade + Vanilla JS
四、建立專案 laravel12PdfMerge 與 安裝套件 setasign/fpdi 與 setasign/fpdf
指令:composer create-project --prefer-dist laravel/laravel laravel12PdfMerge
cd laravel12PdfMerge
指令:composer require setasign/fpdi
指令:composer require setasign/fpdf
五、建立Controller
指令:php artisan make:controller PdfMergeController
編輯 app/Http/Controllers/PdfMergeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use setasign\Fpdi\Fpdi;
class PdfMergeController extends Controller
{
public function index(){
return view('pdf-merge');
}
public function upload(Request $request){
$request->validate([
'files.*' => 'required|mimes:pdf'
]);
$uploaded = [];
foreach ($request->file('files') as $file) {
$path = $file->store('pdfs');
$uploaded[] = [
'name' => $file->getClientOriginalName(),
'path' => $path
];
}
return response()->json($uploaded);
}
public function merge(Request $request){
$paths = $request->input('paths');
$pdf = new FPDI();
foreach ($paths as $path) {
$filePath = Storage::path($path);
$pages = $pdf->setSourceFile($filePath);
for ($i = 1; $i <= $pages; $i++) {
$tpl = $pdf->importPage($i);
$size = $pdf->getTemplateSize($tpl);
$pdf->AddPage(
$size['orientation'],
[$size['width'], $size['height']]
);
$pdf->useTemplate($tpl);
}
}
$output = storage_path('app/merged.pdf');
$pdf->Output($output, 'F');
return response()->download($output)->deleteFileAfterSend();
}
}
六、建立 View
編輯 resources/views/pdf-merge.blade.php
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>Laravel 12 PDF Merge</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
li { padding: 8px; border: 1px solid #ccc; margin: 5px 0; cursor: move; }
#status { margin-top: 15px; color: #555; }
</style>
</head>
<body>
<h2>PDF Merge Tool</h2>
<input type="file" id="fileInput" multiple accept="application/pdf">
<br><br>
<button id="uploadBtn">Upload PDFs</button>
<button id="mergeBtn" disabled>Merge & Download</button>
<ul id="fileList"></ul>
<div id="status"></div>
<script>
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const mergeBtn = document.getElementById('mergeBtn');
const list = document.getElementById('fileList');
const status = document.getElementById('status');
const csrf = document.querySelector('meta[name="csrf-token"]').content;
let uploadedFiles = [];
new Sortable(list, { animation: 150 });
// 只選檔,不上傳
fileInput.addEventListener('change', () => {
uploadBtn.disabled = fileInput.files.length === 0;
status.innerText = "";
});
// 點「上傳」
uploadBtn.addEventListener('click', async () => {
const form = new FormData();
[...fileInput.files].forEach(f => form.append('files[]', f));
uploadBtn.disabled = true;
mergeBtn.disabled = true;
status.innerText = "Uploading...";
const res = await fetch('/upload', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrf },
body: form
});
if (!res.ok) {
const text = await res.text();
status.innerText = "Upload failed: " + text;
uploadBtn.disabled = false;
return;
}
uploadedFiles = await res.json();
render();
mergeBtn.disabled = uploadedFiles.length === 0;
uploadBtn.disabled = false;
status.innerText = "Upload completed.";
});
// 顯示清單
function render() {
list.innerHTML = '';
uploadedFiles.forEach(f => {
const li = document.createElement('li');
li.textContent = f.name;
li.dataset.path = f.path;
list.appendChild(li);
});
}
// 合併下載
mergeBtn.addEventListener('click', async () => {
const paths = [...list.children].map(li => li.dataset.path);
mergeBtn.disabled = true;
status.innerText = "Merging...";
const res = await fetch('/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrf
},
body: JSON.stringify({ paths })
});
if (!res.ok) {
const error = await res.text();
status.innerText = "Merge failed:\n" + error;
mergeBtn.disabled = false;
return;
}
const blob = await res.blob();
const pdfBlob = new Blob([blob], { type: 'application/pdf' });
const url = URL.createObjectURL(pdfBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'merged.pdf';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
status.innerText = "Download completed.";
mergeBtn.disabled = false;
});
</script>
</body>
</html>
七、編輯 Routes
編輯 routes/web.php
<?php
use App\Http\Controllers\PdfMergeController;
use Illuminate\Support\Facades\Route;
Route::get('/', [PdfMergeController::class, 'index']);
Route::post('/upload', [PdfMergeController::class, 'upload']);
Route::post('/merge', [PdfMergeController::class, 'merge']);
沒有留言:
張貼留言