4 changed files with 2 additions and 261 deletions
@ -1,256 +0,0 @@ |
|||||
import React, { PureComponent } from 'react'; |
|
||||
import { Layout, Menu, Icon } from 'antd'; |
|
||||
import pathToRegexp from 'path-to-regexp'; |
|
||||
import Link from 'umi/link'; |
|
||||
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: <Icon type="setting" />,
|
|
||||
const getIcon = icon => { |
|
||||
if (typeof icon === 'string') { |
|
||||
if (icon.indexOf('http') === 0) { |
|
||||
return <img src={icon} alt="icon" className={`${styles.icon} sider-menu-item-img`} />; |
|
||||
} |
|
||||
return <Icon type={icon} />; |
|
||||
} |
|
||||
|
|
||||
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 ( |
|
||||
<a href={itemPath} target={target}> |
|
||||
{icon} |
|
||||
<span>{name}</span> |
|
||||
</a> |
|
||||
); |
|
||||
} |
|
||||
const { location, isMobile, onCollapse } = this.props; |
|
||||
return ( |
|
||||
<Link |
|
||||
to={itemPath} |
|
||||
target={target} |
|
||||
replace={itemPath === location.pathname} |
|
||||
onClick={ |
|
||||
isMobile |
|
||||
? () => { |
|
||||
onCollapse(true); |
|
||||
} |
|
||||
: undefined |
|
||||
} |
|
||||
> |
|
||||
{icon} |
|
||||
<span>{name}</span> |
|
||||
</Link> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* 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 ( |
|
||||
<SubMenu |
|
||||
title={ |
|
||||
item.icon ? ( |
|
||||
<span> |
|
||||
{getIcon(item.icon)} |
|
||||
<span>{item.name}</span> |
|
||||
</span> |
|
||||
) : ( |
|
||||
item.name |
|
||||
) |
|
||||
} |
|
||||
key={item.path} |
|
||||
> |
|
||||
{childrenItems} |
|
||||
</SubMenu> |
|
||||
); |
|
||||
} |
|
||||
return null; |
|
||||
} else { |
|
||||
return <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.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 => { |
|
||||
const { menuData } = this.props; |
|
||||
return 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, menuData, 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 ( |
|
||||
<Sider |
|
||||
trigger={null} |
|
||||
collapsible |
|
||||
collapsed={collapsed} |
|
||||
breakpoint="lg" |
|
||||
onCollapse={onCollapse} |
|
||||
width={256} |
|
||||
className={styles.sider} |
|
||||
> |
|
||||
<div className={styles.logo} key="logo"> |
|
||||
<Link to="/"> |
|
||||
<img src={logo} alt="logo" /> |
|
||||
<h1>Ant Design Pro</h1> |
|
||||
</Link> |
|
||||
</div> |
|
||||
<Menu |
|
||||
key="Menu" |
|
||||
theme="dark" |
|
||||
mode="inline" |
|
||||
{...menuProps} |
|
||||
onOpenChange={this.handleOpenChange} |
|
||||
selectedKeys={selectedKeys} |
|
||||
style={{ padding: '16px 0', width: '100%' }} |
|
||||
> |
|
||||
{this.getNavMenuItems(menuData)} |
|
||||
</Menu> |
|
||||
</Sider> |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue