在这篇文章中,你将了解到:(文末有彩蛋!!!)
写这篇文章呢,是因为笔者公司最近要求做一个后台管理系统,要求在前端这里能根据角色映射并动态修改相应的菜单权限,笔者还是太菜了,看遍了网上关于antd pro权限的文章和issue,最后终于搞定,网上关于V4的文章实在是太少了,所以笔者打算把这次踩坑经历分享给大家,以后大家一起在坑里愉快的玩耍。。。
首先,有关权限的文件有这几个
ok,以上就是一般情况下,我们会涉及到的权限管理文件,下面,我们来逐一详细的分析各个文件的作用
(1)@/components/Authorized
我们首先从入口文件index.jsx进入,可以看到,返回了一个RenderAuthorize变量,这个变量具体是什么呢?我们再看renderAuthorize这个函数
const renderAuthorize = Authorized => currentAuthority => {
if (currentAuthority) {
if (typeof currentAuthority === 'function') {
CURRENT = currentAuthority();
}
if (
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
Array.isArray(currentAuthority)
) {
CURRENT = currentAuthority;
}
} else {
CURRENT = 'NULL';
}
return Authorized;
};
export { CURRENT };
export default Authorized => renderAuthorize(Authorized); //这个是index.jsx中引入的函数
可以看到,renderAuthorize 这个函数,实际上是返回了一个以*currentAuthority* 作为参数,以*Authorized*作为返回值的函数,并将CURRENT这个变量暴露了出去,至于这个变量的作用(实际上就是当前用户登陆时的角色,只是对其进行了一些处理),下文我们会涉及,现在咱们先继续哈!
然后回到index.jsx,我们可以看到,Authrized作为变量被传入renderAuthorize这个方法
const RenderAuthorize = renderAuthorize(Authorized);
而Authorized呢,实则是一个组件(jsx第一公民优越的地方,能够将组件当作变量使用),
它接收
三个props属性,等下我们会看到,在BasicLayouts.jsx中,antd pro v4实际上是将每个路由组件都包裹上了一层这个组件。
import React from 'react';
import { Result } from 'antd';
import check from './CheckPermissions';
const Authorized = ({
children,
authority,
noMatch = (
<Result
status="403"
title="403"
subTitle="Sorry, you are not authorized to access this page."
/>
),
}) => {
const childrenRender = typeof children === 'undefined' ? null : children;
const dom = check(authority, childrenRender, noMatch);
return <>{dom}</>;
};
export default Authorized;
然后,我们又看到了check这个方法
import React from 'react';
import { CURRENT } from './renderAuthorize'; // eslint-disable-next-line import/no-cycle
import PromiseRender from './PromiseRender';
/**
* 通用权限检查方法
* Common check permissions method
* @param { 权限判定 | Permission judgment } authority
* @param { 你的权限 | Your permission description } currentAuthority
* @param { 通过的组件 | Passing components } target
* @param { 未通过的组件 | no pass components } Exception
*/
const checkPermissions = (authority, currentAuthority, target, Exception) => {
// 没有判定权限.默认查看所有
// Retirement authority, return target;
。。。此处省略具体的代码,实际上就是对于路由中,authority数组、string、promise、function四种写法的不同处理,然后根据是否符合权限校验返回通过的组件或者未通过的组件
};
export { checkPermissions };
function check(authority, target, Exception) {
return checkPermissions(authority, CURRENT, target, Exception);
}
export default check;
所以综上所属@/components/Authorized导出的,实际上是一个以Authorized组件为参数的函数,它最后返回的结果仍旧是Authorized,整个文件的目的在于,对路由携带的权限和用户所拥有的权限进行比对,返回通过的组件或者未通过的组件罢了。
(2)@/utils/Authorized.jsx
有了上面的基础呢,我们接下来再看utils下的Authorized.jsx文件
import RenderAuthorize from '@/components/Authorized';
import { getAuthority } from './authority';
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-mutable-exports */
let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component
const reloadAuthorized = () => {
Authorized = RenderAuthorize(getAuthority());
};
/**
* hard code
* block need it。
*/
window.reloadAuthorized = reloadAuthorized;
export { reloadAuthorized };
export default Authorized;
我们上文中提到了,@/components/Authorizedz这个组件,返回的实质上是一个以currentAuthority 作为参数,以Authorized组件作为返回值的函数,因此该文件的作用呢,在于以下三点。
./authority.js
import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project.
export function getAuthority(str) {
const authorityString =
typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"]
...实际上就是从本地读取保存的用户角色,然后进行一系列处理
return authority;
}
export function setAuthority(authority) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload
reloadAuthorized();
}
我们可以看到,这个文件就做了两件事情,一个就是从本地读取当前用户角色,一个是保存角色,而保存角色setAuthority这个函数呢,是在models/login内部调用的,也就是在用户登录的时候,进行保存
reducers: {
changeLoginStatus(state, { payload }) {
setAuthority(payload.currentAuthority);
return { ...state, status: payload.status, type: payload.type };
},
},
(3)@/layouts/BasicLayout.jsx
好啦,一路屠龙,我们终于来到antd pro v4权限最关键的地方了,这里也是自定义动态菜单需要修改的地方,不过呢,我们先来讲解一下此处,是如何进行路由鉴权的
const menuDataRender = menuList =>
menuList.map(item => {
const localItem = {
...item,
children: item.children ? menuDataRender(item.children) : undefined,
};
return Authorized.check(item.authority, localItem, null);
});
接收的参数: menuList,是默认从src/.umi/core/routes.ts中生成的菜单,而这些菜单是根据config下的routes属性自动生成的。
返回值: 返回一个由通过权鉴的目标组件和没有通过的403组件组成的数组
此处的Authorized.check方法,就是我们在上文中讲解过的checkPermisson方法,在index.jsx中,将check方法绑定在了Authorized这个组件上面
Authorized.Secured = Secured;
Authorized.check = check;
const RenderAuthorize = renderAuthorize(Authorized);
export default RenderAuthorize;
(4)@/pages/Authorized.jsx
这个笔者看下来觉得是有些懵逼的,因为笔者搜索过全局,发现并没有任何一个地方调用了这个组件或者跳转到了这个地方,我去Github上看了别人提出来的issue才明白。(umi作者)
也有一位外国小哥提出了不同的意见
他的意思翻译过来呢,就是他认为应该在路由配置的地方,注入Authorized组件。
如果能够创建一个新的Authenticated页面,直接重新跳转到/user/login页面并且使用Authorized组件来展示403页面,会不会我们就可以完全避免在BasicLayout中,再次用Authorized组件进行包裹的逻辑呢?
小哥贴出了自己的代码,大家可以看一下
方案详情代码
笔者对于antd pro V4这个骚操作还是有点疑问,欢迎大家在留言区进行探讨,并指出笔者写作中的不足之处!
动态路由 + 动态菜单
关于这个主题,既然已经有解决方案了,笔者就不再赘述了,以后可能会随着笔者对于umi框架理解的加深,再跟进一些其它文章,这篇文章中的解决方案是笔者亲手实践,可以实现的,如果大家有疑问的话,可以@笔者,笔者会尽力帮助大家
感谢阅读,欢迎批评指正,希望大家能够在追求卓越中不断进步,让优秀成为一种习惯~