react多级权限路由

技术栈:react-router-dom、react18、antd、vite-plugin-mock

1、配置mock

其他的配置我删了,主要是配置mock

import react from '@vitejs/plugin-react'
import viteESLintPlugin from 'vite-plugin-eslint'
import { loadEnv } from 'vite'
import { wrapperEnv } from './scripts/utils'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { viteMockServe } from 'vite-plugin-mock'
import { resolve } from 'path'
import type { ConfigEnv, UserConfig } from 'vite'

export default ({command,mode}: ConfigEnv): UserConfig =>{
  return {
    plugins: [
      react(),
      viteESLintPlugin({
        // 开发阶段因为 ESLint 的错误, 不再会打断开发
        failOnError: false
      }),
      viteMockServe({
        mockPath: './mock',
        ignore: /^_/,
        localEnabled: !isBuild,
        prodEnabled: isBuild,
      }),
    ],

  }
}

2、添加mock接口

在src同级下创建mock/user/index.ts

添加接口数据(模拟数据)

//用户信息数据
function createUserList() {
    return [
        {
            userId: 1,
            username: 'admin',
            password: '111111',
            token: 'Admin Token',
            permission: [{ name: 'home'},{ name: 'dashboard'},{ name: 'form', children: ['formBas', 'formDes']},{ name: 'table', children: ['tableBas', 'tableDes']}],
        },
        {
            userId: 2,
            username: 'system',
            password: '111111',
            token: 'System Token',
            permission: ['home', 'dashboard'],
        },
    ]
}
 
export default [
    // 用户登录接口
    {
        url: '/api/user/login',//请求地址
        method: 'post',//请求方式
        response: ({ body }) => {
            //获取请求体携带过来的用户名与密码
            const { username, password } = body;
            //调用获取用户信息函数,用于判断是否有此用户
            const checkUser = createUserList().find(
                (item) => item.username === username && item.password === password,
            )
            //没有token返回失败信息
            if (!checkUser) {
                return { code: 201, data: { message: '未登录' } }
            }
            //如果有返回成功信息
            const { token } = checkUser
            return { code: 200, data: { token } }
        },
    },
    // 获取用户信息
    {
        url: '/api/user/info',
        method: 'get',
        response: (request) => {
            //获取请求头携带token
            const token = request.headers.token;
            //查看用户信息是否包含有次token用户
            const checkUser = createUserList().find((item) => item.token === token)
            //没有返回失败的信息
            if (!checkUser) {
                return { code: 201, data: { message: '获取用户信息失败' } }
            }
            //如果有返回成功信息
            return { code: 200, data: {checkUser} }
        },
    },
    // 权限分配
    {
        url: '/api/user/permission',
        method: 'get',
        response: (request) => {
            //获取请求头携带token
            const token = request.headers.token;
            //查看用户信息是否包含有次token用户
            const checkUser = createUserList().find((item) => item.token === token)
            console.log(checkUser);
            //没有返回失败的信息
            if (!checkUser) {
                return { code: 201, data: { message: '获取失败' }}
            }
            return { code: 200, data: { permission: checkUser.permission} }
        }
    }
]

3、请求权限接口

在登录的时候请求权限接口,这里不操作,就一个请求而已,注意的是,请求的权限和token数据可以通过toolkit和react-redux进行状态管理(还是要借助数据存储,不然数据会丢失)

4、配置守卫路由

讲需要的路由进行添加,包括权限以及icon等需要的数据配置在meta中

router/route/index.tsx

import { Navigate } from "react-router-dom";
import React from "react";
import HomePage from "@/views/home/homePage";
import DashboardPage from "@/views/dashboard/dashboardPage";
import BasignerPage from "@/views/form/basicPage";
import DesigneerPage from "@/views/form/designerPage";
import TabBasicPage from "@/views/table/basicPage";
import TabDesignerPage from "@/views/table/designerPage";
import {
  PieChartOutlined,
  HomeOutlined,
  ContainerOutlined,
  FormOutlined,
  TableOutlined,
} from "@ant-design/icons";

