diff --git a/.gitignore b/.gitignore
index fcac945fba1338aa06a49ec7b976af0cd3f08977..bdba67fc8fb7b7f8e321508ec03ef324f7079070 100755
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ yarn-error.log
.idea
yarn.lock
package-lock.json
+jsconfig.json
diff --git a/package.json b/package.json
index e227c5bd32c08dba96fc834b931504b35620164e..8db9eb0b8f2206f0e3f6678430afb670bcecbfc6 100755
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"moment": "^2.19.1",
"numeral": "^2.0.6",
"omit.js": "^1.0.0",
+ "path-to-regexp": "^2.1.0",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"rc-drawer-menu": "^0.5.0",
diff --git a/src/common/router.js b/src/common/router.js
index 0b45dcd4cc1baa9c67ea9eeb1191479b584ee891..5df24776509e6f111ce44204585a9bb04d878530 100644
--- a/src/common/router.js
+++ b/src/common/router.js
@@ -1,5 +1,6 @@
import { createElement } from 'react';
import dynamic from 'dva/dynamic';
+import pathToRegexp from 'path-to-regexp';
import { getMenuData } from './menu';
let routerDataCache;
@@ -165,14 +166,31 @@ export const getRouterData = (app) => {
};
// Get name from ./menu.js or just set it in the router data.
const menuData = getFlatMenuData(getMenuData());
+
+ // Route configuration data
+ // eg. {name,authority ...routerConfig }
const routerData = {};
- Object.keys(routerConfig).forEach((item) => {
- const menuItem = menuData[item.replace(/^\//, '')] || {};
- routerData[item] = {
- ...routerConfig[item],
- name: routerConfig[item].name || menuItem.name,
- authority: routerConfig[item].authority || menuItem.authority,
+ // The route matches the menu
+ Object.keys(routerConfig).forEach((path) => {
+ // Regular match item name
+ // eg. router /user/:id === /user/chen
+ const pathRegexp = pathToRegexp(path);
+ const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`/${key}`));
+ let menuItem = {};
+ // If menuKey is not empty
+ if (menuKey) {
+ menuItem = menuData[menuKey];
+ }
+ let router = routerConfig[path];
+ // If you need to configure complex parameter routing,
+ // https://github.com/ant-design/ant-design-pro-site/blob/master/docs/router-and-nav.md#%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E8%B7%AF%E7%94%B1%E8%8F%9C%E5%8D%95
+ // eg . /list/:type/user/info/:id
+ router = {
+ ...router,
+ name: router.name || menuItem.name,
+ authority: router.authority || menuItem.authority,
};
+ routerData[path] = router;
});
return routerData;
};
diff --git a/src/components/PageHeader/index.js b/src/components/PageHeader/index.js
index 57156fc1c0fb9a5ba04cb7ef397b63f587e5a486..4be9af62f4ee7f11e3c35315935125b281dd0897 100644
--- a/src/components/PageHeader/index.js
+++ b/src/components/PageHeader/index.js
@@ -1,24 +1,17 @@
import React, { PureComponent, createElement } from 'react';
import PropTypes from 'prop-types';
+import pathToRegexp from 'path-to-regexp';
import { Breadcrumb, Tabs } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
+
const { TabPane } = Tabs;
function getBreadcrumb(breadcrumbNameMap, url) {
- if (breadcrumbNameMap[url]) {
- return breadcrumbNameMap[url];
- }
- const urlWithoutSplash = url.replace(/\/$/, '');
- if (breadcrumbNameMap[urlWithoutSplash]) {
- return breadcrumbNameMap[urlWithoutSplash];
- }
let breadcrumb = {};
Object.keys(breadcrumbNameMap).forEach((item) => {
- const itemRegExpStr = `^${item.replace(/:[\w-]+/g, '[\\w-]+')}$`;
- const itemRegExp = new RegExp(itemRegExpStr);
- if (itemRegExp.test(url)) {
+ if (pathToRegexp(item).test(url)) {
breadcrumb = breadcrumbNameMap[item];
}
});
@@ -41,10 +34,90 @@ export default class PageHeader extends PureComponent {
return {
routes: this.props.routes || this.context.routes,
params: this.props.params || this.context.params,
- location: this.props.location || this.context.location,
+ routerLocation: this.props.location || this.context.location,
breadcrumbNameMap: this.props.breadcrumbNameMap || this.context.breadcrumbNameMap,
};
};
+ // Generated according to props
+ conversionFromProps= () => {
+ const {
+ breadcrumbList, linkElement = 'a',
+ } = this.props;
+ return (
+
+ {breadcrumbList.map(item => (
+
+ {item.href ? (createElement(linkElement, {
+ [linkElement === 'a' ? 'href' : 'to']: item.href,
+ }, item.title)) : item.title}
+
+ ))}
+
+ );
+ }
+ conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
+ const { linkElement = 'a' } = this.props;
+ // Convert the path to an array
+ const pathSnippets = routerLocation.pathname.split('/').filter(i => i);
+ // Loop data mosaic routing
+ const extraBreadcrumbItems = pathSnippets.map((_, index) => {
+ const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
+ const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
+ const isLinkable = (index !== pathSnippets.length - 1) && currentBreadcrumb.component;
+ return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
+
+ {createElement(
+ isLinkable ? linkElement : 'span',
+ { [linkElement === 'a' ? 'href' : 'to']: url },
+ currentBreadcrumb.name,
+ )}
+
+ ) : null;
+ });
+ // Add home breadcrumbs to your head
+ extraBreadcrumbItems.unshift(
+
+ {createElement(linkElement, {
+ [linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')}
+
+ );
+ return (
+
+ {extraBreadcrumbItems}
+
+ );
+ }
+ /**
+ * 将参数转化为面包屑
+ * Convert parameters into breadcrumbs
+ */
+ conversionBreadcrumbList = () => {
+ const { breadcrumbList } = this.props;
+ const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps();
+ if (breadcrumbList && breadcrumbList.length) {
+ return this.conversionFromProps();
+ }
+ // 如果传入 routes 和 params 属性
+ // If pass routes and params attributes
+ if (routes && params) {
+ return (
+ route.breadcrumbName)}
+ params={params}
+ itemRender={this.itemRender}
+ />
+ );
+ }
+ // 根据 location 生成 面包屑
+ // Generate breadcrumbs based on location
+ if (location && location.pathname) {
+ 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;
@@ -55,77 +128,19 @@ export default class PageHeader extends PureComponent {
to: paths.join('/') || '/',
}, route.breadcrumbName);
}
+
render() {
- const { routes, params, location, breadcrumbNameMap } = this.getBreadcrumbProps();
const {
title, logo, action, content, extraContent,
- breadcrumbList, tabList, className, linkElement = 'a',
- tabActiveKey,
+ tabList, className, tabActiveKey,
} = this.props;
const clsString = classNames(styles.pageHeader, className);
- let breadcrumb;
- if (breadcrumbList && breadcrumbList.length) {
- breadcrumb = (
-
- {
- breadcrumbList.map(item => (
-
- {item.href ? (
- createElement(linkElement, {
- [linkElement === 'a' ? 'href' : 'to']: item.href,
- }, item.title)
- ) : item.title}
- )
- )
- }
-
- );
- } else if (routes && params) {
- breadcrumb = (
- route.breadcrumbName)}
- params={params}
- itemRender={this.itemRender}
- />
- );
- } else if (location && location.pathname) {
- const pathSnippets = location.pathname.split('/').filter(i => i);
- const extraBreadcrumbItems = pathSnippets.map((_, index) => {
- const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
- const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
- const isLinkable = (index !== pathSnippets.length - 1) && currentBreadcrumb.component;
- return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
-
- {createElement(
- isLinkable ? linkElement : 'span',
- { [linkElement === 'a' ? 'href' : 'to']: url },
- currentBreadcrumb.name,
- )}
-
- ) : null;
- });
- const breadcrumbItems = [(
-
- {createElement(linkElement, {
- [linkElement === 'a' ? 'href' : 'to']: '/',
- }, '首页')}
-
- )].concat(extraBreadcrumbItems);
- breadcrumb = (
-
- {breadcrumbItems}
-
- );
- } else {
- breadcrumb = null;
- }
let tabDefaultValue;
if (tabActiveKey !== undefined && tabList) {
tabDefaultValue = tabList.filter(item => item.default)[0] || tabList[0];
}
-
+ const breadcrumb = this.conversionBreadcrumbList();
const activeKeyProps = {
defaultActiveKey: tabDefaultValue && tabDefaultValue.key,
};
diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js
index 71827341ccb6830869940951e9f7506db0ef65d1..279f1b2719cdbdd180d4b9aa7442cf994efa80c7 100644
--- a/src/components/SiderMenu/SiderMenu.js
+++ b/src/components/SiderMenu/SiderMenu.js
@@ -1,5 +1,6 @@
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';
@@ -35,22 +36,43 @@ export default class SiderMenu extends PureComponent {
});
}
}
+ /**
+ * Convert pathname to openKeys
+ * /list/search/articles = > ['list','/list/search']
+ * @param props
+ */
getDefaultCollapsedSubMenus(props) {
const { location: { pathname } } = props || this.props;
- const snippets = pathname.split('/').slice(1, -1);
- const currentPathSnippets = snippets.map((item, index) => {
- const arr = snippets.filter((_, i) => i <= index);
- return arr.join('/');
+ // 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;
});
- let currentMenuSelectedKeys = [];
- currentPathSnippets.forEach((item) => {
- currentMenuSelectedKeys = currentMenuSelectedKeys.concat(this.getSelectedMenuKeys(item));
+ snippets = snippets.map((item) => {
+ return this.getSelectedMenuKeys(`/${item}`)[0];
});
- if (currentMenuSelectedKeys.length === 0) {
- return ['dashboard'];
- }
- return currentMenuSelectedKeys;
+ // eg. ['list','list/search']
+ return snippets;
}
+ /**
+ * Recursively flatten the data
+ * [{path:string},{path:string}] => {path,path2}
+ * @param menus
+ */
getFlatMenuKeys(menus) {
let keys = [];
menus.forEach((item) => {
@@ -63,18 +85,14 @@ export default class SiderMenu extends PureComponent {
});
return keys;
}
+ /**
+ * Get selected child nodes
+ * /user/chen => /user/:id
+ */
getSelectedMenuKeys = (path) => {
const flatMenuKeys = this.getFlatMenuKeys(this.menus);
- if (flatMenuKeys.indexOf(path.replace(/^\//, '')) > -1) {
- return [path.replace(/^\//, '')];
- }
- if (flatMenuKeys.indexOf(path.replace(/^\//, '').replace(/\/$/, '')) > -1) {
- return [path.replace(/^\//, '').replace(/\/$/, '')];
- }
return flatMenuKeys.filter((item) => {
- const itemRegExpStr = `^${item.replace(/:[\w-]+/g, '[\\w-]+')}$`;
- const itemRegExp = new RegExp(itemRegExpStr);
- return itemRegExp.test(path.replace(/^\//, '').replace(/\/$/, ''));
+ return pathToRegexp(`/${item}`).test(path);
});
}
/**
@@ -120,14 +138,14 @@ export default class SiderMenu extends PureComponent {
) : item.name
}
- key={item.key || item.path}
+ key={item.path}
>
{this.getNavMenuItems(item.children)}
);
} else {
return (
-
+
{this.getMenuItemPath(item)}
);
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 46a0a0f28fd364edf2c9a3424f4869ef7e5a0e7b..93fb54f4a0bcff5dcc04f3cd639f34f86aaa6fcc 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -93,6 +93,7 @@ export function digitUppercase(n) {
return s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');
}
+
function getRelation(str1, str2) {
if (str1 === str2) {
console.warn('Two path are equal!'); // eslint-disable-line
@@ -107,20 +108,36 @@ function getRelation(str1, str2) {
return 3;
}
-export function getRoutes(path, routerData) {
- let routes = Object.keys(routerData).filter(routePath =>
- routePath.indexOf(path) === 0 && routePath !== path);
- routes = routes.map(item => item.replace(path, ''));
+function getRenderArr(routes) {
let renderArr = [];
renderArr.push(routes[0]);
for (let i = 1; i < routes.length; i += 1) {
let isAdd = false;
+ // 是否包含
isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
+ // 去重
renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1);
if (isAdd) {
renderArr.push(routes[i]);
}
}
+ return renderArr;
+}
+
+/**
+ * Get router routing configuration
+ * { path:{name,...param}}=>Array<{name,path ...param}>
+ * @param {string} path
+ * @param {routerData} routerData
+ */
+export function getRoutes(path, routerData) {
+ let routes = Object.keys(routerData).filter(routePath =>
+ routePath.indexOf(path) === 0 && routePath !== path);
+ // Replace path to '' eg. path='user' /user/name => name
+ routes = routes.map(item => item.replace(path, ''));
+ // Get the route to be rendered to remove the deep rendering
+ const renderArr = getRenderArr(routes);
+ // Conversion and stitching parameters
const renderRoutes = renderArr.map((item) => {
const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
return {