ant-design/ant-design-pro

🐛[BUG] 从服务端请求菜单时 icon 和 access 不生效

Closed this issue · 41 comments

🐛 bug 描述

https://beta-pro.ant.design/docs/advanced-menu-cn

根据该文档的方式实现从服务端请求菜单,返回的菜单中包含 icon 信息和 accss 信息,icon 显示错误,access 不生效。

icon显示结果:
image

access无效。

📷 复现步骤

返回的菜单信息:
path: '/system',
name: 'system',
icon: 'setting',
access: 'noPermission',

🏞 期望结果

显示设置的icon,access 生效

💻 复现代码

© 版本信息

  • Ant Design Pro 版本: 5.0.0-beta.2
  • umi 版本
  • 浏览器环境
  • 开发环境 [e.g. mac OS]

🚑 其他信息

icon 只能用 dom,access 必须要触发页面的重新渲染才有用

你是缺少 @ant-design/icons 图标组件包吧?
安装方法npm install --save @ant-design/icons
我测试
{ path: '/welcome', name: 'welcome', icon: 'setting', component: './Admin', },
是可以用的
image

服务器请求回来应该自己处理,是不会走到插件的逻辑的。
权限的请求reload 可以实现,但是菜单不行

能不能给个详细的icon使用dom渲染的方法,我尝试了下,没成功

icon 只能用 dom,access 必须要触发页面的重新渲染才有用

能否给点儿详细的代码提示,我将menu数据中的icon字段直接替换成 dom,不生效

在utils中封装了一个方法;

`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;`

在layout组件中直接调用
`menuDataRender={() => fixMenuItemIcon(menus.menusData)}`

就能解决了

你的这个代码会导致 js 大小增加 2m 左右,要慎重

请问怎么更优雅的解决呢?

菜单access怎么解决呢

有啥好的解决办法么,解决服务器请求菜单设置icon图标

@chenshuai2144 同问该怎么优雅的解决icon

用了个死办法,枚举

import {
  BookOutlined,
  LinkOutlined,
  AppstoreOutlined,
  SmileOutlined,
  TableOutlined,
  SoundOutlined,
  CrownOutlined,
} from '@ant-design/icons';

// 利用对象进行图标映射
const iconMapping = {
  AppstoreOutlined: <AppstoreOutlined />,
  SmileOutlined: <SmileOutlined />,
  TableOutlined: <TableOutlined />,
  SoundOutlined: <SoundOutlined />,
  CrownOutlined: <CrownOutlined />
}


   menu: {
      params: initialState,
      // 这里从后端请求动态菜单
      request: async (params, defaultMenuData) => {

        // 这里返回后端的菜单配置  forEach? 把icon字符串 转化为 render图标
        params?.currentUser?.reqMemu.forEach((item: any) => item.icon = iconMapping[item.icon])
        return params?.currentUser?.reqMemu;
      },
    },

用了个死办法,枚举

import {
  BookOutlined,
  LinkOutlined,
  AppstoreOutlined,
  SmileOutlined,
  TableOutlined,
  SoundOutlined,
  CrownOutlined,
} from '@ant-design/icons';

// 利用对象进行图标映射
const iconMapping = {
  AppstoreOutlined: <AppstoreOutlined />,
  SmileOutlined: <SmileOutlined />,
  TableOutlined: <TableOutlined />,
  SoundOutlined: <SoundOutlined />,
  CrownOutlined: <CrownOutlined />
}


   menu: {
      params: initialState,
      // 这里从后端请求动态菜单
      request: async (params, defaultMenuData) => {

        // 这里返回后端的菜单配置  forEach? 把icon字符串 转化为 render图标
        params?.currentUser?.reqMemu.forEach((item: any) => item.icon = iconMapping[item.icon])
        return params?.currentUser?.reqMemu;
      },
    },

得枚举很大一部分... @chengzi-code

所以这么问题优雅的解决了吗

icon 不显示的问题,可以自定义菜单项渲染方法,并引用自定义的图标库
看到这个问题被关闭,并标记为Feature Request,不知道以后是否有官方解决方案。
我的方案如下:

// MenuItemRender.tsx,`app.tsx`直接引用即可
import { MenuDataItem } from '@ant-design/pro-layout';
import { createFromIconfontCN } from '@ant-design/icons';
import { Link, useIntl } from 'umi';

let IconFont = createFromIconfontCN({
  // 以下是默认值,也可以按需要指定
  // scriptUrl: defaultSettings.iconfontUrl,
});

const getIcon = (
  icon?: string | React.ReactNode,
  iconPrefixes: string = 'icon-',
): React.ReactNode => {
  if (typeof icon === 'string' && icon !== '') {
    // 可加入多种图标类型的兼容写法,此处省略
    if (icon.startsWith(iconPrefixes)) {
      return <IconFont type={icon} className={icon} />;
    }
  }
  return icon;
};

