需求:
登录后不同权限用户显示不同的菜单。
实现思路:
1.后端创建一个接口,用来获取用户权限,例如我的接口根据token请求返回的roleid就是用来进行权限判断的。
2.修改原生的getuserinfo,因为登录接口请求成功后,我会调用getinfo,所以在前端存储中拿到token获取返回的roleid,然后重点:把获取到的roleid存储在前端,否则会导致刷新页面后菜单就失效了。
3.更新Menu.tsx,就可以实现了。因为官网没有React部分的权限控制说明,所以在此记录。
接口请求及数据示例
POST /users/info HTTP/1.1
Host: 10.211.55.3:8080
Origin: http://localhost:3003
Referer: http://localhost:3003/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0
Token: 36afd276-7c21-44f8-8240-3d8434aa5965
Content-Type: application/json
{
}
{
"uid": 508151339008790528,
"username": "test4",
"mobile": "15555555555",
"roleid": 1,
"loginname": "15555555555"
}
// src/modules/user/index.ts
export const getUserInfo = createAsyncThunk(`${namespace}/users/info`, async () => {
const token = localStorage.getItem(TOKEN_NAME); // Retrieve the token from localStorage
console.log('Token:', token); // Log the token
const fetchUserInfo = async (token: string | null) => {
if (!token) {
throw new Error('Token is missing');
}
try {
const response = await fetch(`/users/info`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Token': token,
},
body: JSON.stringify({}),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const userInfo = await response.json();
return userInfo;
} catch (error) {
console.error('Error fetching user info:', error);
throw error;
}
};
const userInfo = await fetchUserInfo(token);
let roles: string[] = [];
switch (userInfo.roleid) {
case 1:
roles = ['admin'];
break;
case 2:
roles = ['officer'];
break;
case 3:
roles = ['rounder'];
break;
default:
roles = [];
}
localStorage.setItem('userRoles', JSON.stringify(roles));
console.log('Fetched user info:', userInfo); // Log fetched user info
console.log('Assigned roles:', roles); // Log assigned roles
return {
...userInfo,
roles,
};
});
/ login
export const login = createAsyncThunk(`${namespace}/login`, async (userInfo: Record<string, unknown>, { dispatch }) => {
const loginRequest = async (userInfo: Record<string, unknown>) => {
try {
const response = await fetch(`${apiBaseUrl}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userInfo),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const res = await response.json();
if (res.success === true) {
localStorage.setItem(TOKEN_NAME, res.content); // Store the token from the content property
await dispatch(getUserInfo());
return res.content;
} else {
throw new Error(res.message || 'Login failed');
}
} catch (error) {
console.error('Error during login:', error);
throw error;
}
};
const res = await loginRequest(userInfo);
return res;
});
修改菜单文件
// src/layout/components/Menu.tsx
import React, { memo, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Menu, MenuValue } from 'tdesign-react';
import router, { IRouter, filterRoutesByRole } from 'router';
import { resolve } from 'utils/path';
import { useAppSelector } from 'modules/store';
import { selectGlobal } from 'modules/global';
import MenuLogo from './MenuLogo';
import Style from './Menu.module.less';
const { SubMenu, MenuItem, HeadMenu } = Menu;
interface IMenuProps {
showLogo?: boolean;
showOperation?: boolean;
}
const renderMenuItems = (menu: IRouter[], parentPath = '') => {
const navigate = useNavigate();
return menu.map((item) => {
const { children, meta, path } = item;
if (!meta || meta?.hidden === true) {
return null;
}
const { Icon, title, single } = meta;
const routerPath = resolve(parentPath, path);
if (!children || children.length === 0) {
return (
<MenuItem
key={routerPath}
value={routerPath}
icon={Icon ? <Icon /> : undefined}
onClick={() => navigate(routerPath)}
>
{title}
</MenuItem>
);
}
if (single && children?.length > 0) {
const firstChild = children[0];
if (firstChild?.meta && !firstChild?.meta?.hidden) {
const { Icon, title } = meta;
const singlePath = resolve(resolve(parentPath, path), firstChild.path);
return (
<MenuItem
key={singlePath}
value={singlePath}
icon={Icon ? <Icon /> : undefined}
onClick={() => navigate(singlePath)}
>
{title}
</MenuItem>
);
}
}
return (
<SubMenu key={routerPath} value={routerPath} title={title} icon={Icon ? <Icon /> : undefined}>
{renderMenuItems(children, routerPath)}
</SubMenu>
);
});
};
/**
* 顶部菜单
*/
export const HeaderMenu = memo(() => {
const globalState = useAppSelector(selectGlobal);
const location = useLocation();
const userRoles = useAppSelector((state) => state.user.userInfo.roles);
const filteredRoutes = filterRoutesByRole(router, userRoles);
const [active, setActive] = useState<MenuValue>(location.pathname);
return (
<HeadMenu
expandType='popup'
style={{ marginBottom: 20 }}
value={active}
theme={globalState.theme}
onChange={(v) => setActive(v)}
>
{renderMenuItems(filteredRoutes)}
</HeadMenu>
);
});
/**
* 左侧菜单
*/
export default memo((props: IMenuProps) => {
const location = useLocation();
const globalState = useAppSelector(selectGlobal);
const userRoles = useAppSelector((state) => state.user.userInfo.roles);
console.log('User roles from state:', userRoles); // Log user roles from state
const filteredRoutes = filterRoutesByRole(router, userRoles);
const { version } = globalState;
const bottomText = globalState.collapsed ? version : `阿巴阿巴 ${version}`;
return (
<Menu
width='232px'
style={{ flexShrink: 0, height: '100%' }}
className={Style.menuPanel2}
value={location.pathname}
theme={globalState.theme}
collapsed={globalState.collapsed}
operations={props.showOperation ? <div className={Style.menuTip}>{bottomText}</div> : undefined}
logo={props.showLogo ? <MenuLogo collapsed={globalState.collapsed} /> : undefined}
>
{renderMenuItems(filteredRoutes)}
</Menu>
);
});