SiderMenu.js 6.5 KB

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