React权限管理

63 阅读3分钟

假设myPage002是没有权限的 react-project/src/route/index.jsx 定义路由

import { createBrowserRouter, Navigate } from "react-router-dom";
import { lazy } from "react";
import RequireAuth from "./RequireAuth";

// 动态导入组件
const MyPage001 = lazy(() => import("../views/MyPage001"));
const MyPage002 = lazy(() => import("../views/MyPage002"));
const MyPage003 = lazy(() => import("../views/MyPage003"));
const NotFound = lazy(() => import("../components/NotFound"));
const Layout = lazy(() => import("../views/Layout"));
const Login = lazy(() => import("../views/Login"));
const Home = lazy(() => import("../views/Home"));

const staticRoutes = [
  {
    path: "/login",
    element: <Login />,
    meta: {
      label: "登录",
    },
  },
];
const routeConfig = [
  {
    path: "/",
    element: <Layout />,
    meta: {
      label: "主页",
    },
    children: [
      {
        index: true,
        element: <Navigate replace to="/home" />,
        meta: {
          replace: true,
        },
      },
      {
        path: "home",
        element: <Home />,
        meta: {
          label: "主页",
          first: true,
        },
      },
    ],
  },
  {
    path: "/fenxi",
    element: <Layout />,
    meta: {
      label: "分析",
    },
    children: [
      {
        index: true,
        element: <Navigate replace to="/fenxi/myPage001" />,
        meta: {
          replace: true,
        },
      },
      {
        path: "myPage001",
        element: <MyPage001 />,
        meta: {
          label: "分析一",
        },
      },
      {
        path: "myPage002",
        element: <RequireAuth><MyPage002 /></RequireAuth>,
        meta: {
          label: "分析二",
        },
      },
      {
        path: "myPage003",
        element: <RequireAuth><MyPage003 /></RequireAuth>,
        meta: {
          label: "分析三",
        },
      },
    ],
  },
];
const routerConfig = createBrowserRouter(
  [
    ...staticRoutes,
    ...routeConfig,
    {
      path: "*",
      element: <NotFound />,
    },
  ],
  {
    basename: "/",
  }
);

 

export { routeConfig ,staticRoutes};
export default routerConfig;

react-project/src/route/RequireAuth.jsx 定义权限,包裹路由页面,如上面的myPage002。

// RequireAuth.js
import { Navigate, useLocation } from "react-router-dom";

function RequireAuth({ children }) {
  const location = useLocation();

  if (location.pathname === "/fenxi/myPage002") {
    // 直接去404
    return <Navigate to="/404" />;
    // 权限不足则显示无权限页面
    // return <div>无权限访问</div>;
  }

  return children;
}

export default RequireAuth;

/react-project/src/App.jsx 路由出口

import {  Suspense, useEffect } from "react";
import { RouterProvider } from "react-router-dom";
import routerConfig from "./route";

function App() {
  // const [routers, setRouters] = useState(routerConfig);
  return (
    <>
      <Suspense fallback={<div>加载中...</div>}>
        <RouterProvider router={routerConfig} />
      </Suspense>
    </>
  );
}

export default App;

react-project/src/views/Layout.jsx 左侧菜单

通过getItems将只有一层的子菜单提到主菜单,比如:/home

过滤无权限菜单项,比如filterMenuItems移除myPage002


import React, { useState } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import { routeConfig } from "../route/index";
import {
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  PieChartOutlined,
} from "@ant-design/icons";
import { Button, Menu } from "antd";
// function getItems(_routeConfig, items = []) {
//   _routeConfig.forEach((item) => {
//     if(item?.meta?.replace){
//         return
//     }
//     const _item = {
//       key: item.path,
//       label: item?.meta?.title,
//       icon: <PieChartOutlined />,
//     };
//     items.push(_item);
//     if (item?.children) {
//       _item.children = getItems(item?.children);
//     }
//   });
//   return items;
// }
// [...routeConfig].forEach(item=>{
//     if(item?.meta?.first){
//         item.path = item.path.split('/').pop()
//     }
// })