const routes: Routes = [
  {
    path: "home",
    name: "首页",
    element: <HomePage />,
    meta: {
      title: "首页",
      icon: <HomeOutlined />,
      permission: ["home"],
    },
  },
  {
    path: "dashboard",
    name: "数据大屏",
    element: <DashboardPage />,
    meta: {
      title: "数据大屏",
      icon: <PieChartOutlined />,
      permission: ["dashboard"],
    },
  },
  {
    path: "*",
    element: <Navigate to="/home" />,
    name: "",
    meta: {
      title: "首页",
      icon: <HomeOutlined />,
      permission: ["home"],
    },
  },
  {
    path: "form",
    name: "表单",
    meta: {
      title: "表单",
      icon: <FormOutlined />,
      permission: ["form"],
    },
    children: [
      {
        path: "/form/basic",
        name: "基础表单",
        // element: <BasignerPage />,
        meta: {
          title: "基础表单",
          icon: <FormOutlined />,
          permission: ["formBas"],
        },
        children: [
          {
            path: "/form/basic/basic",
            name: "操作表单",
            element: <DashboardPage />,
            meta: {
              title: "基础表单1",
              icon: <FormOutlined />,
              permission: ["formBas"],
            },
          },
        ],
      },
      {
        path: "/form/designer",
        name: "高级表单",
        element: <DesigneerPage />,
        meta: {
          title: "高级表单",
          icon: <FormOutlined />,
          permission: ["formDes"],
        },
      },
    ],
  },
  {
    path: "table",
    name: "表格",
    meta: {
      title: "表格",
      icon: <ContainerOutlined />,
      permission: ["table"],
    },
    children: [
      {
        path: "/table/basic",
        name: "基础表格",
        element: <TabBasicPage />,
        meta: {
          title: "基础表格",
          icon: <TableOutlined />,
          permission: ["tableBas"],
        },
      },
      {
        path: "/table/designer",
        name: "高级表格",
        element: <TabDesignerPage />,
        meta: {
          title: "高级表格",
          icon: <TableOutlined />,
          permission: ["tableDes"],
        },
      },
    ],
  },
];
export default routes;

上段代码是所有的权限路由,但是还有一个忽略的地方就是login和/路由,需要单独的在router操作如下:

router/index.tsx

import { createBrowserRouter, redirect } from "react-router-dom";
import React from "react";
import { LayoutGuard } from "./utils/guard";
import UserLogin from "@/views/login/loginPage";
import routes from "./routes";
import PermissionChecker from "./utils/permission";
import { getItem } from "@/utils/storeages";

const router = createBrowserRouter([
  {
    path: "/login",
    name: "login",
    element: <UserLogin />,
    loader: () => {
      const token = getItem("token");
      if (token) {
        return redirect("/home");
      }
      return null;
    },
  },
  {
    path: "/",
    name: "model",
    element: <LayoutGuard />,
    meta: {},
    // children: [...routes],
    children: [...PermissionChecker()],
  },
]);

export default router;

上面有一个LayoutGuard组件,其实是用来当作路由守卫用的,主要是针对强行跳转。

这里主要是管理后台所有的组件入口

router/utils/LayoutGuard.tsx

import AuthGuard from "./AuthGuard";
import ModelPage from "@/views/model";
import React from "react";

export const LayoutGuard = () => {
  return (
    <AuthGuard>
      <ModelPage />
    </AuthGuard>
  );
};

router/utils/AuthGuard.tsx

这里主要是针对重定向的操作,我这里没加404/403等页面,可以自己加上

import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import PermissionChecker from "./permission";
function AuthGuard({ children }: { children: JSX.Element }) {
  // console.log(PermissionChecker())
  const navigate = useNavigate();
  const token = useSelector((state: any) => state.userSlice.token);
  useEffect(() => {
    if (token) {
      // 如果已经登录,则重定向到登录页面
      navigate("home");
    } else {
      navigate("login");
    }
  }, [navigate]);

  return <>{children}</>;
}

export default AuthGuard;

5、配置权限路由

这里是路由管理的核心,涉及到权限,用到的主要是后端返回的权限以及前端自己配置的router/routes/index.ts下面的权限路由。

router/utils/permission,.ts

主要用于权限过滤

import routes from "../routes";
import { getItem } from "../../utils/storeages";

