如何在Material UI Table中根据搜索值自动跳转到对应结果页
实现方案
你现在的场景是服务端分页,每次接口仅返回当前页的表格数据,要实现搜索值自动跳转页码,有两种可行方案,优先推荐第一种:
方案1:后端接口支持搜索(最优)
让后端给列表接口新增搜索参数,你把搜索关键词value传给后端,后端直接返回匹配的列表数据、以及对应的分页信息,前端直接用返回的分页信息渲染即可,不需要前端遍历翻页查询,性能最高。
方案2:前端轮询翻页查询(仅适合小数据量场景)
如果暂时无法修改后端接口,可以用前端逐页查询的方式实现,核心逻辑是搜索关键词变化时从第一页开始逐页检查是否有匹配项,查到就停在当前页,查完所有页没找到就提示无匹配。
代码修改点:
- 先修正分页操作组件的跳转逻辑,你原来的下一页逻辑是跳2页,属于bug,先修复
- 新增监听搜索词和接口返回的副作用,实现自动翻页逻辑
- 简化过滤逻辑
修改后完整代码:
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
相关产品推荐
相关产品推荐

