render() 視圖片段返回photos = $photos;
}
// 頁面入口
public function index() {
$photos = $this->photos->paginate(12);
return view('photos.index', compact('photos'));
}
// AJAX 分頁接口
public function ajaxList(Request $request) {
$page = $request->input('page', 1);
$photos = $this->photos->paginate(12, ['*'], 'page', $page);
if ($request->ajax()) {
// 僅返回列表片段 + 分頁鏈接
$html = view('photos._list', ['photos' => $photos])->render();
$pagination = $photos->links('vendor.pagination.bootstrap-4')->render();
return response()->json([
'html' => $html,
'pagination' => $pagination,
'current_page' => $photos->currentPage(),
'last_page' => $photos->lastPage()
]);
}
return view('photos.index', compact('photos'));
}
}
resources/views/ ├── photos/ │ ├── index.blade.php # 主頁面 │ ├── _list.blade.php # 列表片段(AJAX 返回) │ └── _item.blade.php # 單項片段(可選)
@extends('layouts.app')
@section('content')
照片庫
@include('photos._list', ['photos' => $photos])
{!! $photos->links('vendor.pagination.bootstrap-4') !!}
@endsection
@push('scripts')
@endpush
@foreach($photos as $photo)@endforeach![]()
{{ $photo->title }}
// public/js/photos.js
(function() {
const $ = window.jQuery;
const app = window.PhotosApp;
let loading = false;
// 初始化:綁定分頁鏈接點擊事件
$(document).on('click', '#pagination-container a.page-link', function(e) {
e.preventDefault();
if (loading) return;
const url = $(this).attr('href');
const page = getPageFromUrl(url);
if (page) loadPage(page);
});
// 從 URL 解析頁碼
function getPageFromUrl(url) {
const match = url.match(/[?&]page=(\d+)/);
return match ? parseInt(match[1]) : null;
}
// 載入指定頁面
function loadPage(page) {
loading = true;
showLoading();
$.ajax({
url: app.baseUrl,
method: 'GET',
data: { page: page },
dataType: 'json',
success: function(response) {
// 更新列表
$('#photos-list').html(response.html);
// 更新分頁
$('#pagination-container').html(response.pagination);
// 更新狀態
app.currentPage = response.current_page;
app.lastPage = response.last_page;
// 更新瀏覽器歷史(支援前進後退)
const newUrl = updateUrlParameter(window.location.href, 'page', page);
history.pushState({ page: page }, '', newUrl);
hideLoading();
},
error: function(xhr) {
console.error('Load failed:', xhr);
alert('加載失敗,請重試');
hideLoading();
},
complete: function() {
loading = false;
}
});
}
// 瀏覽器前進後退支援
window.addEventListener('popstate', function(e) {
if (e.state && e.state.page) {
loadPage(e.state.page);
}
});
// URL 參數更新工具
function updateUrlParameter(url, param, value) {
const urlObj = new URL(url);
urlObj.searchParams.set(param, value);
return urlObj.toString();
}
function showLoading() {
$('#photos-list').addClass('opacity-50');
// 可添加 spinner
}
function hideLoading() {
$('#photos-list').removeClass('opacity-50');
}
// 初始化
$(function() {
// 綁定選擇按鈕等業務邏輯
$(document).on('click', '.select-photo', function() {
const id = $(this).data('id');
// 業務處理...
});
});
})();
// routes/web.php
Route::get('/photos', 'PhotoController@index')->name('photos.index');
Route::get('/photos/ajax', 'PhotoController@ajaxList')->name('photos.ajax');
// 替換分頁點擊為滾動加載
let loading = false;
$(window).on('scroll', function() {
if (loading) return;
if ($(window).scrollTop() + $(window).height() > $(document).height() - 200) {
if (app.currentPage < app.lastPage) {
loadPage(app.currentPage + 1);
}
}
});
// 修改 loadPage 追加而非替換
function loadPage(page) {
// ...
success: function(response) {
if (page === 1) {
$('#photos-list').html(response.html);
} else {
$('#photos-list').append(response.html); // 追加模式
}
// ...
}
}
| 實踐 | 說明 |
|---|---|
| History API | pushState 保持 URL 同步,支援分享、刷新、前進後退 |
| 片段渲染 | 後端僅返回 _list 片段,減少傳輸體積 |
| Loading 狀態 | 防止重複請求,提升體驗 |
| 錯誤處理 | 網絡異常友好提示,不阻塞頁面 |
| CSRF Token | Laravel 自動驗證 AJAX 請求頭 X-CSRF-TOKEN |
render() 輕鬆輸出片段這套方案零依賴第三方分頁插件,完全掌控前後端交互細節,是 Laravel 專案實現無刷新分頁的標準最佳實踐。