export default function PermissionChecker() {
  const userPermission: any = getItem("permission") || []; //这里是本地存储的数据,拿取权限
  let newRoutes: any = [];

  // 检查并过滤路由
  const filterRoutes = (routes: any[]) => {
    return routes.filter((route) => {
      if (route.meta && route.meta.permission) {
        const permission = route.meta.permission;
        if (permission.some((item: any) => userPermission.includes(item))) {
          // 如果当前路由有子路由,递归地过滤子路由
          if (route.children) {
            route.children = filterRoutes(route.children);
          }
          return true; // 符合条件,保留此路由
        }
      }
      return false; // 不符合条件,移除此路由
    });
  };

  // 使用 filterRoutes 函数处理所有路由
  newRoutes = filterRoutes(routes);

  return newRoutes;
}

6、asides路径跳转配置

这里主要是针对侧边栏跳转的展示配置,

src/views/model/asides/utils/routePromission.ts

主要是icon以及文字展示(当然都由权限控制)

import type { MenuProps } from "antd";

// 定义菜单项类型
type MenuItem = Required<MenuProps>["items"][number];

// 定义路由项类型
interface RouteItem {
  name: string;
  meta: {
    permission: string[];
    icon?: string;
  };
  path: string;
  children?: RouteItem[];
}

// 创建菜单项的函数
function getItems(
  label: React.ReactNode,
  key: React.Key,
  icon?: React.ReactNode,
  children?: MenuItem[],
): MenuItem {
  return {
    key,
    icon,
    children,
    label,
  } as MenuItem;
}

// 获取经过权限过滤的路由

// 处理路由并转换成菜单项
const routePromissionMeta = (routes: RouteItem[]): MenuItem[] => {
  const processRoutes = (
    routeList: RouteItem[],
    currentDepth = 0,
    maxDepth = 10,
  ): MenuItem[] => {
    if (currentDepth > maxDepth) return []; // 防止递归过深
    const result: MenuItem[] = [];
    for (const element of routeList) {
      if (element.meta.permission && element.name) {
        let keyPath = element.path;
        if (currentDepth !== 0) {
          keyPath = element.path.substring(1);
        }
        if (element.children && element.children.length > 0) {
          const children = processRoutes(element.children, currentDepth + 1);
          result.push(
            getItems(element.name, keyPath, element.meta.icon, children),
          );
        } else {
          result.push(getItems(element.name, keyPath, element.meta.icon));
        }
      }
    }
    return result;
  };

  return processRoutes(routes);
};

// 导出处理后的菜单项
export default routePromissionMeta;

在asides组件中使用

src/views/model/asides/index.tsx

import { Menu, Layout } from "antd";

import React from "react";
import { useNavigate } from "react-router-dom";
import loginName from "@/assets/images/logo.png";
import nameWhite from "@/assets/images/name_white.png";
import "./index.less";
import { useSelector } from "react-redux";
import { MenuInfo } from "rc-menu/lib/interface";
import routePromissionMeta from "./utils/routePromission";
import PermissionChecker from "@/router/utils/permission";
const { Sider } = Layout;

const SiderPage: React.FC = () => {
  const routes = PermissionChecker();
  const items = [...routePromissionMeta(routes)];
  console.log(items);
  const navigate = useNavigate();
  const { menuStatus } = useSelector((state) => state.settingSlice);
  const clickSide = (e: MenuInfo) => {
    console.log(e);
    navigate(`/${e.key}`);
  };
  return (
    <>
      <Sider collapsed={menuStatus}>
        <div className="demo-logo-vertical" />
        <div className="logo">
          <img src={loginName} alt="" className="logo-img" />
          {!menuStatus ? (
            <img src={nameWhite} alt="" className="logo-img-white" />
          ) : null}
        </div>
        <Menu
          theme="dark"
          defaultSelectedKeys={["Home"]}
          mode="inline"
          items={items}
          onClick={(e) => {
            clickSide(e);
          }}
        />
      </Sider>
    </>
  );
};

export default SiderPage;

到这里配置就结束了,通过react的使用来看,权限配置更加灵活多变,不像vue那样指定路由守卫操作。

相关推荐
前端啊龙2 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠6 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds26 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~1 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm