动态菜单和动态路由的 antd pro

SiderMenu.js 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import React, { PureComponent } from 'react';
  2. // import { Layout, Menu, Icon } from 'antd';
  3. import { Layout, Menu, Icon, Spin } from 'antd';
  4. import pathToRegexp from 'path-to-regexp';
  5. import { Link } from 'dva/router';
  6. import styles from './index.less';
  7. import { urlToList } from '../_utils/pathTools';
  8. const { Sider } = Layout;
  9. const { SubMenu } = Menu;
  10. // Allow menu.js config icon as string or ReactNode
  11. // icon: 'setting',
  12. // icon: 'http://demo.com/icon.png',
  13. // icon: <Icon type="setting" />,
  14. const getIcon = icon => {
  15. if (typeof icon === 'string' && icon.indexOf('http') === 0) {
  16. return <img src={icon} alt="icon" className={`${styles.icon} sider-menu-item-img`} />;
  17. }
  18. if (typeof icon === 'string') {
  19. return <Icon type={icon} />;
  20. }
  21. return icon;
  22. };
  23. export const getMeunMatcheys = (flatMenuKeys, path) => {
  24. return flatMenuKeys.filter(item => {
  25. return pathToRegexp(item).test(path);
  26. });
  27. };
  28. export default class SiderMenu extends PureComponent {
  29. constructor(props) {
  30. super(props);
  31. // this.menus = props.menuData;
  32. this.flatMenuKeys = this.getFlatMenuKeys(props.menuData);
  33. this.state = {
  34. openKeys: this.getDefaultCollapsedSubMenus(props),
  35. };
  36. }
  37. componentWillReceiveProps(nextProps) {
  38. if (nextProps.location.pathname !== this.props.location.pathname) {
  39. this.setState({
  40. openKeys: this.getDefaultCollapsedSubMenus(nextProps),
  41. });
  42. }
  43. }
  44. /**
  45. * Convert pathname to openKeys
  46. * /list/search/articles = > ['list','/list/search']
  47. * @param props
  48. */
  49. getDefaultCollapsedSubMenus(props) {
  50. const { location: { pathname } } = props || this.props;
  51. return urlToList(pathname)
  52. .map(item => {
  53. // return getMeunMatcheys(this.flatMenuKeys, item)[0];
  54. return getMeunMatcheys(this.getFlatMenuKeys(props.menuData), item)[0];
  55. })
  56. .filter(item => item);
  57. }
  58. /**
  59. * Recursively flatten the data
  60. * [{path:string},{path:string}] => {path,path2}
  61. * @param menus
  62. */
  63. getFlatMenuKeys(menus) {
  64. let keys = [];
  65. menus.forEach(item => {
  66. if (item.children) {
  67. keys = keys.concat(this.getFlatMenuKeys(item.children));
  68. }
  69. keys.push(item.path);
  70. });
  71. return keys;
  72. }
  73. /**
  74. * 判断是否是http链接.返回 Link 或 a
  75. * Judge whether it is http link.return a or Link
  76. * @memberof SiderMenu
  77. */
  78. getMenuItemPath = item => {
  79. const itemPath = this.conversionPath(item.path);
  80. const icon = getIcon(item.icon);
  81. const { target, name } = item;
  82. // Is it a http link
  83. if (/^https?:\/\//.test(itemPath)) {
  84. return (
  85. <a href={itemPath} target={target}>
  86. {icon}
  87. <span>{name}</span>
  88. </a>
  89. );
  90. }
  91. return (
  92. <Link
  93. to={itemPath}
  94. target={target}
  95. replace={itemPath === this.props.location.pathname}
  96. onClick={
  97. this.props.isMobile
  98. ? () => {
  99. this.props.onCollapse(true);
  100. }
  101. : undefined
  102. }
  103. >
  104. {icon}
  105. <span>{name}</span>
  106. </Link>
  107. );
  108. };
  109. /**
  110. * get SubMenu or Item
  111. */
  112. getSubMenuOrItem = item => {
  113. if (item.children && item.children.some(child => child.name)) {
  114. const childrenItems = this.getNavMenuItems(item.children);
  115. // 当无子菜单时就不展示菜单
  116. if (childrenItems && childrenItems.length > 0) {
  117. return (
  118. <SubMenu
  119. title={
  120. item.icon ? (
  121. <span>
  122. {getIcon(item.icon)}
  123. <span>{item.name}</span>
  124. </span>
  125. ) : (
  126. item.name
  127. )
  128. }
  129. key={item.path}
  130. >
  131. {childrenItems}
  132. </SubMenu>
  133. );
  134. }
  135. return null;
  136. } else {
  137. return <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>;
  138. }
  139. };
  140. /**
  141. * 获得菜单子节点
  142. * @memberof SiderMenu
  143. */
  144. getNavMenuItems = menusData => {
  145. if (!menusData) {
  146. return [];
  147. }
  148. return menusData
  149. .filter(item => item.name && !item.hideInMenu)
  150. .map(item => {
  151. // make dom
  152. const ItemDom = this.getSubMenuOrItem(item);
  153. return this.checkPermissionItem(item.authority, ItemDom);
  154. })
  155. .filter(item => item);
  156. };
  157. // Get the currently selected menu
  158. getSelectedMenuKeys = () => {
  159. const { location: { pathname } } = this.props;
  160. // return urlToList(pathname).map(itemPath => getMeunMatcheys(this.flatMenuKeys, itemPath).pop());
  161. return urlToList(pathname).map(itemPath => getMeunMatcheys(this.getFlatMenuKeys(this.props.menuData), itemPath).pop());
  162. };
  163. // conversion Path
  164. // 转化路径
  165. conversionPath = path => {
  166. if (path && path.indexOf('http') === 0) {
  167. return path;
  168. } else {
  169. return `/${path || ''}`.replace(/\/+/g, '/');
  170. }
  171. };
  172. // permission to check
  173. checkPermissionItem = (authority, ItemDom) => {
  174. if (this.props.Authorized && this.props.Authorized.check) {
  175. const { check } = this.props.Authorized;
  176. return check(authority, ItemDom);
  177. }
  178. return ItemDom;
  179. };
  180. isMainMenu = key => {
  181. // return this.menus.some(item => key && (item.key === key || item.path === key));
  182. return this.props.menuData.some(item => key && (item.key === key || item.path === key));
  183. };
  184. handleOpenChange = openKeys => {
  185. const lastOpenKey = openKeys[openKeys.length - 1];
  186. const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1;
  187. this.setState({
  188. openKeys: moreThanOne ? [lastOpenKey] : [...openKeys],
  189. });
  190. };
  191. render() {
  192. // const { logo, collapsed, onCollapse } = this.props;
  193. const { logo, collapsed, onCollapse, menuData } = this.props;
  194. const { openKeys } = this.state;
  195. // Don't show popup menu when it is been collapsed
  196. const menuProps = collapsed
  197. ? {}
  198. : {
  199. openKeys,
  200. };
  201. // if pathname can't match, use the nearest parent's key
  202. let selectedKeys = this.getSelectedMenuKeys();
  203. if (!selectedKeys.length) {
  204. selectedKeys = [openKeys[openKeys.length - 1]];
  205. }
  206. console.log('menuData: ', menuData);
  207. return (
  208. <Sider
  209. trigger={null}
  210. collapsible
  211. collapsed={collapsed}
  212. breakpoint="lg"
  213. onCollapse={onCollapse}
  214. width={256}
  215. className={styles.sider}
  216. >
  217. <div className={styles.logo} key="logo">
  218. <Link to="/">
  219. <img src={logo} alt="logo" />
  220. <h1>Ant Design Pro</h1>
  221. </Link>
  222. </div>
  223. {/* <Menu
  224. key="Menu"
  225. theme="dark"
  226. mode="inline"
  227. {...menuProps}
  228. onOpenChange={this.handleOpenChange}
  229. selectedKeys={selectedKeys}
  230. style={{ padding: '16px 0', width: '100%' }}
  231. >
  232. {this.getNavMenuItems(this.menus)}
  233. </Menu> */}
  234. <Spin spinning={menuData.length === 0}>
  235. <Menu
  236. key="Menu"
  237. theme="dark"
  238. mode="inline"
  239. {...menuProps}
  240. onOpenChange={this.handleOpenChange}
  241. selectedKeys={selectedKeys}
  242. style={{ padding: '16px 0', width: '100%' }}
  243. >
  244. {this.getNavMenuItems(menuData)}
  245. </Menu>
  246. </Spin>
  247. </Sider>
  248. );
  249. }
  250. }