动态路由
路由分成两部分:
-
静态路由,固定的部分,如主页、404、login 这几个页面
-
动态路由,变化的部分,经常是主页内的嵌套路由,比如 Student、Teacher 这些
动态路由应该是根据用户登录后,根据角色的不同,从后端服务获取,因为这些数据是变化的,所以用 mobx 来管理
在src\store\路径下新建RoutesStore.tsx
TypeScript
import axios from "axios";
import {
LoginReq,
LoginResp,
Menu,
MenuAndRoute,
Route,
} from "../model/Student";
import R from "../model/R";
import { makeAutoObservable, runInAction } from "mobx";
import { Link, Navigate, RouteObject } from "react-router-dom";
import { load } from "../router/MyRouter";
import A8Main from "../pages/A8Main";
import A8NotFound from "../pages/A8NotFound";
import { ItemType } from "antd/es/menu/hooks/useItems";
import Icon from "./Icon";
//其中 convertMenu 为核心方法,负责将服务器返回的 Menu 转换成 antd Menu 组件需要的 Menu
function convertMenu(m: Menu): ItemType {
const Label = m.routePath ? <Link to={m.routePath}>{m.label}</Link> : m.label;
return {
key: m.key,
label: Label,
icon: <Icon name={m.icon}></Icon>,
children: m.children && m.children.map(convertMenu),
};
}
class RoutesStore {
dynamicRoutes: Route[] = [];
dynamicMenus: Menu[] = [];
token: string = "";
message: string = "";
state: string = "pending";
async login(loginReq: LoginReq) {
this.state = "pending";
const resp1 = await axios.post<R<LoginResp>>(
"http://localhost:8080/api/loginJwt",
loginReq
);
if (resp1.data.code === 999) {
const resp2 = await axios.get<R<MenuAndRoute>>(
`http://localhost:8080/api/menu/${loginReq.username}`
);
runInAction(() => {
this.dynamicRoutes = resp2.data.data.routeList;
localStorage.setItem(
"dynamicRoutes",
JSON.stringify(this.dynamicRoutes)
);
this.dynamicMenus = resp2.data.data.menuTree;
localStorage.setItem("dynamicMenus", JSON.stringify(this.dynamicMenus));
this.token = resp1.data.data.token;
localStorage.setItem("token", this.token);
this.state = "success";
});
} else {
runInAction(() => {
this.state = "error";
this.message = resp1.data.message || "未知错误";
});
}
}
/* async fetch(username: string) {
const resp = await axios.get<R<MenuAndRoute>>(
`http://localhost:8080/api/menu/${username}`
);
runInAction(() => {
this.dynamicRoutes = resp.data.data.routeList;
//当在浏览器地址栏重新输入路径的时候,会重新向7070服务器发送一个请求,导致RoutesStore.tsx重新执行,
//导致路由对象重新被创建,那么登录之后获得的动态路由数据就会丢失,所以为了防止这种情况,把登录后获得的
//路由数据存入到localStorage中
localStorage.setItem("dynamicRoutes", JSON.stringify(this.dynamicRoutes));
this.dynamicMenus = resp.data.data.menuTree;
localStorage.setItem("dynamicMenus", JSON.stringify(this.dynamicMenus));
});
} */
get routes() {
const staticRoutes: RouteObject[] = [
{
path: "/login",
element: load("A8Login"),
},
{
path: "/",
element: <A8Main></A8Main>,
children: [],
},
{
path: "/404",
element: <A8NotFound></A8NotFound>,
},
// 使用这个路径,上面的路径匹配不到时,显示notFound页面,但是路径还是输入的路径不变
{ path: "/*", element: <A8NotFound></A8NotFound> },
// 使用这种路径写法的时候,上面的路径匹配不到时,页面是重定向到notFound,路径会跳转到404
{
path: "/*",
element: <Navigate to={"/404"}></Navigate>,
},
];
staticRoutes[1].children = this.dynamicRoutes.map((r) => {
return { path: r.path, element: load(r.element) };
});
return staticRoutes;
}
get menus() {
return this.dynamicMenus.map(convertMenu);
}
get username() {
if (this.token.length === 0) {
return "";
}
//token 的前两部分都可以解码出来,其中 [1] 就是 token 的内容部分
const json = atob(this.token.split(".")[1]);
//parse方法把字符串还原成对象
return JSON.parse(json).sub;
}
constructor() {
makeAutoObservable(this);
//页面刷新会重新调用构造器,这个时候从localStorage中获取存储的路由数据
const routesJson = localStorage.getItem("dynamicRoutes");
this.dynamicRoutes = routesJson ? JSON.parse(routesJson) : [];
const menusJson = localStorage.getItem("dynamicMenus");
this.dynamicMenus = menusJson ? JSON.parse(menusJson) : [];
}
reset() {
localStorage.removeItem("dynamicRoutes");
this.dynamicRoutes = [];
localStorage.removeItem("dynamicMenus");
this.dynamicMenus = [];
localStorage.removeItem("token");
this.token = "";
this.state = "pending";
}
}
export default new RoutesStore();
-
其中用 localStorage 进行了数据的持久化,避免刷新后丢失数据
-
跳转若发生错误,可能是因为组件懒加载引起的,需要用 Suspense 解决
TypeScript
root.render(
<ConfigProvider locale={zhCN}>
<BrowserRouter>
<Suspense fallback={<h3>加载中...</h3>}>
<MyRouter></MyRouter>
</Suspense>
</BrowserRouter>
</ConfigProvider>
)