技术栈: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 } = checkUserreturn { code: 200, data: { token } }},},// 获取用户信息{url: '/api/user/info',method: 'get',response: (request) => {//获取请求头携带tokenconst 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) => {//获取请求头携带tokenconst 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><Menutheme="dark"defaultSelectedKeys={["Home"]}mode="inline"items={items}onClick={(e) => {clickSide(e);}}/></Sider></>);
};export default SiderPage;
到这里配置就结束了,通过react的使用来看,权限配置更加灵活多变,不像vue那样指定路由守卫操作。