人人都会AI编程

28.1 后台管理系统:权限路由、表格分页、表单联动

更新时间:2026-07-10

后台管理系统是 React 技术栈最经典的应用场景之一,绝大多数前端工程师都会在工作中遇到这类需求。下面围绕权限路由、表格分页、表单联动三个核心模块,给出可以直接落地的实现方案。

权限路由:根据角色动态控制菜单与页面访问

后台系统通常存在不同角色(超级管理员、编辑、普通用户等),不同角色看到不同的侧边栏菜单,并且无权访问的页面需要拦截。

核心思路

  1. 定义路由表,为每个路由配置所需的权限标识(如 admineditor)。
  2. 用户登录后存储角色信息(通常放在全局状态或本地缓存)。
  3. 侧边栏菜单根据权限过滤路由表生成可看到的菜单项。
  4. 路由守卫组件(或高阶组件)在页面渲染前检查权限,无权限则重定向到 403 或首页。

路由表定义(React Router v6 数据路由风格)

// routes.js
export const routes = [
  {
    path: '/dashboard',
    element: <Dashboard />,
    meta: { title: '仪表盘', roles: ['admin', 'editor'] },
  },
  {
    path: '/users',
    element: <UserList />,
    meta: { title: '用户管理', roles: ['admin'] },
  },
  {
    path: '/settings',
    element: <Settings />,
    meta: { title: '系统设置', roles: ['admin', 'editor'] },
  },
  {
    path: '/403',
    element: <Forbidden />,
    meta: { hidden: true }, // 不在菜单中显示
  },
  {
    path: '*',
    element: <Navigate to="/dashboard" replace />,
  },
];

权限守卫组件

// AuthGuard.jsx
import { useAuth } from '@/hooks/useAuth';
import { Navigate, useLocation } from 'react-router-dom';

function AuthGuard({ children, requiredRoles }) {
  const { user } = useAuth(); // 从全局获取当前用户信息(含角色)
  const hasPermission = requiredRoles
    ? requiredRoles.some(role => user.roles.includes(role))
    : true;

  if (!hasPermission) {
    return <Navigate to="/403" replace />;
  }
  return children;
}

将需要权限保护的页面用 AuthGuard 包裹:

<Route
  path="/users"
  element={
    <AuthGuard requiredRoles={['admin']}>
      <UserList />
    </AuthGuard>
  }
/>

动态菜单生成

// Sidebar.jsx
import { useAuth } from '@/hooks/useAuth';
import { routes } from '@/routes';
import { NavLink } from 'react-router-dom';

function Sidebar() {
  const { user } = useAuth();
  const visibleRoutes = routes.filter(
    route => !route.meta?.hidden && route.meta?.roles?.some(r => user.roles.includes(r))
  );

  return (
    <nav>
      {visibleRoutes.map(route => (
        <NavLink to={route.path} key={route.path}>
          {route.meta.title}
        </NavLink>
      ))}
    </nav>
  );
}

这套方案实现了完整的权限路由闭环:既控制了菜单显示,也防止了直接输入 URL 越权访问。

表格分页:前后端配合的数据展示器

后台管理系统中表格是数据展示的主力,通常需要配合分页、排序、筛选功能。下面给出一个结合 React Hooks 和通用表格组件(如 Ant Design 或自定义)的分页逻辑封装。

封装一个请求 Hook

// useTableData.js
import { useState, useEffect } from 'react';

function useTableData(apiFn, defaultParams = {}) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [pagination, setPagination] = useState({
    current: 1,
    pageSize: 10,
    total: 0,
  });
  const [filters, setFilters] = useState({});

  const fetchData = async () => {
    setLoading(true);
    try {
      const { data: res } = await apiFn({
        page: pagination.current,
        pageSize: pagination.pageSize,
        ...filters,
      });
      setData(res.list);
      setPagination(prev => ({ ...prev, total: res.total }));
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [pagination.current, pagination.pageSize, filters]);

  const handleTableChange = (pagination, filtersFromUI) => {
    if (pagination) {
      setPagination(prev => ({
        ...prev,
        current: pagination.current || prev.current,
        pageSize: pagination.pageSize || prev.pageSize,
      }));
    }
    setFilters(filtersFromUI || {});
  };

  return { data, loading, pagination, fetchData, handleTableChange };
}

在组件中使用

function UserList() {
  const { data, loading, pagination, handleTableChange } = useTableData(fetchUsersAPI);

  const columns = [
    { title: '用户名', dataIndex: 'username' },
    { title: '邮箱', dataIndex: 'email' },
    // ...
  ];

  return (
    <Table
      dataSource={data}
      columns={columns}
      loading={loading}
      pagination={{
        current: pagination.current,
        pageSize: pagination.pageSize,
        total: pagination.total,
      }}
      onChange={handleTableChange}
    />
  );
}

核心要点

  • 后端必须返回总数,分页才有意义。
  • 翻页、筛选等变化都触发同一个 fetchData,避免状态分散导致多请求。
  • 取消竞态:如果数据请求较慢,用户快速切分页时可能产生覆盖问题。在 useEffect 的清理函数中使用 AbortController 或在 fetchData 内用标志位忽略过期响应。

表单联动:省市区选择、条件动态校验等

后台表单常出现字段间的联动逻辑,例如“选择省份后加载城市列表”、“选择某个选项后显示额外的输入框”。以下是几个典型场景的实现。

场景一:级联选择(省份→城市)

function RegionSelector() {
  const [provinces, setProvinces] = useState([]);
  const [cities, setCities] = useState([]);
  const [selectedProvince, setSelectedProvince] = useState(null);
  const [selectedCity, setSelectedCity] = useState(null);

  useEffect(() => {
    fetchProvinces().then(setProvinces);
  }, []);

  // 监听省份变化,加载对应城市
  useEffect(() => {
    if (selectedProvince) {
      fetchCities(selectedProvince.code).then(setCities);
    } else {
      setCities([]);
      setSelectedCity(null);
    }
  }, [selectedProvince]);

  return (
    <>
      <Select
        options={provinces}
        value={selectedProvince}
        onChange={val => setSelectedProvince(val)}
      />
      <Select
        options={cities}
        value={selectedCity}
        onChange={val => setSelectedCity(val)}
        disabled={!selectedProvince}
      />
    </>
  );
}

场景二:条件显示字段

“是否开发票”选择“是”时,才显示发票抬头输入框。

function OrderForm() {
  const [needInvoice, setNeedInvoice] = useState(false);

  return (
    <Form>
      <Form.Item label="是否开发票">
        <Switch checked={needInvoice} onChange={setNeedInvoice} />
      </Form.Item>
      {needInvoice && (
        <Form.Item label="发票抬头" name="invoiceTitle">
          <Input />
        </Form.Item>
      )}
    </Form>
  );
}

场景三:动态校验规则

发票抬头在选择开发票时为必填,否则非必填。使用 React Hook Form 的 watch 动态设定校验:

import { useForm, Controller } from 'react-hook-form';

function OrderForm() {
  const { control, watch } = useForm();
  const needInvoice = watch('needInvoice');

  return (
    <form>
      <Controller
        name="needInvoice"
        control={control}
        render={({ field }) => <Switch {...field} />}
      />
      <Controller
        name="invoiceTitle"
        control={control}
        rules={{ required: needInvoice ? '请填写发票抬头' : false }}
        render={({ field, fieldState }) => (
          <Input {...field} placeholder={needInvoice ? '必填' : '可选'} />
        )}
      />
    </form>
  );
}

综合实战建议

  1. 权限路由:建议结合 React Router v6 的 loader + redirect 在路由层做权限验证,避免组件闪烁。同时菜单和路由配置可以放在后端,实现动态下发。
  2. 表格分页:尽量将分页参数与 URL query 同步(useSearchParams),这样用户刷新页面后仍能停留在当前分页状态,体验更好。
  3. 表单联动:对于复杂的业务表单,优先使用 React Hook Form 等高性能表单库,其 watchsetValue 等 API 能优雅处理联动逻辑。

这三个模块看似独立,实际上一套后台系统通常需要将它们组合使用:权限控制菜单和页面,表格分页展示数据,表单联动处理新增/编辑的交互。掌握了这些基础模式,就能应对八成以上的后台开发需求。