function getItems(_routes) {
  const items = [];
  _routes.forEach((item) => {
    if (
      item.children &&
      item?.children?.filter((v) => !v.meta?.replace).length === 1
    ) {
      const _item = item.children[1];
      _item.path = item.path + _item.path;
      _item.label = item.meta.label;
      _item.key = _item.path;
      _item.icon = <PieChartOutlined />;
      items.push(_item);
    } else if (
      item?.children &&
      item?.children?.filter((v) => !v.meta?.replace).length > 1
    ) {
      const _child = item.children.filter((v) => !v.meta?.replace);
      item.label = item.meta.label;
      item.key = item.path;
      item.icon = <PieChartOutlined />;
      item.children = _child.map((v) => {
        return {
          ...v,
          label: v.meta.label,
          path: item.path + "/" + v.path,
          key: item.path + "/" + v.path,
          icon: <PieChartOutlined />,
        };
      });

      items.push(item);
    }
  });

  return items;
}

/**
 * 过滤菜单项,移除指定路径的项
 * @param {Array} items - 菜单项数组
 * @param {string|Array} excludePaths - 要排除的路径(字符串或数组)
 * @returns {Array} 过滤后的菜单项
 */
function filterMenuItems(items, excludePaths = '/fenxi/myPage002') {
  // 统一处理排除路径,支持字符串或数组
  const excludeList = Array.isArray(excludePaths) ? excludePaths : [excludePaths];
  
  return items
    .filter(item => !excludeList.includes(item.path)) // 过滤掉指定路径
    .map(item => {
      // 如果有子菜单,递归过滤
      if (item.children && item.children.length > 0) {
        const filteredChildren = filterMenuItems(item.children, excludePaths);
        
        // 如果过滤后还有子菜单,保留children
        if (filteredChildren.length > 0) {
          return {
            ...item,
            children: filteredChildren
          };
        } else {
          // 如果过滤后没有子菜单,删除children属性
          // eslint-disable-next-line no-unused-vars
          const { children, ...itemWithoutChildren } = item;
          return itemWithoutChildren;
        }
      }
      
      return item;
    });
}

const items = getItems(routeConfig);

// 使用示例:
// 1. 过滤单个路径
const filteredItems = filterMenuItems(items, '/fenxi/myPage002');

// 2. 过滤多个路径
// const filteredItems = filterMenuItems(items, ['/fenxi/myPage002', '/fenxi/myPage003']);

// 3. 不过滤任何路径(显示所有菜单)
// const filteredItems = filterMenuItems(items, []);

console.log("原始菜单项:", items);
console.log("过滤后的菜单项:", filteredItems);

// const items = [
//   { key: "1", icon: <PieChartOutlined />, label: "Option 1" },
//   { key: "2", icon: <DesktopOutlined />, label: "Option 2" },
//   { key: "3", icon: <ContainerOutlined />, label: "Option 3" },
//   {
//     key: "sub1",
//     label: "Navigation One",
//     icon: <MailOutlined />,
//     children: [
//       { key: "5", label: "Option 5" },
//       { key: "6", label: "Option 6" },
//       { key: "7", label: "Option 7" },
//       { key: "8", label: "Option 8" },
//     ],
//   },
//   {
//     key: "sub2",
//     label: "Navigation Two",
//     icon: <AppstoreOutlined />,
//     children: [
//       { key: "9", label: "Option 9" },
//       { key: "10", label: "Option 10" },
//       {
//         key: "sub3",
//         label: "Submenu",
//         children: [
//           { key: "11", label: "Option 11" },
//           { key: "12", label: "Option 12" },
//         ],
//       },
//     ],
//   },
// ];
const Layout = () => {
  const navigate = useNavigate();
  const [collapsed, setCollapsed] = useState(false);
  const toggleCollapsed = () => {
    setCollapsed(!collapsed);
  };
  return (
    <div style={{ width: 256, display: "flex" }}>
      <div>
        <Button
          type="primary"
          onClick={toggleCollapsed}
          style={{ marginBottom: 16 }}
        >
          {collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
        </Button>
        <Menu
          defaultSelectedKeys={["/home"]}
          defaultOpenKeys={["/home"]}
          mode="inline"
          theme="dark"
          inlineCollapsed={collapsed}
          onClick={(item) => {
            console.log(item);
            //跳转路由
            navigate(item.key);
          }}
          items={filteredItems}
        />
      </div>
      <div>
        <Outlet />
      </div>
    </div>
  );
};
export default Layout;

image.png