1. 配置权限插件(@umijs/plugin-access)
先安装或启用权限插件,在 .umirc.ts
中配置:
typescript
复制
arduino
// .umirc.ts
export default {
plugins: ['@umijs/plugins/dist/access'],
access: {},
// 其他配置...
}
2. 创建 access.ts
定义权限规则
typescript
复制
typescript
// src/access.ts
export default function access(initialState: { permissions?: string[] }) {
const { permissions = [] } = initialState || {};
return {
// 示例:根据后端返回的权限标识控制权限
canAdmin: permissions.includes('admin'),
canRead: (routeName: string) => permissions.includes(routeName),
};
}
3. 实现动态权限路由和菜单(核心代码)
在 app.tsx
中实现以下逻辑:
typescript
复制
typescript
// src/app.tsx
import { history } from 'umi';
// 类型定义(根据后端接口调整)
interface BackendRoute {
path: string;
name: string;
icon?: string;
component: string;
access: string; // 权限标识
children?: BackendRoute[];
}
export async function getInitialState() {
// 1. 获取用户权限数据(模拟从后端接口获取)
const { permissions, dynamicRoutes } = await fetch('/api/auth/routes')
.then(res => res.json());
// 2. 返回初始状态(包含权限和菜单数据)
return {
permissions,
menuData: generateMenuData(dynamicRoutes), // 生成菜单结构
dynamicRoutes
};
}
// 动态路由转换器
const generateMenuData = (routes: BackendRoute[]) => {
return routes.map(route => ({
path: route.path,
name: route.name,
icon: route.icon,
children: route.children ? generateMenuData(route.children) : undefined,
}));
};
// 运行时动态插入路由
export function patchRoutes({ routes }: { routes: any[] }) {
// 找到需要插入路由的位置(通常是在某个布局路由的 children 中)
const layoutRoute = routes.find(route => route.path === '/');
if (layoutRoute && layoutRoute.children) {
layoutRoute.children.push(...initialState?.dynamicRoutes.map(convertRoute));
}
}
// 将后端路由转换为 Umi 路由格式
const convertRoute = (route: BackendRoute) => ({
path: route.path,
name: route.name,
access: route.access, // 对应 access.ts 中的权限规则
component: require(`@/pages/${route.component}`).default,
routes: route.children?.map(convertRoute),
});
// 动态菜单渲染
export const layout = {
menuDataRender: () => initialState?.menuData || [],
};
// 路由守卫(可选)
export function onRouteChange({ location }: any) {
const access = useAccess();
const isLogin = localStorage.getItem('token');
// 未登录跳转登录页
if (!isLogin && location.pathname !== '/login') {
history.push('/login');
}
// 检查页面权限(示例)
if (!access.canRead(location.pathname)) {
history.push('/403');
}
}
4. 后端接口数据格式示例
后端应返回类似以下结构的 JSON 数据:
json
复制
json
{
"permissions": ["dashboard", "user_manage"],
"dynamicRoutes": [
{
"path": "/dashboard",
"name": "仪表盘",
"component": "Dashboard/index",
"access": "canReadDashboard"
},
{
"path": "/user",
"name": "用户管理",
"component": "User/index",
"access": "user_manage",
"children": [
{
"path": "/user/list",
"name": "用户列表",
"component": "User/List"
}
]
}
]
}
5. 页面组件中控制按钮级权限
tsx
复制
javascript
// src/pages/User/List.tsx
import { useAccess } from 'umi';
export default () => {
const access = useAccess();
return (
<div>
{access.user_manage && <button>删除用户</button>}
</div>
);
}
关键点说明:
-
权限初始化 :通过
getInitialState
获取用户权限和动态路由数据 -
动态路由注入 :使用
patchRoutes
将后端路由转换为 Umi 路由格式 -
菜单动态渲染 :通过
menuDataRender
结合后端返回的结构生成菜单 -
细粒度控制:
- 路由级:通过
access
属性控制路由可见性 - 组件级:使用
useAccess
钩子 - 按钮级:直接在组件中判断权限
- 路由级:通过
常见问题处理:
- 热更新问题:动态路由修改后可能需要手动刷新页面
- 组件加载 :确保
component
路径与后端返回的组件路径匹配 - 权限同步:当权限变更时需清除缓存并刷新页面(或重新获取权限数据)
通过这套方案,可以实现完整的动态权限路由体系,且与后端完全解耦。