体验ANT DESIGN PRO V5--菜单角色权限和服务端请求菜单(解决icon显示文字)

根据接口返回的角色渲染菜单

大致流程如下图所示
在这里插入图片描述

在建立的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了
步骤如下

  1. 在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业务代码了

  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值