diff --git a/src/common/menu.js b/src/common/menu.js index 21b58d7ae9ffe8b77e3b53d58e9db6eb56322918..50f8a46406f846851b9a4423428b9dfbdf444ab4 100644 --- a/src/common/menu.js +++ b/src/common/menu.js @@ -115,7 +115,7 @@ const menuData = [{ }], }]; -function formatter(data, parentPath = '', parentAuthority) { +function formatter(data, parentPath = '/', parentAuthority) { return data.map((item) => { let { path } = item; if (!isUrl(path)) { diff --git a/src/components/PageHeader/index.js b/src/components/PageHeader/index.js index df5148749dad22b35ebb0bd9122429e96ef5e813..22dac8705208e12ee45e146deb40649a3c7e515a 100644 --- a/src/components/PageHeader/index.js +++ b/src/components/PageHeader/index.js @@ -4,7 +4,7 @@ import pathToRegexp from 'path-to-regexp'; import { Breadcrumb, Tabs } from 'antd'; import classNames from 'classnames'; import styles from './index.less'; - +import { urlToList } from '../utils/pathTools'; const { TabPane } = Tabs; export function getBreadcrumb(breadcrumbNameMap, url) { @@ -19,14 +19,6 @@ export function getBreadcrumb(breadcrumbNameMap, url) { return breadcrumb || {}; } -// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] -export function urlToList(url) { - const urllist = url.split('/').filter(i => i); - return urllist.map((urlItem, index) => { - return `/${urllist.slice(0, index + 1).join('/')}`; - }); -} - export default class PageHeader extends PureComponent { static contextTypes = { routes: PropTypes.array, @@ -44,29 +36,35 @@ export default class PageHeader extends PureComponent { routes: this.props.routes || this.context.routes, params: this.props.params || this.context.params, routerLocation: this.props.location || this.context.location, - breadcrumbNameMap: this.props.breadcrumbNameMap || this.context.breadcrumbNameMap, + breadcrumbNameMap: + this.props.breadcrumbNameMap || this.context.breadcrumbNameMap, }; }; // Generated according to props - conversionFromProps= () => { + conversionFromProps = () => { const { - breadcrumbList, breadcrumbSeparator, linkElement = 'a', + breadcrumbList, + breadcrumbSeparator, + linkElement = 'a', } = this.props; return ( - + {breadcrumbList.map(item => ( - {item.href ? (createElement(linkElement, { - [linkElement === 'a' ? 'href' : 'to']: item.href, - }, item.title)) : item.title} + {item.href + ? createElement( + linkElement, + { + [linkElement === 'a' ? 'href' : 'to']: item.href, + }, + item.title, + ) + : item.title} - ))} + ))} ); - } + }; conversionFromLocation = (routerLocation, breadcrumbNameMap) => { const { breadcrumbSeparator, linkElement = 'a' } = this.props; // Convert the url to an array @@ -74,7 +72,8 @@ export default class PageHeader extends PureComponent { // Loop data mosaic routing const extraBreadcrumbItems = pathSnippets.map((url, index) => { const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url); - const isLinkable = (index !== pathSnippets.length - 1) && currentBreadcrumb.component; + const isLinkable = + index !== pathSnippets.length - 1 && currentBreadcrumb.component; return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? ( {createElement( @@ -88,26 +87,33 @@ export default class PageHeader extends PureComponent { // Add home breadcrumbs to your head extraBreadcrumbItems.unshift( - {createElement(linkElement, { - [linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')} - + {createElement( + linkElement, + { + [linkElement === 'a' ? 'href' : 'to']: '/', + }, + '首页', + )} + , ); return ( - + {extraBreadcrumbItems} ); - } + }; /** * 将参数转化为面包屑 * Convert parameters into breadcrumbs */ conversionBreadcrumbList = () => { const { breadcrumbList, breadcrumbSeparator } = this.props; - const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps(); + const { + routes, + params, + routerLocation, + breadcrumbNameMap, + } = this.getBreadcrumbProps(); if (breadcrumbList && breadcrumbList.length) { return this.conversionFromProps(); } @@ -130,24 +136,37 @@ export default class PageHeader extends PureComponent { return this.conversionFromLocation(routerLocation, breadcrumbNameMap); } return null; - } + }; // 渲染Breadcrumb 子节点 // Render the Breadcrumb child node itemRender = (route, params, routes, paths) => { const { linkElement = 'a' } = this.props; const last = routes.indexOf(route) === routes.length - 1; - return (last || !route.component) - ? {route.breadcrumbName} - : createElement(linkElement, { - href: paths.join('/') || '/', - to: paths.join('/') || '/', - }, route.breadcrumbName); - } + return last || !route.component ? ( + {route.breadcrumbName} + ) : ( + createElement( + linkElement, + { + href: paths.join('/') || '/', + to: paths.join('/') || '/', + }, + route.breadcrumbName, + ) + ); + }; render() { const { - title, logo, action, content, extraContent, - tabList, className, tabActiveKey, tabBarExtraContent, + title, + logo, + action, + content, + extraContent, + tabList, + className, + tabActiveKey, + tabBarExtraContent, } = this.props; const clsString = classNames(styles.pageHeader, className); @@ -175,12 +194,13 @@ export default class PageHeader extends PureComponent {
{content &&
{content}
} - {extraContent &&
{extraContent}
} + {extraContent && ( +
{extraContent}
+ )}
- { - tabList && + {tabList && tabList.length && ( - { - tabList.map(item => ) - } + {tabList.map(item => )} - ) - } + )} ); } diff --git a/src/components/PageHeader/index.test.js b/src/components/PageHeader/index.test.js index 385a3d31da4910474fd67b83a2c607b57b8c6dfc..d9aaa0004e7e682a690f2f3a59175abe9093a210 100644 --- a/src/components/PageHeader/index.test.js +++ b/src/components/PageHeader/index.test.js @@ -1,23 +1,5 @@ -import { getBreadcrumb, urlToList } from './index'; - -describe('test urlToList', () => { - it('A path', () => { - expect(urlToList('/userinfo')).toEqual(['/userinfo']); - }); - it('Secondary path', () => { - expect(urlToList('/userinfo/2144')).toEqual([ - '/userinfo', - '/userinfo/2144', - ]); - }); - it('Three paths', () => { - expect(urlToList('/userinfo/2144/addr')).toEqual([ - '/userinfo', - '/userinfo/2144', - '/userinfo/2144/addr', - ]); - }); -}); +import { getBreadcrumb } from './index'; +import { urlToList } from '../utils/pathTools'; const routerData = { '/dashboard/analysis': { @@ -36,17 +18,17 @@ const routerData = { describe('test getBreadcrumb', () => { it('Simple url', () => { expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual( - '分析页' + '分析页', ); }); it('Parameters url', () => { expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual( - '用户信息' + '用户信息', ); }); it('The middle parameter url', () => { expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual( - '收货订单' + '收货订单', ); }); it('Loop through the parameters', () => { diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js index 4811894da1f5d837a2a18307b58f2d81586c5944..df3bb488d99b923e35284b1336d87e4e934245ba 100644 --- a/src/components/SiderMenu/SiderMenu.js +++ b/src/components/SiderMenu/SiderMenu.js @@ -3,6 +3,7 @@ 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; @@ -21,10 +22,17 @@ const getIcon = (icon) => { return icon; }; +export const getMeunMatcheys = (flatMenuKeys, path) => { + return flatMenuKeys.filter((item) => { + return pathToRegexp(item).test(path); + }); +}; + export default class SiderMenu extends PureComponent { constructor(props) { super(props); this.menus = props.menuData; + this.flatMenuKeys = this.getFlatMenuKeys(props.menuData); this.state = { openKeys: this.getDefaultCollapsedSubMenus(props), }; @@ -43,30 +51,11 @@ export default class SiderMenu extends PureComponent { */ getDefaultCollapsedSubMenus(props) { const { location: { pathname } } = props || this.props; - // eg. /list/search/articles = > ['','list','search','articles'] - let snippets = pathname.split('/'); - // Delete the end - // eg. delete 'articles' - snippets.pop(); - // Delete the head - // eg. delete '' - snippets.shift(); - // eg. After the operation is completed, the array should be ['list','search'] - // eg. Forward the array as ['list','list/search'] - snippets = snippets.map((item, index) => { - // If the array length > 1 - if (index > 0) { - // eg. search => ['list','search'].join('/') - return snippets.slice(0, index + 1).join('/'); - } - // index 0 to not do anything - return item; - }); - snippets = snippets.map((item) => { - return this.getSelectedMenuKeys(`/${item}`)[0]; - }); - // eg. ['list','list/search'] - return snippets; + return urlToList(pathname) + .map((item) => { + return getMeunMatcheys(this.flatMenuKeys, item)[0]; + }) + .filter(item => item); } /** * Recursively flatten the data @@ -77,29 +66,17 @@ export default class SiderMenu extends PureComponent { let keys = []; menus.forEach((item) => { if (item.children) { - keys.push(item.path); keys = keys.concat(this.getFlatMenuKeys(item.children)); - } else { - keys.push(item.path); } + keys.push(item.path); }); return keys; } /** - * Get selected child nodes - * /user/chen => ['user','/user/:id'] + * 判断是否是http链接.返回 Link 或 a + * Judge whether it is http link.return a or Link + * @memberof SiderMenu */ - getSelectedMenuKeys = (path) => { - const flatMenuKeys = this.getFlatMenuKeys(this.menus); - return flatMenuKeys.filter((item) => { - return pathToRegexp(`/${item}(.*)`).test(path); - }); - } - /** - * 判断是否是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); @@ -108,7 +85,8 @@ export default class SiderMenu extends PureComponent { if (/^https?:\/\//.test(itemPath)) { return ( - {icon}{name} + {icon} + {name} ); } @@ -117,16 +95,23 @@ export default class SiderMenu extends PureComponent { to={itemPath} target={target} replace={itemPath === this.props.location.pathname} - onClick={this.props.isMobile ? () => { this.props.onCollapse(true); } : undefined} + onClick={ + this.props.isMobile + ? () => { + this.props.onCollapse(true); + } + : undefined + } > - {icon}{name} + {icon} + {name} ); - } + }; /** * get SubMenu or Item */ - getSubMenuOrItem=(item) => { + getSubMenuOrItem = (item) => { if (item.children && item.children.some(child => child.name)) { return ( {item.name} - ) : item.name - } + ) : ( + item.name + ) + } key={item.path} > {this.getNavMenuItems(item.children)} @@ -145,16 +132,14 @@ export default class SiderMenu extends PureComponent { ); } else { return ( - - {this.getMenuItemPath(item)} - + {this.getMenuItemPath(item)} ); } - } + }; /** - * 获得菜单子节点 - * @memberof SiderMenu - */ + * 获得菜单子节点 + * @memberof SiderMenu + */ getNavMenuItems = (menusData) => { if (!menusData) { return []; @@ -162,49 +147,57 @@ export default class SiderMenu extends PureComponent { 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); - } + .filter(item => item); + }; + // Get the currently selected menu + getSelectedMenuKeys = () => { + const { location: { pathname } } = this.props; + return urlToList(pathname).map(itemPath => + getMeunMatcheys(this.flatMenuKeys, itemPath).pop(), + ); + }; // conversion Path // 转化路径 - conversionPath=(path) => { + conversionPath = (path) => { if (path && path.indexOf('http') === 0) { return path; } else { return `/${path || ''}`.replace(/\/+/g, '/'); } - } + }; // permission to check checkPermissionItem = (authority, ItemDom) => { if (this.props.Authorized && this.props.Authorized.check) { const { check } = this.props.Authorized; - return check( - authority, - ItemDom - ); + return check(authority, ItemDom); } return ItemDom; - } + }; handleOpenChange = (openKeys) => { const lastOpenKey = openKeys[openKeys.length - 1]; const isMainMenu = this.menus.some( - item => lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey) + item => + lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey), ); this.setState({ openKeys: isMainMenu ? [lastOpenKey] : [...openKeys], }); - } + }; render() { - const { logo, collapsed, location: { pathname }, onCollapse } = this.props; + const { logo, collapsed, onCollapse } = this.props; const { openKeys } = this.state; // Don't show popup menu when it is been collapsed - const menuProps = collapsed ? {} : { - openKeys, - }; + const menuProps = collapsed + ? {} + : { + openKeys, + }; // if pathname can't match, use the nearest parent's key - let selectedKeys = this.getSelectedMenuKeys(pathname); + let selectedKeys = this.getSelectedMenuKeys(); if (!selectedKeys.length) { selectedKeys = [openKeys[openKeys.length - 1]]; } diff --git a/src/components/SiderMenu/SilderMenu.test.js b/src/components/SiderMenu/SilderMenu.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e6f1f55a8dc578a8ad156171e6b4be423b730abd --- /dev/null +++ b/src/components/SiderMenu/SilderMenu.test.js @@ -0,0 +1,36 @@ +import { getMeunMatcheys } from './SiderMenu'; + +const meun = [ + '/dashboard', + '/userinfo', + '/dashboard/name', + '/userinfo/:id', + '/userinfo/:id/info', +]; + +describe('test meun match', () => { + it('simple path', () => { + expect(getMeunMatcheys(meun, '/dashboard')).toEqual(['/dashboard']); + }); + it('error path', () => { + expect(getMeunMatcheys(meun, '/dashboardname')).toEqual([]); + }); + + it('Secondary path', () => { + expect(getMeunMatcheys(meun, '/dashboard/name')).toEqual([ + '/dashboard/name', + ]); + }); + + it('Parameter path', () => { + expect(getMeunMatcheys(meun, '/userinfo/2144')).toEqual([ + '/userinfo/:id', + ]); + }); + + it('three parameter path', () => { + expect(getMeunMatcheys(meun, '/userinfo/2144/info')).toEqual([ + '/userinfo/:id/info', + ]); + }); +}); diff --git a/src/components/utils/pathTools.js b/src/components/utils/pathTools.js new file mode 100644 index 0000000000000000000000000000000000000000..7d49400a41c6f975b072bb4cda467218be40e9c6 --- /dev/null +++ b/src/components/utils/pathTools.js @@ -0,0 +1,7 @@ +// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] +export function urlToList(url) { + const urllist = url.split('/').filter(i => i); + return urllist.map((urlItem, index) => { + return `/${urllist.slice(0, index + 1).join('/')}`; + }); +} diff --git a/src/components/utils/pathTools.test.js b/src/components/utils/pathTools.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4daf28b4b8438d7d03b671ca542858c025bb95bf --- /dev/null +++ b/src/components/utils/pathTools.test.js @@ -0,0 +1,20 @@ +import { urlToList } from './pathTools'; + +describe('test urlToList', () => { + it('A path', () => { + expect(urlToList('/userinfo')).toEqual(['/userinfo']); + }); + it('Secondary path', () => { + expect(urlToList('/userinfo/2144')).toEqual([ + '/userinfo', + '/userinfo/2144', + ]); + }); + it('Three paths', () => { + expect(urlToList('/userinfo/2144/addr')).toEqual([ + '/userinfo', + '/userinfo/2144', + '/userinfo/2144/addr', + ]); + }); +});