Unverified Commit 3b826259 authored by 陈帅's avatar 陈帅 Committed by GitHub

add meun test and Modify part of the logic (#917)

parent bd391d7e
...@@ -115,7 +115,7 @@ const menuData = [{ ...@@ -115,7 +115,7 @@ const menuData = [{
}], }],
}]; }];
function formatter(data, parentPath = '', parentAuthority) { function formatter(data, parentPath = '/', parentAuthority) {
return data.map((item) => { return data.map((item) => {
let { path } = item; let { path } = item;
if (!isUrl(path)) { if (!isUrl(path)) {
......
...@@ -4,7 +4,7 @@ import pathToRegexp from 'path-to-regexp'; ...@@ -4,7 +4,7 @@ import pathToRegexp from 'path-to-regexp';
import { Breadcrumb, Tabs } from 'antd'; import { Breadcrumb, Tabs } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './index.less'; import styles from './index.less';
import { urlToList } from '../utils/pathTools';
const { TabPane } = Tabs; const { TabPane } = Tabs;
export function getBreadcrumb(breadcrumbNameMap, url) { export function getBreadcrumb(breadcrumbNameMap, url) {
...@@ -19,14 +19,6 @@ export function getBreadcrumb(breadcrumbNameMap, url) { ...@@ -19,14 +19,6 @@ export function getBreadcrumb(breadcrumbNameMap, url) {
return breadcrumb || {}; 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 { export default class PageHeader extends PureComponent {
static contextTypes = { static contextTypes = {
routes: PropTypes.array, routes: PropTypes.array,
...@@ -44,29 +36,35 @@ export default class PageHeader extends PureComponent { ...@@ -44,29 +36,35 @@ export default class PageHeader extends PureComponent {
routes: this.props.routes || this.context.routes, routes: this.props.routes || this.context.routes,
params: this.props.params || this.context.params, params: this.props.params || this.context.params,
routerLocation: this.props.location || this.context.location, 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 // Generated according to props
conversionFromProps= () => { conversionFromProps = () => {
const { const {
breadcrumbList, breadcrumbSeparator, linkElement = 'a', breadcrumbList,
breadcrumbSeparator,
linkElement = 'a',
} = this.props; } = this.props;
return ( return (
<Breadcrumb <Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
{breadcrumbList.map(item => ( {breadcrumbList.map(item => (
<Breadcrumb.Item key={item.title}> <Breadcrumb.Item key={item.title}>
{item.href ? (createElement(linkElement, { {item.href
? createElement(
linkElement,
{
[linkElement === 'a' ? 'href' : 'to']: item.href, [linkElement === 'a' ? 'href' : 'to']: item.href,
}, item.title)) : item.title} },
item.title,
)
: item.title}
</Breadcrumb.Item> </Breadcrumb.Item>
))} ))}
</Breadcrumb> </Breadcrumb>
); );
} };
conversionFromLocation = (routerLocation, breadcrumbNameMap) => { conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
const { breadcrumbSeparator, linkElement = 'a' } = this.props; const { breadcrumbSeparator, linkElement = 'a' } = this.props;
// Convert the url to an array // Convert the url to an array
...@@ -74,7 +72,8 @@ export default class PageHeader extends PureComponent { ...@@ -74,7 +72,8 @@ export default class PageHeader extends PureComponent {
// Loop data mosaic routing // Loop data mosaic routing
const extraBreadcrumbItems = pathSnippets.map((url, index) => { const extraBreadcrumbItems = pathSnippets.map((url, index) => {
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url); 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 ? ( return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
<Breadcrumb.Item key={url}> <Breadcrumb.Item key={url}>
{createElement( {createElement(
...@@ -88,26 +87,33 @@ export default class PageHeader extends PureComponent { ...@@ -88,26 +87,33 @@ export default class PageHeader extends PureComponent {
// Add home breadcrumbs to your head // Add home breadcrumbs to your head
extraBreadcrumbItems.unshift( extraBreadcrumbItems.unshift(
<Breadcrumb.Item key="home"> <Breadcrumb.Item key="home">
{createElement(linkElement, { {createElement(
[linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')} linkElement,
</Breadcrumb.Item> {
[linkElement === 'a' ? 'href' : 'to']: '/',
},
'首页',
)}
</Breadcrumb.Item>,
); );
return ( return (
<Breadcrumb <Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
{extraBreadcrumbItems} {extraBreadcrumbItems}
</Breadcrumb> </Breadcrumb>
); );
} };
/** /**
* 将参数转化为面包屑 * 将参数转化为面包屑
* Convert parameters into breadcrumbs * Convert parameters into breadcrumbs
*/ */
conversionBreadcrumbList = () => { conversionBreadcrumbList = () => {
const { breadcrumbList, breadcrumbSeparator } = this.props; const { breadcrumbList, breadcrumbSeparator } = this.props;
const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps(); const {
routes,
params,
routerLocation,
breadcrumbNameMap,
} = this.getBreadcrumbProps();
if (breadcrumbList && breadcrumbList.length) { if (breadcrumbList && breadcrumbList.length) {
return this.conversionFromProps(); return this.conversionFromProps();
} }
...@@ -130,24 +136,37 @@ export default class PageHeader extends PureComponent { ...@@ -130,24 +136,37 @@ export default class PageHeader extends PureComponent {
return this.conversionFromLocation(routerLocation, breadcrumbNameMap); return this.conversionFromLocation(routerLocation, breadcrumbNameMap);
} }
return null; return null;
} };
// 渲染Breadcrumb 子节点 // 渲染Breadcrumb 子节点
// Render the Breadcrumb child node // Render the Breadcrumb child node
itemRender = (route, params, routes, paths) => { itemRender = (route, params, routes, paths) => {
const { linkElement = 'a' } = this.props; const { linkElement = 'a' } = this.props;
const last = routes.indexOf(route) === routes.length - 1; const last = routes.indexOf(route) === routes.length - 1;
return (last || !route.component) return last || !route.component ? (
? <span>{route.breadcrumbName}</span> <span>{route.breadcrumbName}</span>
: createElement(linkElement, { ) : (
createElement(
linkElement,
{
href: paths.join('/') || '/', href: paths.join('/') || '/',
to: paths.join('/') || '/', to: paths.join('/') || '/',
}, route.breadcrumbName); },
} route.breadcrumbName,
)
);
};
render() { render() {
const { const {
title, logo, action, content, extraContent, title,
tabList, className, tabActiveKey, tabBarExtraContent, logo,
action,
content,
extraContent,
tabList,
className,
tabActiveKey,
tabBarExtraContent,
} = this.props; } = this.props;
const clsString = classNames(styles.pageHeader, className); const clsString = classNames(styles.pageHeader, className);
...@@ -175,12 +194,13 @@ export default class PageHeader extends PureComponent { ...@@ -175,12 +194,13 @@ export default class PageHeader extends PureComponent {
</div> </div>
<div className={styles.row}> <div className={styles.row}>
{content && <div className={styles.content}>{content}</div>} {content && <div className={styles.content}>{content}</div>}
{extraContent && <div className={styles.extraContent}>{extraContent}</div>} {extraContent && (
<div className={styles.extraContent}>{extraContent}</div>
)}
</div> </div>
</div> </div>
</div> </div>
{ {tabList &&
tabList &&
tabList.length && ( tabList.length && (
<Tabs <Tabs
className={styles.tabs} className={styles.tabs}
...@@ -188,12 +208,9 @@ export default class PageHeader extends PureComponent { ...@@ -188,12 +208,9 @@ export default class PageHeader extends PureComponent {
onChange={this.onChange} onChange={this.onChange}
tabBarExtraContent={tabBarExtraContent} tabBarExtraContent={tabBarExtraContent}
> >
{ {tabList.map(item => <TabPane tab={item.tab} key={item.key} />)}
tabList.map(item => <TabPane tab={item.tab} key={item.key} />)
}
</Tabs> </Tabs>
) )}
}
</div> </div>
); );
} }
......
import { getBreadcrumb, urlToList } from './index'; import { getBreadcrumb } from './index';
import { urlToList } from '../utils/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',
]);
});
});
const routerData = { const routerData = {
'/dashboard/analysis': { '/dashboard/analysis': {
...@@ -36,17 +18,17 @@ const routerData = { ...@@ -36,17 +18,17 @@ const routerData = {
describe('test getBreadcrumb', () => { describe('test getBreadcrumb', () => {
it('Simple url', () => { it('Simple url', () => {
expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual( expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual(
'分析页' '分析页',
); );
}); });
it('Parameters url', () => { it('Parameters url', () => {
expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual( expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual(
'用户信息' '用户信息',
); );
}); });
it('The middle parameter url', () => { it('The middle parameter url', () => {
expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual( expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual(
'收货订单' '收货订单',
); );
}); });
it('Loop through the parameters', () => { it('Loop through the parameters', () => {
......
...@@ -3,6 +3,7 @@ import { Layout, Menu, Icon } from 'antd'; ...@@ -3,6 +3,7 @@ import { Layout, Menu, Icon } from 'antd';
import pathToRegexp from 'path-to-regexp'; import pathToRegexp from 'path-to-regexp';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import styles from './index.less'; import styles from './index.less';
import { urlToList } from '../utils/pathTools';
const { Sider } = Layout; const { Sider } = Layout;
const { SubMenu } = Menu; const { SubMenu } = Menu;
...@@ -21,10 +22,17 @@ const getIcon = (icon) => { ...@@ -21,10 +22,17 @@ const getIcon = (icon) => {
return icon; return icon;
}; };
export const getMeunMatcheys = (flatMenuKeys, path) => {
return flatMenuKeys.filter((item) => {
return pathToRegexp(item).test(path);
});
};
export default class SiderMenu extends PureComponent { export default class SiderMenu extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.menus = props.menuData; this.menus = props.menuData;
this.flatMenuKeys = this.getFlatMenuKeys(props.menuData);
this.state = { this.state = {
openKeys: this.getDefaultCollapsedSubMenus(props), openKeys: this.getDefaultCollapsedSubMenus(props),
}; };
...@@ -43,30 +51,11 @@ export default class SiderMenu extends PureComponent { ...@@ -43,30 +51,11 @@ export default class SiderMenu extends PureComponent {
*/ */
getDefaultCollapsedSubMenus(props) { getDefaultCollapsedSubMenus(props) {
const { location: { pathname } } = props || this.props; const { location: { pathname } } = props || this.props;
// eg. /list/search/articles = > ['','list','search','articles'] return urlToList(pathname)
let snippets = pathname.split('/'); .map((item) => {
// Delete the end return getMeunMatcheys(this.flatMenuKeys, item)[0];
// eg. delete 'articles' })
snippets.pop(); .filter(item => item);
// 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;
} }
/** /**
* Recursively flatten the data * Recursively flatten the data
...@@ -77,24 +66,12 @@ export default class SiderMenu extends PureComponent { ...@@ -77,24 +66,12 @@ export default class SiderMenu extends PureComponent {
let keys = []; let keys = [];
menus.forEach((item) => { menus.forEach((item) => {
if (item.children) { if (item.children) {
keys.push(item.path);
keys = keys.concat(this.getFlatMenuKeys(item.children)); keys = keys.concat(this.getFlatMenuKeys(item.children));
} else {
keys.push(item.path);
} }
keys.push(item.path);
}); });
return keys; return keys;
} }
/**
* Get selected child nodes
* /user/chen => ['user','/user/:id']
*/
getSelectedMenuKeys = (path) => {
const flatMenuKeys = this.getFlatMenuKeys(this.menus);
return flatMenuKeys.filter((item) => {
return pathToRegexp(`/${item}(.*)`).test(path);
});
}
/** /**
* 判断是否是http链接.返回 Link 或 a * 判断是否是http链接.返回 Link 或 a
* Judge whether it is http link.return a or Link * Judge whether it is http link.return a or Link
...@@ -108,7 +85,8 @@ export default class SiderMenu extends PureComponent { ...@@ -108,7 +85,8 @@ export default class SiderMenu extends PureComponent {
if (/^https?:\/\//.test(itemPath)) { if (/^https?:\/\//.test(itemPath)) {
return ( return (
<a href={itemPath} target={target}> <a href={itemPath} target={target}>
{icon}<span>{name}</span> {icon}
<span>{name}</span>
</a> </a>
); );
} }
...@@ -117,16 +95,23 @@ export default class SiderMenu extends PureComponent { ...@@ -117,16 +95,23 @@ export default class SiderMenu extends PureComponent {
to={itemPath} to={itemPath}
target={target} target={target}
replace={itemPath === this.props.location.pathname} 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}<span>{name}</span> {icon}
<span>{name}</span>
</Link> </Link>
); );
} };
/** /**
* get SubMenu or Item * get SubMenu or Item
*/ */
getSubMenuOrItem=(item) => { getSubMenuOrItem = (item) => {
if (item.children && item.children.some(child => child.name)) { if (item.children && item.children.some(child => child.name)) {
return ( return (
<SubMenu <SubMenu
...@@ -136,7 +121,9 @@ export default class SiderMenu extends PureComponent { ...@@ -136,7 +121,9 @@ export default class SiderMenu extends PureComponent {
{getIcon(item.icon)} {getIcon(item.icon)}
<span>{item.name}</span> <span>{item.name}</span>
</span> </span>
) : item.name ) : (
item.name
)
} }
key={item.path} key={item.path}
> >
...@@ -145,12 +132,10 @@ export default class SiderMenu extends PureComponent { ...@@ -145,12 +132,10 @@ export default class SiderMenu extends PureComponent {
); );
} else { } else {
return ( return (
<Menu.Item key={item.path}> <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>
{this.getMenuItemPath(item)}
</Menu.Item>
); );
} }
} };
/** /**
* 获得菜单子节点 * 获得菜单子节点
* @memberof SiderMenu * @memberof SiderMenu
...@@ -162,49 +147,57 @@ export default class SiderMenu extends PureComponent { ...@@ -162,49 +147,57 @@ export default class SiderMenu extends PureComponent {
return menusData return menusData
.filter(item => item.name && !item.hideInMenu) .filter(item => item.name && !item.hideInMenu)
.map((item) => { .map((item) => {
// make dom
const ItemDom = this.getSubMenuOrItem(item); const ItemDom = this.getSubMenuOrItem(item);
return this.checkPermissionItem(item.authority, ItemDom); 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 // conversion Path
// 转化路径 // 转化路径
conversionPath=(path) => { conversionPath = (path) => {
if (path && path.indexOf('http') === 0) { if (path && path.indexOf('http') === 0) {
return path; return path;
} else { } else {
return `/${path || ''}`.replace(/\/+/g, '/'); return `/${path || ''}`.replace(/\/+/g, '/');
} }
} };
// permission to check // permission to check
checkPermissionItem = (authority, ItemDom) => { checkPermissionItem = (authority, ItemDom) => {
if (this.props.Authorized && this.props.Authorized.check) { if (this.props.Authorized && this.props.Authorized.check) {
const { check } = this.props.Authorized; const { check } = this.props.Authorized;
return check( return check(authority, ItemDom);
authority,
ItemDom
);
} }
return ItemDom; return ItemDom;
} };
handleOpenChange = (openKeys) => { handleOpenChange = (openKeys) => {
const lastOpenKey = openKeys[openKeys.length - 1]; const lastOpenKey = openKeys[openKeys.length - 1];
const isMainMenu = this.menus.some( const isMainMenu = this.menus.some(
item => lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey) item =>
lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey),
); );
this.setState({ this.setState({
openKeys: isMainMenu ? [lastOpenKey] : [...openKeys], openKeys: isMainMenu ? [lastOpenKey] : [...openKeys],
}); });
} };
render() { render() {
const { logo, collapsed, location: { pathname }, onCollapse } = this.props; const { logo, collapsed, onCollapse } = this.props;
const { openKeys } = this.state; const { openKeys } = this.state;
// Don't show popup menu when it is been collapsed // Don't show popup menu when it is been collapsed
const menuProps = collapsed ? {} : { const menuProps = collapsed
? {}
: {
openKeys, openKeys,
}; };
// if pathname can't match, use the nearest parent's key // if pathname can't match, use the nearest parent's key
let selectedKeys = this.getSelectedMenuKeys(pathname); let selectedKeys = this.getSelectedMenuKeys();
if (!selectedKeys.length) { if (!selectedKeys.length) {
selectedKeys = [openKeys[openKeys.length - 1]]; selectedKeys = [openKeys[openKeys.length - 1]];
} }
......
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',
]);
});
});
// /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('/')}`;
});
}
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',
]);
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment