- ant design pro 如何去保存颜色
- ant design pro v6 如何做好角色管理
- ant design 的 tree 如何作为角色中的权限选择之一
- ant design 的 tree 如何作为角色中的权限选择之二
- ant design pro access.ts 是如何控制多角色的权限的
- ant design pro 中用户的表单如何控制多个角色
如上图所示,这里的菜单是从后端动态得到的
我们看下后端菜单的响应数据:
{
"success": true,
"data": [
{
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": {
"_id": "66b9ad528554e602536acc84",
"name": "授权管理菜单",
"path": "/auth",
"action": "GET",
"permissionGroup": "66b9ad348554e602536acc67",
"createdAt": "2024-08-12T06:36:02.754Z",
"updatedAt": "2024-08-12T06:36:02.754Z",
"__v": 0
},
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined",
"children": [
{
"_id": "66b6cdbbb9ad87dfa985f1f9",
"name": "用户管理",
"path": "/auth/users",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b6d352b9ad87dfa985f3f0",
"name": "查看用户",
"path": "/users",
"action": "GET",
"permissionGroup": "66b6d2c9b9ad87dfa985f34f",
"createdAt": "2024-08-10T02:41:22.895Z",
"updatedAt": "2024-08-10T08:03:22.477Z",
"__v": 0
},
"createdAt": "2024-08-10T02:17:31.227Z",
"updatedAt": "2024-08-12T10:23:32.641Z",
"__v": 0,
"icon": "HeartOutlined",
"children": []
},
{
"_id": "66b6cdcfb9ad87dfa985f210",
"name": "角色管理",
"path": "/auth/roles",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b6d40db9ad87dfa985f475",
"name": "查看角色",
"path": "/roles",
"action": "GET",
"permissionGroup": "66b6d2e9b9ad87dfa985f377",
"createdAt": "2024-08-10T02:44:29.797Z",
"updatedAt": "2024-08-10T08:03:18.669Z",
"__v": 0
},
"createdAt": "2024-08-10T02:17:51.754Z",
"updatedAt": "2024-08-12T10:11:47.776Z",
"__v": 0,
"icon": "MenuFoldOutlined",
"children": []
},
{
"_id": "66b6cde2b9ad87dfa985f229",
"name": "菜单管理",
"path": "/auth/menus",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b6d48bb9ad87dfa985f4e7",
"name": "查看菜单",
"path": "/menus",
"action": "GET",
"permissionGroup": "66b6d2ddb9ad87dfa985f362",
"createdAt": "2024-08-10T02:46:35.896Z",
"updatedAt": "2024-08-10T08:03:13.698Z",
"__v": 0
},
"createdAt": "2024-08-10T02:18:10.776Z",
"updatedAt": "2024-08-12T10:30:56.693Z",
"__v": 0,
"icon": "MenuFoldOutlined",
"children": []
},
{
"_id": "66b6cdfcb9ad87dfa985f244",
"name": "权限管理",
"path": "/auth/permissions",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b1c55141364c27c464f858",
"name": "查看权限",
"path": "/permissions",
"action": "GET",
"permissionGroup": "66b1b00bb5d937a0aef34034",
"createdAt": "2024-08-06T06:40:17.991Z",
"updatedAt": "2024-08-10T08:03:27.245Z",
"__v": 0
},
"createdAt": "2024-08-10T02:18:36.390Z",
"updatedAt": "2024-08-10T02:18:36.390Z",
"__v": 0,
"children": []
},
{
"_id": "66b6ce29b9ad87dfa985f261",
"name": "权限组管理",
"path": "/auth/permission-groups",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b6d52cb9ad87dfa985f546",
"name": "查看权限组",
"path": "/permission-groups",
"action": "GET",
"permissionGroup": "66b6d314b9ad87dfa985f3a7",
"createdAt": "2024-08-10T02:49:16.624Z",
"updatedAt": "2024-08-10T08:03:09.517Z",
"__v": 0
},
"createdAt": "2024-08-10T02:19:21.424Z",
"updatedAt": "2024-08-12T06:37:05.295Z",
"__v": 0,
"children": []
},
{
"_id": "66b6ce41b9ad87dfa985f280",
"name": "数据权限管理",
"path": "/auth/data-permissions",
"parent": {
"_id": "66b6cd18b9ad87dfa985f190",
"name": "认证管理",
"path": "/auth",
"permission": "66b9ad528554e602536acc84",
"createdAt": "2024-08-10T02:14:48.819Z",
"updatedAt": "2024-08-12T10:30:50.749Z",
"__v": 0,
"icon": "SecurityScanOutlined"
},
"permission": {
"_id": "66b6d586b9ad87dfa985f592",
"name": "查看数据权限",
"path": "/data-permissions",
"action": "GET",
"permissionGroup": "66b6d2fdb9ad87dfa985f38e",
"createdAt": "2024-08-10T02:50:46.780Z",
"updatedAt": "2024-08-10T08:03:04.925Z",
"__v": 0
},
"createdAt": "2024-08-10T02:19:45.054Z",
"updatedAt": "2024-08-12T08:23:19.027Z",
"__v": 0,
"children": []
}
]
},
{
"_id": "66b6ce76b9ad87dfa985f2a1",
"name": "材料类目",
"path": "/material-categories",
"permission": {
"_id": "66b6d7d0b9ad87dfa985f782",
"name": "查看材料类目",
"path": "/material-categories",
"action": "GET",
"permissionGroup": "66adec30d647a4fde5546b1c",
"createdAt": "2024-08-10T03:00:32.932Z",
"updatedAt": "2024-08-10T08:02:59.634Z",
"__v": 0
},
"createdAt": "2024-08-10T02:20:38.550Z",
"updatedAt": "2024-08-12T09:58:32.426Z",
"__v": 0,
"icon": "UsergroupAddOutlined",
"children": []
}
]
}
看到这里的数据结构了吗:
主要是 name children 这些比较重要,name 是显示出来的,children 是层级结构
当然还有 icon 之类的 还有 path ,也是要的,毕竟菜单要有路径的。
那后端如何实现呢:
// @desc Get permission menus
// @route GET /api/menus/fetch
// @access Private
const fetchMenus = handleAsync(async (req: RequestCustom, res: Response) => {
const query = buildQuery(req.query);
const menus = await Menu.find(query)
.populate('parent')
.populate('permission');
const menusWithChildren = await Promise.all(
menus.map(async (menu) => {
const menuWithChildren = menu.toObject();
menuWithChildren.children = await getChildren(menu._id);
return menuWithChildren;
}),
);
res.json({
success: true,
data: checkMenu(menusWithChildren, req.user),
});
});
只要你的后端能返回出这样的数据就行。
那前端呢:
app.tsx
menu: {
// 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
params: {
userId: initialState?.currentUser?._id,
},
request: async () => {
// initialState.currentUser 中包含了所有用户信息
const { data, success } = await fetchMenuData();
console.log('data', data);
if (success) {
console.log('loopMenuItem(data)', loopMenuItem(data));
return loopMenuItem(data);
} else {
return [];
}
},
},
这里还有 icon 的使用:
const iconEnum: { [key: string]: ReactElement<any, any> } = {
UsergroupAddOutlined: <UsergroupAddOutlined />,
SmileOutlined: <SmileOutlined />,
HeartOutlined: <HeartOutlined />,
GlobalOutlined: <GlobalOutlined />,
MenuFoldOutlined: <MenuFoldOutlined />,
TeamOutlined: <TeamOutlined />,
DatabaseOutlined: <DatabaseOutlined />,
GatewayOutlined: <GatewayOutlined />,
SecurityScanOutlined: <SecurityScanOutlined />,
};
console.log('iconEnum', iconEnum);
const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>
menus.map(({ icon, children, ...item }) => {
return {
...item,
icon: icon && iconEnum[icon as string],
children: children && loopMenuItem(children),
};
});
完整代码是这样的:
import { Footer, SelectLang, AvatarDropdown, AvatarName } from '@/components';
import {
DatabaseOutlined,
GatewayOutlined,
GlobalOutlined,
HeartOutlined,
LinkOutlined,
MenuFoldOutlined,
SecurityScanOutlined,
SmileOutlined,
TeamOutlined,
UsergroupAddOutlined,
} from '@ant-design/icons';
import type { Settings as LayoutSettings, MenuDataItem } from '@ant-design/pro-components';
import { SettingDrawer } from '@ant-design/pro-components';
import type { RunTimeLayoutConfig } from '@umijs/max';
import { history, Link } from '@umijs/max';
import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig';
import { fetchMenuData, currentUser as queryCurrentUser } from '@/services/ant-design-pro/api';
import React, { ReactElement } from 'react';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
const iconEnum: { [key: string]: ReactElement<any, any> } = {
UsergroupAddOutlined: <UsergroupAddOutlined />,
SmileOutlined: <SmileOutlined />,
HeartOutlined: <HeartOutlined />,
GlobalOutlined: <GlobalOutlined />,
MenuFoldOutlined: <MenuFoldOutlined />,
TeamOutlined: <TeamOutlined />,
DatabaseOutlined: <DatabaseOutlined />,
GatewayOutlined: <GatewayOutlined />,
SecurityScanOutlined: <SecurityScanOutlined />,
};
console.log('iconEnum', iconEnum);
const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>
menus.map(({ icon, children, ...item }) => {
return {
...item,
icon: icon && iconEnum[icon as string],
children: children && loopMenuItem(children),
};
});
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
currentUser?: API.CurrentUser;
loading?: boolean;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
const fetchUserInfo = async () => {
try {
const response = await queryCurrentUser({
skipErrorHandler: true,
});
return response.data;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
// 如果不是登录页面,执行
const { location } = history;
if (location.pathname !== loginPath) {
const currentUser = await fetchUserInfo();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings as Partial<LayoutSettings>,
};
}
return {
fetchUserInfo,
settings: defaultSettings as Partial<LayoutSettings>,
};
}
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return {
actionsRender: () => [<SelectLang key="SelectLang" />],
avatarProps: {
src: initialState?.currentUser?.avatar,
title: <AvatarName />,
render: (_, avatarChildren) => {
return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
},
},
menu: {
// 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
params: {
userId: initialState?.currentUser?._id,
},
request: async () => {
// initialState.currentUser 中包含了所有用户信息
const { data, success } = await fetchMenuData();
console.log('data', data);
if (success) {
console.log('loopMenuItem(data)', loopMenuItem(data));
return loopMenuItem(data);
} else {
return [];
}
},
},
waterMarkProps: {
content: '',
},
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}
},
links: isDev
? [
<Link key="openapi" to="/umi/plugin/openapi" target="_blank">
<LinkOutlined />
<span>OpenAPI 文档</span>
</Link>,
]
: [],
menuHeaderRender: undefined,
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态
childrenRender: (children) => {
// if (initialState?.loading) return <PageLoading />;
return (
<>
{children}
{isDev && (
<SettingDrawer
disableUrlParams
enableDarkTheme
settings={initialState?.settings}
onSettingChange={(settings) => {
setInitialState((preInitialState) => ({
...preInitialState,
settings,
}));
}}
/>
)}
</>
);
},
...initialState?.settings,
};
};
/**
* @name request 配置,可以配置错误处理
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
* @doc https://umijs.org/docs/max/request#配置
*/
export const request = {
baseURL: `${process.env.UMI_APP_API_URL}/api`,
...errorConfig,
};
export async function fetchMenuData() {
return request<menuResponse>('/menus/fetch', {
method: 'GET',
});
}
这样就可以弄好动态菜单的:
我们拥有 12 年建站编程经验
- 虚拟产品交易平台定制开发
- WordPress 外贸电商独立站建站
我的网站