Quellcode durchsuchen

fix parameters match warning (#613)

* fix parameters match warning

* Use path-to-regexp to match the path

* use regular expressions to match the path

* update doc

* add getSelectedMenuKeys
陈帅 vor 8 Jahren
Ursprung
Commit
3f2263d45a
6 geänderte Dateien mit 174 neuen und 104 gelöschten Zeilen
  1. 1 0
      .gitignore
  2. 1 0
      package.json
  3. 24 6
      src/common/router.js
  4. 87 72
      src/components/PageHeader/index.js
  5. 40 22
      src/components/SiderMenu/SiderMenu.js
  6. 21 4
      src/utils/utils.js

+ 1 - 0
.gitignore

@@ -18,3 +18,4 @@ yarn-error.log
 .idea
 yarn.lock
 package-lock.json
+jsconfig.json

+ 1 - 0
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",

+ 24 - 6
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;
 };

+ 87 - 72
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 (
+      <Breadcrumb className={styles.breadcrumb}>
+        {breadcrumbList.map(item => (
+          <Breadcrumb.Item key={item.title}>
+            {item.href ? (createElement(linkElement, {
+          [linkElement === 'a' ? 'href' : 'to']: item.href,
+        }, item.title)) : item.title}
+          </Breadcrumb.Item>
+      ))}
+      </Breadcrumb>
+    );
+  }
+  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 ? (
+        <Breadcrumb.Item key={url}>
+          {createElement(
+            isLinkable ? linkElement : 'span',
+            { [linkElement === 'a' ? 'href' : 'to']: url },
+            currentBreadcrumb.name,
+          )}
+        </Breadcrumb.Item>
+      ) : null;
+    });
+    // Add home breadcrumbs to your head
+    extraBreadcrumbItems.unshift(
+      <Breadcrumb.Item key="home">
+        {createElement(linkElement, {
+        [linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')}
+      </Breadcrumb.Item>
+    );
+    return (
+      <Breadcrumb className={styles.breadcrumb}>
+        {extraBreadcrumbItems}
+      </Breadcrumb>
+    );
+  }
+  /**
+   * 将参数转化为面包屑
+   * 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 (
+        <Breadcrumb
+          className={styles.breadcrumb}
+          routes={routes.filter(route => 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 = (
-        <Breadcrumb className={styles.breadcrumb}>
-          {
-            breadcrumbList.map(item => (
-              <Breadcrumb.Item key={item.title}>
-                {item.href ? (
-                  createElement(linkElement, {
-                    [linkElement === 'a' ? 'href' : 'to']: item.href,
-                  }, item.title)
-                ) : item.title}
-              </Breadcrumb.Item>)
-            )
-          }
-        </Breadcrumb>
-      );
-    } else if (routes && params) {
-      breadcrumb = (
-        <Breadcrumb
-          className={styles.breadcrumb}
-          routes={routes.filter(route => 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 ? (
-          <Breadcrumb.Item key={url}>
-            {createElement(
-              isLinkable ? linkElement : 'span',
-              { [linkElement === 'a' ? 'href' : 'to']: url },
-              currentBreadcrumb.name,
-            )}
-          </Breadcrumb.Item>
-        ) : null;
-      });
-      const breadcrumbItems = [(
-        <Breadcrumb.Item key="home">
-          {createElement(linkElement, {
-            [linkElement === 'a' ? 'href' : 'to']: '/',
-          }, '首页')}
-        </Breadcrumb.Item>
-      )].concat(extraBreadcrumbItems);
-      breadcrumb = (
-        <Breadcrumb className={styles.breadcrumb}>
-          {breadcrumbItems}
-        </Breadcrumb>
-      );
-    } 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,
     };

+ 40 - 22
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 {
               </span>
             ) : item.name
             }
-          key={item.key || item.path}
+          key={item.path}
         >
           {this.getNavMenuItems(item.children)}
         </SubMenu>
       );
     } else {
       return (
-        <Menu.Item key={item.key || item.path}>
+        <Menu.Item key={item.path}>
           {this.getMenuItemPath(item)}
         </Menu.Item>
       );

+ 21 - 4
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 {