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

如何在Material UI Table中根据搜索值自动跳转到对应结果页

实现方案

你现在的场景是服务端分页,每次接口仅返回当前页的表格数据,要实现搜索值自动跳转页码,有两种可行方案,优先推荐第一种:

方案1:后端接口支持搜索(最优)

让后端给列表接口新增搜索参数,你把搜索关键词value传给后端,后端直接返回匹配的列表数据、以及对应的分页信息,前端直接用返回的分页信息渲染即可,不需要前端遍历翻页查询,性能最高。

方案2:前端轮询翻页查询(仅适合小数据量场景)

如果暂时无法修改后端接口,可以用前端逐页查询的方式实现,核心逻辑是搜索关键词变化时从第一页开始逐页检查是否有匹配项,查到就停在当前页,查完所有页没找到就提示无匹配。

代码修改点:

  1. 先修正分页操作组件的跳转逻辑,你原来的下一页逻辑是跳2页,属于bug,先修复
  2. 新增监听搜索词和接口返回的副作用,实现自动翻页逻辑
  3. 简化过滤逻辑

修改后完整代码:

import React, { useState, useEffect } from "react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import useFetch from "./custom_hooks/useFetch";
import TableFooter from "@mui/material/TableFooter";
import TablePagination from "@mui/material/TablePagination";
import IconButton from "@mui/material/IconButton";
import Box from "@mui/material/Box";
import { MdKeyboardArrowRight, MdKeyboardArrowLeft } from "react-icons/md";
import styled from "styled-components";

const TableH = styled(TableHead)`
  height: 3.5rem;
`;

const TableB = styled(TableBody)`
  height: 3.375rem;
`;

const RoleButton = styled.button`
  color: #c80539;
  outline: none;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: normal;
  font-stretch: normal;
  font-style: normal;
  line-height: 1.43;
  letter-spacing: normal;
`;

function TablePaginationActions(props) {
  const { page, count, rowsPerPage, onPageChange } = props;
  // 计算总页数
  const totalPages = Math.ceil(count / rowsPerPage) - 1;

  const handleBackButtonClick = (event) => {
    onPageChange(event, page - 1);
  };

  const handleNextButtonClick = (event) => {
    onPageChange(event, page + 1);
  };

  return (
    <Box sx={{ flexShrink: 0, ml: 2.5 }}>
      <IconButton
        onClick={handleBackButtonClick}
        disabled={page === 0}
        aria-label="previous page"
      >
        <MdKeyboardArrowLeft color="#707070" />
      </IconButton>
      <IconButton 
        onClick={handleNextButtonClick} 
        disabled={page >= totalPages}
        aria-label="next page"
      >
        <MdKeyboardArrowRight color="#707070" />
      </IconButton>
    </Box>
  );
}

