技术栈: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那样指定路由守卫操作。