From a5b5f9c8da07a6f2123921429c46e844e68adbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=BC=9F=E8=8D=A3?= Date: Mon, 16 Jul 2018 14:31:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BE=A7=E8=BE=B9=E6=A0=8F?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=B8=8D=E8=83=BD=E5=8A=A8=E6=80=81=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SiderMenu/SiderMenu.js | 254 ++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 src/components/SiderMenu/SiderMenu.js diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js new file mode 100644 index 00000000..fb8f1a76 --- /dev/null +++ b/src/components/SiderMenu/SiderMenu.js @@ -0,0 +1,254 @@ +import React, { PureComponent } from 'react'; +import { Layout, Menu, Icon } from 'antd'; +import pathToRegexp from 'path-to-regexp'; +import { Link } from 'dva/router'; +import styles from './index.less'; +import { urlToList } from '../_utils/pathTools'; + +const { Sider } = Layout; +const { SubMenu } = Menu; + +// Allow menu.js config icon as string or ReactNode +// icon: 'setting', +// icon: 'http://demo.com/icon.png', +// icon: , +const getIcon = icon => { + if (typeof icon === 'string' && icon.indexOf('http') === 0) { + return icon; + } + if (typeof icon === 'string') { + return ; + } + return icon; +}; + +/** + * Recursively flatten the data + * [{path:string},{path:string}] => {path,path2} + * @param menu + */ +export const getFlatMenuKeys = menu => + menu.reduce((keys, item) => { + keys.push(item.path); + if (item.children) { + return keys.concat(getFlatMenuKeys(item.children)); + } + return keys; + }, []); + +/** + * Find all matched menu keys based on paths + * @param flatMenuKeys: [/abc, /abc/:id, /abc/:id/info] + * @param paths: [/abc, /abc/11, /abc/11/info] + */ +export const getMenuMatchKeys = (flatMenuKeys, paths) => + paths.reduce( + (matchKeys, path) => + matchKeys.concat(flatMenuKeys.filter(item => pathToRegexp(item).test(path))), + [] + ); + +export default class SiderMenu extends PureComponent { + constructor(props) { + super(props); + this.flatMenuKeys = getFlatMenuKeys(props.menuData); + this.state = { + openKeys: this.getDefaultCollapsedSubMenus(props), + }; + } + + componentWillReceiveProps(nextProps) { + const { location } = this.props; + if (nextProps.location.pathname !== location.pathname) { + this.setState({ + openKeys: this.getDefaultCollapsedSubMenus(nextProps), + }); + } + } + + /** + * Convert pathname to openKeys + * /list/search/articles = > ['list','/list/search'] + * @param props + */ + getDefaultCollapsedSubMenus(props) { + const { + location: { pathname }, + } = + props || this.props; + return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname)); + } + + /** + * 判断是否是http链接.返回 Link 或 a + * Judge whether it is http link.return a or Link + * @memberof SiderMenu + */ + getMenuItemPath = item => { + const itemPath = this.conversionPath(item.path); + const icon = getIcon(item.icon); + const { target, name } = item; + // Is it a http link + if (/^https?:\/\//.test(itemPath)) { + return ( + + {icon} + {name} + + ); + } + const { location, isMobile, onCollapse } = this.props; + return ( + { + onCollapse(true); + } + : undefined + } + > + {icon} + {name} + + ); + }; + + /** + * get SubMenu or Item + */ + getSubMenuOrItem = item => { + if (item.children && item.children.some(child => child.name)) { + const childrenItems = this.getNavMenuItems(item.children); + // 当无子菜单时就不展示菜单 + if (childrenItems && childrenItems.length > 0) { + return ( + + {getIcon(item.icon)} + {item.name} + + ) : ( + item.name + ) + } + key={item.path} + > + {childrenItems} + + ); + } + return null; + } else { + return {this.getMenuItemPath(item)}; + } + }; + + /** + * 获得菜单子节点 + * @memberof SiderMenu + */ + getNavMenuItems = menusData => { + if (!menusData) { + return []; + } + return menusData + .filter(item => item.name && !item.hideInMenu) + .map(item => { + // make dom + const ItemDom = this.getSubMenuOrItem(item); + return this.checkPermissionItem(item.authority, ItemDom); + }) + .filter(item => item); + }; + + // Get the currently selected menu + getSelectedMenuKeys = () => { + const { + location: { pathname }, + } = this.props; + return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname)); + }; + + // conversion Path + // 转化路径 + conversionPath = path => { + if (path && path.indexOf('http') === 0) { + return path; + } else { + return `/${path || ''}`.replace(/\/+/g, '/'); + } + }; + + // permission to check + checkPermissionItem = (authority, ItemDom) => { + const { Authorized } = this.props; + if (Authorized && Authorized.check) { + const { check } = Authorized; + return check(authority, ItemDom); + } + return ItemDom; + }; + + isMainMenu = key => { + return this.props.menuData.some(item => key && (item.key === key || item.path === key)); + }; + + handleOpenChange = openKeys => { + const lastOpenKey = openKeys[openKeys.length - 1]; + const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1; + this.setState({ + openKeys: moreThanOne ? [lastOpenKey] : [...openKeys], + }); + }; + + render() { + const { logo, collapsed, onCollapse } = this.props; + const { openKeys } = this.state; + // Don't show popup menu when it is been collapsed + const menuProps = collapsed + ? {} + : { + openKeys, + }; + // if pathname can't match, use the nearest parent's key + let selectedKeys = this.getSelectedMenuKeys(); + if (!selectedKeys.length) { + selectedKeys = [openKeys[openKeys.length - 1]]; + } + return ( + +
+ + logo +

Ant Design Pro

+ +
+ + {this.getNavMenuItems(this.props.menuData)} + +
+ ); + } +} -- GitLab