const MenuItem: React.FC<MenuDataItem> = (menuItemProps) => {
  const { formatMessage } = useIntl();
  const { isUrl: isLink, path, icon, locale } = menuItemProps;
  const localeStr = locale as string; // 和 `formatMessage中` 的 `id` 类型不一致,只好断言一下
  const itemContent = (
    <span className="ant-pro-menu-item">
      {getIcon(icon)}
      <span className="ant-pro-menu-item-title">{formatMessage({ id: localeStr })}</span>
    </span>
  );
  return isLink || !path || location.pathname === path ? (
    itemContent
  ) : (
    <Link to={path}>{itemContent}</Link>
  );
};

export default MenuItem;

还是想用 Ant Design 的图标库?拿去吧:https://www.iconfont.cn/collections/detail?cid=9402

 // app.tsx
 export const layout: RunTimeLayoutConfig = ({ initialState }) => {
   return {
       rightContentRender: () => <RightContent />,
       disableContentMargin: false,
       onPageChange: () => {
          const { location } = history;
         // 如果没有登录,重定向到 login
        if (!initialState?.currentUser && location.pathname !== loginPath) {
            history.push(loginPath);
        }
     },
     ...initialState?.settings,
     menuDataRender: () => {
        if (initialState?.menus) {
           return initialState.menus;
        }
        return [];
     },
    menuItemRender: (menuItemProps: MenuDataItem) => {
        return <MenuItemRender {...menuItemProps} />;
    }
   }
 };
// 菜单
 {
    path: '/welcome',
    name: 'welcome',
    icon: 'smile',
    component: './Welcome'
  },
  {
    path: '/admin',
    name: 'admin',
    icon: 'crown',
    access: 'canAdmin',
    component: './Admin',
    routes: [
      {
        path: '/admin/sub-page',
        name: 'sub-page',
        icon: 'smile',
        component: './Welcome'
      },
      {
        component: './404'
      }
    ]
  },
  {
    name: 'list.table-list',
    icon: 'table',
    path: '/list',
    component: './TableList'
  },

@kevinsheep 是写法问题吗?iconPrefixes 的判断也去掉了,菜单加载出来一直是一个路径...
image

@kevinsheep 是写法问题吗?iconPrefixes 的判断也去掉了,菜单加载出来一直是一个路径...

确认一下,icon中的名称,与你的图标库中对应图标命名完全一致(大小写敏感)

@kevinsheep 是写法问题吗?iconPrefixes 的判断也去掉了,菜单加载出来一直是一个路径...

确认一下,icon中的名称,与你的图标库中对应图标命名完全一致(大小写敏感)

引用的就是Ant Design 的图标库

@kevinsheep 是写法问题吗?iconPrefixes 的判断也去掉了,菜单加载出来一直是一个路径...

确认一下,icon中的名称,与你的图标库中对应图标命名完全一致(大小写敏感)

引用的就是Ant Design 的图标库

~\config\defaultSettings.ts iconfontUrl 怎么设置的?完整点的代码贴一下?

defaultSettings

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。
基本上的代码就是楼上贴的了呀,主要是app.tsx里面调用menuItemRender

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。

对比你的字库文件和菜单设置中的icon名称,问题应该是你的图标字库文件内容:
那些叫smile,crown的图标,一个都找不到,并且留意到你的图标名称是以icon为前缀的

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。

对比你的字库文件和菜单设置中的icon名称,问题应该是你的图标字库文件内容:
那些叫smile,crown的图标,一个都找不到,并且留意到你的图标名称是以icon为前缀的

用的是 Ant Design 的图标库,加上icon- 也不行
image

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。
用的是 Ant Design 的图标库,加上icon- 也不行

你在这个js中搜索一下,有没有一个叫’icon-smile‘的图标名?

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。
用的是 Ant Design 的图标库,加上icon- 也不行

你在这个js中搜索一下,有没有一个叫’icon-smile‘的图标名?

我以为这个链接时 antd 的图标库...里面没有smile的图标

在utils中封装了一个方法;

`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;`

在layout组件中直接调用
`menuDataRender={() => fixMenuItemIcon(menus.menusData)}`

就能解决了

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。
用的是 Ant Design 的图标库,加上icon- 也不行

你在这个js中搜索一下,有没有一个叫’icon-smile‘的图标名?

我以为这个链接时 antd 的图标库...里面没有smile的图标

在utils中封装了一个方法;

`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;`

在layout组件中直接调用
`menuDataRender={() => fixMenuItemIcon(menus.menusData)}`

就能解决了

scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js' 。
用的是 Ant Design 的图标库,加上icon- 也不行

你在这个js中搜索一下,有没有一个叫’icon-smile‘的图标名?

我以为这个链接时 antd 的图标库...里面没有smile的图标

这个子菜单的icon好像渲染不出来

从服务器加载 menu 并且使用 icon

这里有 ant-design-pro 的使用示例

