如何整合Laravel分页API(SQL)与React并修改自定义分页
整合Laravel分页API与React前端分页表格
一、Laravel后端修改
1. 更新FruitController的index方法
将原本返回全量数据的逻辑改为返回分页数据,同时保留搜索过滤功能,并关联营养数据:
public function index(Request $request) { $name = $request->input('name'); $family = $request->input('family'); $page = $request->input('page', 1); // 获取当前页码,默认1 $perPage = $request->input('per_page', 5); // 每页数量,默认5 $query = Fruit::with("nutritions"); if ($name) { $query->where('name', 'LIKE', '%' . $name . '%'); } if ($family) { $query->where('family', 'LIKE', '%' . $family . '%'); } $fruits = $query->paginate($perPage, ['*'], 'page', $page); return $this->sendResponse(FruitResource::collection($fruits), 'Fruits retrieved successfully.'); }
2. 清理冗余API路由
api.php中存在两个/fruits路由,删除直接返回全量数据的冗余路由,保留资源路由即可:
// 删除这条路由 // Route::get('/fruits', function(){ // $fruits = Fruit::get(); // return response()->json($fruits); // });
Laravel分页API返回的结构示例:
{ "success": true, "data": [/* 水果数据数组 */], "message": "...", "meta": { "current_page": 1, "last_page": 10, "per_page": 5, "total": 48 } }
二、React前端修改
1. 更新状态与请求逻辑
修改Design.jsx,移除前端分页逻辑,改为从后端获取分页数据,并存储分页元信息:
import "../src/App.css"; import axios from "axios"; import { useEffect, useState } from "react"; import Pagination from "./Pagination"; import { Link } from "react-router-dom"; export default function Design() { const [searchTerm, setSearchTerm] = useState(""); const [records, setRecords] = useState([]); const [suggestions, setSuggestions] = useState([]); // 新增分页相关状态 const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalCount, setTotalCount] = useState(0); const pageSize = 5; // 和后端默认保持一致 const localData = () => { const data = window.localStorage.getItem("pnum"); return data ? JSON.parse(data) : 1; }; useEffect(() => { const savedPage = localData(); setCurrentPage(savedPage); fetchFruits(savedPage); }, []); // 替换原searchRecords,改为带分页和搜索参数的请求 const fetchFruits = async (page = 1) => { try { const params = new URLSearchParams(); params.append('page', page); params.append('per_page', pageSize); if (searchTerm) { params.append('name', searchTerm); } const res = await axios.get(`http://localhost:8000/api/fruits`, { params }); setRecords(res.data.data); // 从后端返回的meta中获取分页信息 setTotalPages(res.data.meta.last_page); setTotalCount(res.data.meta.total); setCurrentPage(page); window.localStorage.setItem("pnum", page); } catch (error) { console.error("Error fetching fruits:", error); setRecords([]); setTotalPages(1); setTotalCount(0); } }; const fetchSuggestions = async (input) => { try { const res = await axios.get(`https://fruityvice.com/api/fruit/all`); const fruits = res.data.map((fruit) => fruit.name); const filteredSuggestions = fruits.filter((fruit) => fruit.toLowerCase().includes(input.toLowerCase()) ); setSuggestions(filteredSuggestions); } catch (error) { console.error("Error fetching suggestions:", error); setSuggestions([]); } }; const handleInputChange = (e) => { const inputValue = e.target.value; setSearchTerm(inputValue); fetchSuggestions(inputValue); }; // 搜索按钮点击时,从第一页开始加载 const handleSearch = () => { fetchFruits(1); }; const getTotalNutritions = (item) => { return Object.values(item.nutritions).reduce( (total, value) => total + Math.trunc(value), 0 ); }; return ( <> <h1 className="main--heading">Fruit Project</h1> <div className="main"> <input className="input" onChange={handleInputChange} list="suggestions" placeholder="Search Fruit" value={searchTerm} /> <datalist id="suggestions"> {suggestions.map((fruit, index) => ( <option key={index} value={fruit} /> ))} </datalist> <button className="btn" onClick={handleSearch}> Search Fruit </button> </div> <div> <table> <thead> <tr> <th>Sr No</th> <th>Name</th> <th>Family</th> <th>Order</th> <th>Nutritions</th> <th>Genus</th> </tr> </thead> <tbody> {records.length > 0 ? ( records.map((item) => ( <tr key={item.id}> <td>{item.id}</td> <td> {item.name} <Link to={`/details/${item.id}`}>View Details</Link> </td> <td>{item.family}</td> <td>{item.order}</td> <td>{getTotalNutritions(item)}</td> <td>{item.genus}</td> </tr> )) ) : ( <tr> <td colSpan="6">No data available</td> </tr> )} </tbody> </table> <Pagination className="pagination-bar" currentPage={currentPage} totalCount={totalCount} pageSize={pageSize} totalPages={totalPages} onPageChange={(page) => { fetchFruits(page); }} /> </div> </> ); }
2. 适配自定义Pagination组件
确保Pagination组件能接收totalPages参数,基于后端返回的总页数渲染分页按钮:
export default function Pagination({ currentPage, totalPages, onPageChange, className }) { const pageNumbers = []; for (let i = 1; i <= totalPages; i++) { pageNumbers.push(i); } return ( <div className={className}> <button onClick={() => onPageChange(currentPage - 1)} disabled={currentPage === 1} > Previous </button> {pageNumbers.map(number => ( <button key={number} onClick={() => onPageChange(number)} className={currentPage === number ? 'active' : ''} > {number} </button> ))} <button onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages} > Next </button> </div> ); }
关键说明
- 后端用Laravel内置
paginate()自动处理分页,返回包含元数据的响应,无需手动计算参数 - 前端不再做本地分页,切换页码或搜索时请求对应页码的后端数据,保证数据一致性
- 搜索参数通过URL传递给后端,由数据库层面过滤,性能更优
- 分页状态持久化到localStorage,刷新页面后保留当前页码
内容的提问来源于stack exchange,提问作者Muhammad Sibtain
相关产品推荐
相关产品推荐

