SiderMenu.js 6.2 KB

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