2026年1月24日 星期六

在本機 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']);






沒有留言:

張貼留言

在本機 laravel 12 ,上傳很多PDF,上傳之後可以自己排序,合併之後下載-方法二

系列文章: 1. 利用 laravel 12 做一個類似Google文件的簡單範例 2. 在本機Laravel 12 中實作 Google OAuth2 登入 3. 如何在 Laravel 12 使用 Gmail SMTP 寄信 4. 在本機Laravel 12 中實作 Face...