UmiJS PC 项目一:ProTable 高度设置
UmiJS PC 项目二:动态路由配置
UmiJS PC 项目三:TypeScript 使用
在 PC 项目开发里,路由的使用与配置始终是关键要点,尤其是实际项目中动态路由的引入,进一步提升了其复杂性。
当我们运用脚手架搭建 vue 或 react 项目时,社区里存在诸多成熟的解决方案可供选用。然而,在使用 UmiJS 时,却会遭遇一些棘手的问题。基于此,结合官方文档、参考他人的经验以及实际的操作过程,我将为大家分享两种行之有效的解决方案。
一、基于权限 的动态路由方案
这种方案在官方文档中已给出详尽的使用步骤,此处便不再赘述,建议大家参考示例源码,或者亲自上手实践。
路由重定向处理
不过在实际运用过程中,若我们配置了 redirect,而权限配置却将该路由过滤掉,那么跳转时就会出现问题。解决办法是在动态配置的 patchRoutes 中,重新构建路由的 redirect 配置,以此确保路由跳转的正常运行。
import { rebuildRedirect } from "./dynamicRoutes";
export function patchRoutes({ routes }: DynamicRoutes.ParseRoutesReturnType) {
rebuildRedirect(routes);
}
- rebuildRedirect 核心处理文件
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(获取权限控制信息):::process
B --> C(权限过滤: 删除无访问权限的路由):::process
C --> D(获取第一个动态路由):::process
D --> E(路由分类):::process
E --> E1(无重定向路由):::process
E --> E2(有重定向路由):::process
E2 --> F{是否找到父级路由?}:::decision
F -->|是| G(更新重定向路径):::process
F -->|否| H(标记为待删除并移除父级 ID):::process
G --> I(删除无效路由):::process
H --> I
I --> J(更新根路径重定向):::process
J --> K([结束]):::startend
关键步骤:
- 权限过滤,移除无权限访问的路由。
- 路由分类,分为无重定向路由、有重定向路由。
- 处理重定向路由,更新重定向路径或标记为待删除。
- 删除无效路由。
- 更新根路径的重定向。
import access from "@/access";
import type { DynamicRoutes } from "./dynamicRoutes.d";
import { find } from "lodash-es";
export function rebuildRedirect(
routes: DynamicRoutes.ParsedRoutes,
baseRouteIdx = 2
) {
const accessControl: Record<string, boolean> = access();
// 删除没有访问权限的路由
Object.keys(routes).forEach((key: any) => {
let r = routes[key] as any;
if (r.access && !accessControl[r.access]) {
delete routes[key];
}
});
const firstRouteKey = Object.keys(routes)[baseRouteIdx];
const firstDynamicRoute = routes[firstRouteKey];
const nonRedirectRoutes: any[] = [];
const redirectRoutes: any[] = [],
routesToDelete: any[] = [];
// 分类路由
Object.keys(routes).forEach((key: any) => {
const k = Number(key);
let r = routes[key] as any;
if (k && !r.redirect && r.parentId) {
nonRedirectRoutes.push(r);
} else if (k && r.redirect && r.parentId) {
redirectRoutes.push(r);
}
});
// 处理重定向路由
redirectRoutes.forEach((route: any) => {
const parentRoute = find(
nonRedirectRoutes,
(r: any) => r.parentId === route.parentId
);
if (!parentRoute) {
routesToDelete.push(route.id);
delete route["parentId"];
} else {
route.redirect = parentRoute.path;
}
});
// 删除无效路由
routesToDelete.forEach((id: any) => {
delete routes[id];
});
// 更新根路径的重定向
Object.keys(routes).forEach((key: any) => {
let r = routes[key] as any;
if (r.path === "/" && r.redirect) {
r.redirect = firstDynamicRoute.path;
}
});
}
二、服务端响应数据动态更新路由
路由响应 mock 数据
生成动态路由数据及组件
该函数的主要目的是将原始的路由配置数据解析成包含路由信息和 React 组件的对象。其核心步骤包括初始化数据结构、遍历原始路由数据、根据是否为一级路由进行不同处理,同时处理组件加载,最后返回解析结果。
export function parseRoutes(
routesRaw: DynamicRoutes.RouteRaw[],
beginIdx: number
): DynamicRoutes.ParseRoutesReturnType {
const routes: DynamicRoutes.ParsedRoutes = {}; // 转换后的路由信息
const routeComponents: DynamicRoutes.ParsedRouteComponent = {}; // 生成的React.lazy组件
const routeParentMap = new Map<string, number>(); // menuId 与路由记录在 routes 中的键 的映射。如:'role_management' -> 7
let currentIdx = beginIdx; // 当前处理的路由项的键。把 patchRoutes 传进来的 routes 看作一个数组,这里就是元素的下标。
routesRaw.forEach((route) => {
let effectiveRoute = true; // 当前处理中的路由是否有效
const routePath = route.path; // 全小写的路由路径
const componentPath = route.component; // 组件路径 不含 @/pages/
// 是否为直接显示(不含子路由)的路由记录,如:/home; /Dashboard
if (!route.parentId) {
// 生成路由信息
const tempRoute: DynamicRoutes.Route = {
id: currentIdx.toString(),
parentId: "@@/global-layout",
name: route.name,
path: routePath,
icon: route.icon,
}; // 存储路由信息
if (route.redirect) {
tempRoute["redirect"] = route.redirect;
}
routes[currentIdx] = tempRoute; // 生成组件
const tempComponent = route.component
? lazy(() => import(`@/pages/${componentPath}`))
: Outlet; // 存储组件
routeComponents[currentIdx] = tempComponent;
routeParentMap.set(route.menuId, currentIdx);
} else {
// 非一级路由
// 获取父级路由ID
const realParentId = routeParentMap.get(route.parentId);
if (realParentId) {
// 生成路由信息
const tempRoute: DynamicRoutes.Route = {
id: currentIdx.toString(),
parentId: realParentId.toString(),
name: route.name,
path: routePath,
}; // 存储路由信息
if (route.redirect) {
tempRoute["redirect"] = route.redirect;
}
routes[currentIdx] = tempRoute; // 生成组件
const tempComponent = componentPath
? lazy(() => import(`@/pages/${componentPath}`))
: Outlet; // 存储组件
routeComponents[currentIdx] = tempComponent;
} else {
// 找不到父级路由,路由无效,workingIdx不自增
effectiveRoute = false;
}
}
if (effectiveRoute) {
// 当路由有效时,将workingIdx加一
currentIdx += 1;
}
});
return {
routes,
routeComponents,
};
}
app.ts 运行时配置
import type { DynamicRoutes } from "./dynamicRoutes.d";
import { parseRoutes } from "./dynamicRoutes";
async function fetchDynamicRoutes() {
try {
const role = getRole();
if (!role) return;
const { data: routesData } = await fetch(`/api/system/routes/${role}`, {
method: "POST",
}).then((res) => res.json());
if (routesData) {
window.dynamicRoutes = routesData;
}
} catch {
message.error("路由加载失败");
}
}
await fetchDynamicRoutes();
export function patchRoutes({
routes,
routeComponents,
}: DynamicRoutes.ParseRoutesReturnType) {
if (window.dynamicRoutes) {
const routeKeys = Object.keys(routes)
.filter((key) => parseInt(key) > 0)
.map(parseInt);
const beginIdx = routeKeys[routeKeys.length - 1] + 1;
const parsedRoutes = parseRoutes(window.dynamicRoutes, beginIdx);
Object.assign(routes, parsedRoutes.routes); // 直接操作原对象,合并路由数据
Object.assign(routeComponents, parsedRoutes.routeComponents); // 合并组件
}
}
参考: