根据接口返回的角色渲染菜单
大致流程如下图所示
在建立的ANT DESIGN PRO V5项目中,主要靠以下两个文件完成左侧菜单根据角色权限渲染
默认的ant design pro v5项目给我们两个权限角色 admin和user,查看账户登录接口(/api/login/account)mock的数据:
// mock/user.ts
// 登录接口返回 mock/user.ts下接口/api/login/account
if (password === 'ant.design' && username === 'admin') {
res.send({
status: 'ok',
type,
currentAuthority: 'admin',
});
// 缓存角色
access = 'admin';
return;
}
if (password === 'ant.design' && username === 'user') {
res.send({
status: 'ok',
type,
currentAuthority: 'user',
});
// 缓存角色
access = 'user';
return;
}
// 登录用户信息返回
'GET /api/currentUser': (req: Request, res: Response) => {
...
res.send({
...
access: getAccess(), // 拿到登录时缓存的角色
...
});
},
在app.tsx中设置全局初始数据initialState
//app.tsx
// 将/api/currentUser接口返回的信息存入全局初始数据initialState
export async function getInitialState(): Promise<{
settings?: LayoutSettings;
currentUser?: API.CurrentUser;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
const fetchUserInfo = async () => {
try {
// 请求当前登录用户信息
const currentUser = await queryCurrent();
return currentUser;
} catch (error) {
history.push('/user/login');
}
return undefined;
};
// 如果是登录页面,不执行
if (history.location.pathname !== '/user/login') {
const currentUser = await fetchUserInfo();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings,
};
}
return {
fetchUserInfo,
settings: defaultSettings,
};
}
在src/access.ts通过initialState拿到当前角色access,并返回一个权限对象
// src/access.ts
// 通过initialState获取到access角色
// 这里return的对象key值设置到路由上即可实现权限
export default function access(initialState: { currentUser?: API.CurrentUser | undefined }) {
const { currentUser } = initialState || {};
//在config/routes.ts配置的access就是这个对象内元素的key值,只能为String类型,不能写数组
return {
canAdmin: currentUser && currentUser.access === 'admin',
// 自己根据实际业务写逻辑 参考umi插件 @umijs/plugin-access
canUser: (currentUser)=>(currentUser.access === 'user')
};
}
将权限对象内元素key值设置到需要配置权限的路由
// config/routes.ts
// 在每个路由下设置access
{
path: '/admin',
name: 'admin',
icon: 'crown',
access: 'canAdmin', // 设置权限
component: './Admin',
routes: [
{
path: '/admin/sub-page',
name: 'sub-page',
icon: 'smile',
component: './Welcome',
},
],
},
总结一下就是:
不同角色每次登录会把access设置为相应的角色admin,user,将该角色在(/api/currentUser)接口返回的currentUser塞入到全局初始数据initialState。在access.ts文件中就可以拿到当前登录的角色,并根据当前角色写权限的逻辑,再将权限名设置到rotues.ts中需要的路由上,就完成了通过角色控制左侧菜单。
根据接口返回的菜单渲染菜单
很多项目都是通过后台接口返回的菜单数据渲染左侧菜单,在V5中这么做也很方便,v5接受下面类型的菜单数据:
[
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
},
{
path: '/admin',
name: 'admin',
icon: 'crown',
children: [
{
path: '/admin/sub-page',
name: 'sub-page',
icon: 'SmileOutlined',
children: [
{
path: '/admin/thr-page',
name: 'thr-page',
icon: 'TabletOutlined',
},
],
},
],
},
{
name: 'list.table-list',
icon: 'table',
path: '/list',
},
]
mock一个菜单数据接口
// mock/user.ts
'GET /api/menuData': (req: Request, res: Response) => {
if (getAccess() === 'admin') {
res.send([
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
},
{
path: '/admin',
name: 'admin',
icon: 'crown',
children: [
{
path: '/admin/sub-page',
name: 'sub-page',
icon: 'SmileOutlined',
children: [
{
path: '/admin/thr-page',
name: 'thr-page',
icon: 'TabletOutlined',
},
],
},
],
},
{
name: 'list.table-list',
icon: 'table',
path: '/list',
},
]);
} else {
res.send([
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
},
{
name: 'list.table-list',
icon: 'SmileOutlined',
path: '/list',
}
]);
}
},
规范的写到services中
// src/services/user.ts
export async function queryMenuData() {
return request<any>('/api/menuData');
}
在app.tsx中获取菜单数据并塞入全局初始数据initialState
import { queryCurrent, queryMenuData } from './services/user';
export async function getInitialState(): Promise<{
settings?: LayoutSettings;
currentUser?: API.CurrentUser;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
fetchMenuData?: () => Promise<any>;
menuData: any;
}> {
...
// +++-------------------------start
// 获取菜单数据
const fetchMenuData = async () => {
try {
const menuData = await queryMenuData();
return menuData;
} catch (error) {
history.push('/user/login');
}
return undefined;
};
// +++-------------------------end
// 如果是登录页面,不执行
if (history.location.pathname !== '/user/login') {
const currentUser = await fetchUserInfo();
const menuData = await fetchMenuData();
return {
...
// +++------------------------start
menuData, // 将菜单数据存入initialState
// +++------------------------end
};
}
return {
// +++------------------------start
menuData: [],
// +++------------------------end
fetchUserInfo,
settings: defaultSettings,
};
}
// --------------------------------------------------
export const layout = ({
initialState,
}: {
initialState: {
settings?: LayoutSettings;
menuData: MenuDataItem[];
currentUser?: API.CurrentUser;
};
}): BasicLayoutProps => {
return {
...
// +++------------------------start
// 增加渲染接口返回的菜单数据
// v5的文档中把 menuDataRender写成了menuDateRender,复制粘贴的时候注意
menuDataRender: () => {
return initialState.menuData
},
// +++------------------------end
...
};
};
从新启动项目菜单就是来自接口数据了
经过上面步骤,已经成功从接口拿到了菜单数据并且渲染出来,不过美中不足的是菜单中的icon展示成文字了,看了下MenuDataItem的类型定义:
/**
* @name 菜单的icon
*/
icon?: React.ReactNode;
icon是ReactNode类型,而接口返回的icon是String类型,只需要做个映射就OK了
步骤如下
- 在src/utils下新建fixMenuItemIcon.ts
import React from 'react';
import { MenuDataItem } from '@ant-design/pro-layout';
import * as allIcons from '@ant-design/icons';
// FIX从接口获取菜单时icon为string类型
const fixMenuItemIcon = (menus: MenuDataItem[], iconType='Outlined'): MenuDataItem[] => {
menus.forEach((item) => {
const {icon, children} = item
if (typeof icon === 'string') {
let fixIconName = icon.slice(0,1).toLocaleUpperCase()+icon.slice(1) + iconType
item.icon = React.createElement(allIcons[fixIconName] || allIcons[icon])
}
children && children.length>0 ? item.children = fixMenuItemIcon(children) : null
});
return menus
};
export default fixMenuItemIcon;
然后在app.jsx中引入import fixMenuItemIcon from './utils/fixMenuItemIcon'
在修改menuDataRender
menuDataRender: () => {
// return initialState.menuData
return fixMenuItemIcon(initialState.menuData)
},
重启服务
接下来就可以开心的coding业务代码了