You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何整合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}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                    <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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:52:02