const TableComponent = ({ url, cellNames, value }) => {
  const [page, setPage] = useState(1);
  const [rowsPerPage, setRowsPerPage] = useState(5);
  const [roles, setRoles] = useState("Admin");
  // 搜索状态标记,避免非搜索场景触发自动翻页
  const [isSearching, setIsSearching] = useState(false);
  // 如果后端支持搜索参数,直接把value拼到url里,性能更好
  const { data, error } = useFetch(`${url}${page}`);

  if (!data) {
    return <h1> LOADING...</h1>;
  }

  if (error) {
    console.log(error);
  }

  const totalCount = data.meta.pagination.total;
  const totalPages = data.meta.pagination.pageCount;

  // 监听搜索关键词变化,初始化搜索逻辑
  useEffect(() => {
    // 搜索词为空时重置到第一页,停止搜索
    if (!value.trim()) {
      setPage(1);
      setIsSearching(false);
      return;
    }
    // 新搜索开始,从第一页开始查
    setPage(1);
    setIsSearching(true);
  }, [value]);

  // 监听接口返回结果,判断是否需要继续翻页
  useEffect(() => {
    // 非搜索状态、无数据、无搜索词时不执行逻辑
    if (!isSearching || !data || !value.trim()) return;

    const lowerCaseValue = value.toLowerCase();
    // 检查当前页是否有匹配项
    const hasMatch = data.data.some(item => 
      item.name.toLowerCase().includes(lowerCaseValue)
    );

    if (hasMatch) {
      // 找到匹配项,停止搜索
      setIsSearching(false);
      return;
    }

    // 没找到匹配项,还有下一页就继续查
    if (page < totalPages) {
      setPage(prevPage => prevPage + 1);
    } else {
      // 所有页查完无匹配,停止搜索
      setIsSearching(false);
      alert("未找到匹配的内容");
    }
  }, [data, isSearching, value, page, totalPages]);

  const handleChangePage = (event, newPage) => {
    // MUI分页返回的newPage从0开始,转成我们的从1开始的页码
    setPage(newPage + 1);
  };

  const handleChangeRowsPerPage = (event) => {
    setRowsPerPage(parseInt(event.target.value, 10));
    setPage(1);
  };

  // 注意:你原来的角色切换逻辑是全局的,会改所有行的角色,要按行区分的话需要存每行的角色状态,这里暂时保留原逻辑
  const handleRoleChange = () => {
    setRoles(prev => prev === "Admin" ? "User" : "Admin");
  };

  // 过滤当前页数据
  const lowerCaseValue = value.toLowerCase();
  const filteredData = value.trim() 
    ? data.data.filter(item => item.name.toLowerCase().includes(lowerCaseValue))
    : data.data;

  return (
    <TableContainer
      style={{
        height: 385,
        border: "1px solid #e2e2e3",
        borderRadius: 5,
      }}
      data-testid="table-container"
    >
      <Table stickyHeader>
        <TableH>
          <TableRow>
            {cellNames.map((names, index) => {
              return (
                <TableCell
                  style={{
                    fontWeight: "bold",
                    fontSize: 14,
                    paddingRight: names.paddingRight,
                    paddingLeft: names.paddingLeft,
                  }}
                  align={names.align}
                  key={index}
                >
                  {names.name}
                </TableCell>
              );
            })}
          </TableRow>
        </TableH>
        <TableB>
          {filteredData
            .slice(0, rowsPerPage)
            .map((apiData, index) => {
              return (
                <TableRow key={index}>
                  <TableCell align="left" style={{ paddingLeft: 40 }}>
                    {apiData.name}
                  </TableCell>
                  <TableCell align="left">{apiData.email}</TableCell>
                  <TableCell align="left">{apiData.status}</TableCell>
                  <TableCell align="left">{roles}</TableCell>
                  <TableCell align="right" style={{ paddingRight: 40 }}>
                    <RoleButton onClick={handleRoleChange}>
                      {roles === "Admin" ? "Set as User" : "Set as Admin"}
                    </RoleButton>
                  </TableCell>
                </TableRow>
              );
            })}
        </TableB>
        <TableFooter>
          <TableRow>
            <TablePagination
              rowsPerPageOptions={[5, 10]}
              colSpan={6}
              style={{
                borderBottom: "none",
                height: 50,
                color: "#707070",
              }}
              count={totalCount}
              rowsPerPage={rowsPerPage}
              page={page - 1}
              SelectProps={{
                inputProps: {
                  "aria-label": "rows per page",
                },
                native: true,
              }}
              onPageChange={handleChangePage}
              onRowsPerPageChange={handleChangeRowsPerPage}
              ActionsComponent={(props) => <TablePaginationActions {...props} count={totalCount} rowsPerPage={rowsPerPage} />}
            />
          </TableRow>
        </TableFooter>
      </Table>
    </TableContainer>
  );
};

export default TableComponent;

注意事项

  • 方案2如果数据量很大(超过10页)会发起大量请求,建议优先选择后端支持搜索的方案
  • 你原有的角色切换逻辑是全局统一的,所有行的角色会同步变化,如果需要按行单独设置,需要把roles改成数组,存每行对应的角色状态

内容的提问来源于stack exchange,提问作者ahmedskulj10

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 07:54:01