从服务器加载 menu 并且使用 icon

这里有 ant-design-pro 的使用示例

这是写死的icon

这么久了还有挖坟的

不改代码,直接在nginx里面加一行rewrite规则即可:
location ~* ^/(.*)/logo\.svg$ { rewrite ^/(.*)/(logo\.svg)$ https://a.com/logo.svg permanent; }

动态菜单的渲染 subMenuItemRender 和 menuItemRender 两个渲染API要配合使用。menuItemRender可以参考 #8101 (comment)

您好 请问解决了吗

icon 不显示的问题,可以自定义菜单项渲染方法,并引用自定义的图标库。 看到这个问题被关闭,并标记为Feature Request,不知道以后是否有官方解决方案。 我的方案如下:

// MenuItemRender.tsx,`app.tsx`直接引用即可
import { MenuDataItem } from '@ant-design/pro-layout';
import { createFromIconfontCN } from '@ant-design/icons';
import { Link, useIntl } from 'umi';

let IconFont = createFromIconfontCN({
  // 以下是默认值,也可以按需要指定
  // scriptUrl: defaultSettings.iconfontUrl,
});

const getIcon = (
  icon?: string | React.ReactNode,
  iconPrefixes: string = 'icon-',
): React.ReactNode => {
  if (typeof icon === 'string' && icon !== '') {
    // 可加入多种图标类型的兼容写法,此处省略
    if (icon.startsWith(iconPrefixes)) {
      return <IconFont type={icon} className={icon} />;
    }
  }
  return icon;
};

const MenuItem: React.FC<MenuDataItem> = (menuItemProps) => {
  const { formatMessage } = useIntl();
  const { isUrl: isLink, path, icon, locale } = menuItemProps;
  const localeStr = locale as string; // 和 `formatMessage中` 的 `id` 类型不一致,只好断言一下
  const itemContent = (
    <span className="ant-pro-menu-item">
      {getIcon(icon)}
      <span className="ant-pro-menu-item-title">{formatMessage({ id: localeStr })}</span>
    </span>
  );
  return isLink || !path || location.pathname === path ? (
    itemContent
  ) : (
    <Link to={path}>{itemContent}</Link>
  );
};

export default MenuItem;

还是想用 Ant Design 的图标库?拿去吧:https://www.iconfont.cn/collections/detail?cid=9402

image

icon 不显示的问题,可以自定义菜单项渲染方法,并引用自定义的图标库。 看到这个问题被关闭,并标记为Feature Request,不知道以后是否有官方解决方案。 我的方案如下:

// MenuItemRender.tsx,`app.tsx`直接引用即可
import { MenuDataItem } from '@ant-design/pro-layout';
import { createFromIconfontCN } from '@ant-design/icons';
import { Link, useIntl } from 'umi';

let IconFont = createFromIconfontCN({
  // 以下是默认值,也可以按需要指定
  // scriptUrl: defaultSettings.iconfontUrl,
});

const getIcon = (
  icon?: string | React.ReactNode,
  iconPrefixes: string = 'icon-',
): React.ReactNode => {
  if (typeof icon === 'string' && icon !== '') {
    // 可加入多种图标类型的兼容写法,此处省略
    if (icon.startsWith(iconPrefixes)) {
      return <IconFont type={icon} className={icon} />;
    }
  }
  return icon;
};

const MenuItem: React.FC<MenuDataItem> = (menuItemProps) => {
  const { formatMessage } = useIntl();
  const { isUrl: isLink, path, icon, locale } = menuItemProps;
  const localeStr = locale as string; // 和 `formatMessage中` 的 `id` 类型不一致,只好断言一下
  const itemContent = (
    <span className="ant-pro-menu-item">
      {getIcon(icon)}
      <span className="ant-pro-menu-item-title">{formatMessage({ id: localeStr })}</span>
    </span>
  );
  return isLink || !path || location.pathname === path ? (
    itemContent
  ) : (
    <Link to={path}>{itemContent}</Link>
  );
};

export default MenuItem;

还是想用 Ant Design 的图标库?拿去吧:https://www.iconfont.cn/collections/detail?cid=9402

image

image
image
解决

image image 解决

目前尝试了这种写法,导致控制台报出警告:
image

从服务器加载 menu 并且使用 icon

我也是这个思路

击掌(✧∇✧)╯╰(✧∇✧)̣

kasnti commented

根据图标字符串创建图标元素,除了使用上文提到的这个方法:
item.icon = React.createElement(allIcons[fixIconName] || allIcons[icon]);
也可以使用
<Icon component={allIcons['fixIconName']} /> (其中Icon标签来源:import Icon from '@ant-design/icons';
不知道哪个性能好一些?

image image 解决

完美解决我的问题,谢谢兄弟

icon 只能用 dom,access 必须要触发页面的重新渲染才有用

如何重新渲染呢! 贴个示例悄悄呢,大神