1.菜单能正常加载的话,加载的菜单都是用户有权访问的,没权访问的不会显示出来,但是有个明显的权限问题,就是用户如果知道某个路由的访问url地址的话,直接在地址栏中输入url地址,是会正常加载该url的。比如我们之前做的登录日志列表的页面访问地址是/list/loginfo,我们先成功登录后,然后在地址栏中输入/list/loginfo,,登录日志列表页面是会正常加载的,如下图所示
2.如果我们想控制在地址栏中直接输入/list/loginfo,提示403,该如何实现呢?我们先参照官网文档路由和菜单的权限控制,理下思路。
如果需要对路由还有菜单进行权限控制,可以直接在路由上原有基础配置上加上权限控制相关的属性,即可快速实现路由和菜单的权限控制。(前提需要使用最佳实践的 Layout 方案 - @alipay/umi-plugin-layout )。
在以上定义(src/access.ts, src/app.ts)完成的基础上,再在路由配置项上添加 access 属性即可完成路由和菜单的权限控制。access 属性的值为 src/access.ts 中返回的对象的 key。以下为实际例子:
假设权限定义文件 src/access.ts 内容如下:
// src/access.ts
export default function (initialState = {}) {
const { isAdmin, hasRoutes = [] } = initialState;
return {
// ...
adminRouteFilter: () => isAdmin, // 只有管理员可访问
normalRouteFilter: (route) => hasRoutes.includes(route.name), // initialState 中包含了的路由才有权限访问
};
}
通过以上示例可以看到,权限路由控制相关的函数,接收"当前处理的路由"作为第一个参数
那么只需要按以下方式在常规路由配置中加上 access 这一项即可:
// config/config.ts
import { defineConfig } from 'umi';
export default defineConfig({
routes: [
{
path: '/foo',
name: 'foo',
// ...
access: 'normalRouteFilter', // 会调用 src/access.ts 中返回的 normalRouteFilter 进行鉴权
},
{
path: '/admin',
name: 'admin',
// ...
access: 'adminRouteFilter', // 会调用 src/access.ts 中返回的 adminRouteFilter 进行鉴权
},
],
// ...
});
对应鉴权函数(比如 adminRouteFilter)在接收路由作为参数后返回值为 false,该条路由将会被禁用,并且从左侧 layout 菜单中移除,如果直接从 URL 访问对应路由,将看到一个 403 页面。
3.官网这段话比较难理解,本人经过实践操作,确定如果要实现直接从 URL 访问对应(无权)路由,将看到一个 403 页面的效果,操作制步骤如下,先在src/access.ts中定义权限标识,比如在access.js(js版是js)中我们定义一个key叫 authorize的函数,authorize: (route) =>,官网上这句话:权限路由控制相关的函数,接收”当前处理的路由”作为第一个参数,也就是authorize的参数route就是当前正在访问的路由,然后我们可以得到当前用户有权访问的所有菜单,遍历菜单跟route参数做对比,如果route不在有权访问的菜单数组中,我们可以返回false,返回false即无权访问。下一步还要改config/config.js下的路由配置,我们的登录日志列表是配置在这里的,原始路由配置如下:
{
name: 'loginfor',
icon: 'smile',
path: '/list/loginfo',
component: './loginfo',
},
我们在要控制的路由其中加一行access:authorize即可实现效果,authorize是我们之前在access中配置的key authorize,即
{
name: 'loginfor',
icon: 'smile',
path: '/list/loginfo',
component: './loginfo',
access: 'authorize',
},
4.这里有一个小问题就是如何得到当前用户有权访问的所有菜单,我们之前是可以通过函数获取的,当然也可以再调用一下函数来获取,如果频繁要调用的数据,我们可以考虑放入initialState,全局初始数据中
5.原理我们理清后,我们来做操作,完整的操作步骤如下,修改app.tsx,拿到菜单数据后,将放入到全局初始数据中
export const layout = ({ initialState,setInitialState }) => {
layout中要传入setInitialState方法
menu: {
// 每当 initialState?.currentUser?.userId 发生修改时重新执行 request
params: {
userId: initialState?.currentUser?.userId,
},
request: async (params, defaultMenuData) => {
const tempMenuData = await getCurrentUserMenus();
const menuData=fixMenuItemIcon(tempMenuData);
setInitialState({
...initialState,
menuData: menuData,
});
return menuData;
},
setInitialState中放入拿到的菜单数据menuData
6.打开src/services/ant-design-pro/menu.js,增加判定当前访问路由是否在有权限访问的菜单数组中的方法 getMatchMenuItem,
export function getMatchMenuItem(path, menuData){
if(!menuData)
return [];
let items= [];
menuData.forEach((item) => {
if (item.path) {
if (item.path === path) {
items.push(item);
}
if (path.length >= item.path?.length) {
const exp = `${item.path}/*`;
if (path.match(exp)) {
if(item.children) {
const subpath = path.substr(item.path.length+1);
const subItem = getMatchMenuItem(subpath, item.children);
items = items.concat(subItem);
} else {
const paths = path.split('/');
if(paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
items.push(item);
}
}
}
}
}
});
return items;
}
7.打开src/access.js,先引入getMatchMenuItem方法,按照之前说的逻辑,增加authorize: (route) => {…}函数,完整的access.js代码如下:
/**
* @see https://umijs.org/zh-CN/plugins/plugin-access
* */
import { getMatchMenuItem } from "./services/ant-design-pro/menu";
export default function access(initialState) {
const { currentUser, menuData } = initialState || {};
return {
authorize: (route) => {
if(menuData) {
const items = getMatchMenuItem(route.path, menuData);
if(!items || items.length === 0){
return false;
} else {
return true;
}
}
return true;
}, // initialState 中包含了的路由才有权限访问
};
}
access.js中的menuData是从initialState中拿的。
8.修改config/config.js中登录日志的路由配置,代码如下:
{
name: 'loginfor',
icon: 'smile',
path: '/list/loginfo',
component: './loginfo',
access: 'authorize',
},
9.编译后,重启前台程序,这时候我们再登录后,在浏览器中直接访问/list/loginfo,就会返现报403了,截图如下: