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 = [{
}],
}];
function formatter(data, parentPath = '', parentAuthority) {
function formatter(data, parentPath = '/', parentAuthority) {
return data.map((item) => {
let { path } = item;
if (!isUrl(path)) {
......
......@@ -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 (
<Breadcrumb
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
{breadcrumbList.map(item => (
<Breadcrumb.Item key={item.title}>
{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}
</Breadcrumb.Item>
))}
))}
</Breadcrumb>
);
}
};
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 ? (
<Breadcrumb.Item key={url}>
{createElement(
......@@ -88,26 +87,33 @@ export default class PageHeader extends PureComponent {
// Add home breadcrumbs to your head
extraBreadcrumbItems.unshift(
<Breadcrumb.Item key="home">
{createElement(linkElement, {
[linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')}
</Breadcrumb.Item>
{createElement(
linkElement,
{
[linkElement === 'a' ? 'href' : 'to']: '/',
},
'首页',
)}
</Breadcrumb.Item>,
);
return (
<Breadcrumb
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
{extraBreadcrumbItems}
</Breadcrumb>
);
}
};
/**
* 将参数转化为面包屑
* 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)
? <span>{route.breadcrumbName}</span>
: createElement(linkElement, {
href: paths.join('/') || '/',
to: paths.join('/') || '/',
}, route.breadcrumbName);
}
return last || !route.component ? (
<span>{route.breadcrumbName}</span>
) : (
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 {
</div>
<div className={styles.row}>
{content && <div className={styles.content}>{content}</div>}
{extraContent && <div className={styles.extraContent}>{extraContent}</div>}
{extraContent && (
<div className={styles.extraContent}>{extraContent}</div>
)}
</div>
</div>
</div>
{
tabList &&
{tabList &&
tabList.length && (
<Tabs
className={styles.tabs}
......@@ -188,12 +208,9 @@ export default class PageHeader extends PureComponent {
onChange={this.onChange}
tabBarExtraContent={tabBarExtraContent}
>
{
tabList.map(item => <TabPane tab={item.tab} key={item.key} />)
}
{tabList.map(item => <TabPane tab={item.tab} key={item.key} />)}
</Tabs>
)
}
)}
</div>
);
}
......
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', () => {
......
......@@ -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 (
<a href={itemPath} target={target}>
{icon}<span>{name}</span>
{icon}
<span>{name}</span>
</a>
);
}
......@@ -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}<span>{name}</span>
{icon}
<span>{name}</span>
</Link>
);
}
};
/**
* get SubMenu or Item
*/
getSubMenuOrItem=(item) => {
getSubMenuOrItem = (item) => {
if (item.children && item.children.some(child => child.name)) {
return (
<SubMenu
......@@ -136,8 +121,10 @@ export default class SiderMenu extends PureComponent {
{getIcon(item.icon)}
<span>{item.name}</span>
</span>
) : item.name
}
) : (
item.name
)
}
key={item.path}
>
{this.getNavMenuItems(item.children)}
......@@ -145,16 +132,14 @@ export default class SiderMenu extends PureComponent {
);
} else {
return (
<Menu.Item key={item.path}>
{this.getMenuItemPath(item)}
</Menu.Item>
<Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.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]];
}
......
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