Forráskód Böngészése

Authority management (#508)

* temp save

* rebase master

* fix same error

* Add a new user and different permissions

* fix eol-last

* add Secured decorator

* fix list redirect bug (#507)

* Mobile menu (#463)

* Increase the sliding menu

* Add a simple animation

* update mobile menu

* update

* update

* update

* rebase master

* recovery import/first

* fix error

* Fix some bugs
Change "ALL" to "NONE"
Remove the "!" Support
After landing successfully reload
Reset the format

* Pump your public logic

* add some test

* Add documents

* use default currentRole in Authorized/AuthorizedRoute

* rename props & change some authority setting

* A big change
😄 unified router and Secured parameters
😭 loginOut logout also changed to reload

* fix siderMeun bugs

* Decoupled SiderMenu

* Remove the handsome head of information

* Add a simple error

* rebase master
ddcat1115 8 éve
szülő
commit
cfa27b1833

+ 1 - 0
.gitignore

@@ -15,3 +15,4 @@ npm-debug.log*
 
 /coverage
 .idea
+yarn.lock

+ 19 - 2
.roadhogrc.mock.js

@@ -70,13 +70,30 @@ const proxy = {
   'GET /api/profile/advanced': getProfileAdvancedData,
   'POST /api/login/account': (req, res) => {
     const { password, userName, type } = req.body;
+    if(password === '888888' && userName === 'admin'){
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin'
+      });
+      return ;
+    }
+    if(password === '123456' && userName === 'user'){
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'user'
+      });
+      return ;
+    }
     res.send({
-      status: password === '888888' && userName === 'admin' ? 'ok' : 'error',
+      status: 'error',
       type,
+      currentAuthority: 'guest'
     });
   },
   'POST /api/register': (req, res) => {
-    res.send({ status: 'ok' });
+    res.send({ status: 'ok', currentAuthority: 'user' });
   },
   'GET /api/notices': getNotices,
   'GET /api/500': (req, res) => {

+ 4 - 3
package.json

@@ -16,11 +16,12 @@
     "lint-staged": "lint-staged",
     "lint-staged:js": "eslint --ext .js",
     "test": "jest",
+    "test:comps": "jest ./src/components",
     "test:all": "node ./tests/run-tests.js"
   },
   "dependencies": {
     "@antv/data-set": "^0.8.0",
-    "antd": "^3.0.0",
+    "antd": "^3.1.0",
     "babel-polyfill": "^6.26.0",
     "babel-runtime": "^6.9.2",
     "bizcharts": "^3.1.0-beta.4",
@@ -37,10 +38,10 @@
     "prop-types": "^15.5.10",
     "qs": "^6.5.0",
     "rc-drawer-menu": "^0.5.0",
-    "react": "^16.0.0",
+    "react": "^16.2.0",
     "react-container-query": "^0.9.1",
     "react-document-title": "^2.0.3",
-    "react-dom": "^16.0.0",
+    "react-dom": "^16.2.0",
     "react-fittext": "^1.0.0"
   },
   "devDependencies": {

+ 7 - 2
src/common/menu.js

@@ -25,6 +25,7 @@ const menuData = [{
     path: 'step-form',
   }, {
     name: '高级表单',
+    authority: 'admin',
     path: 'advanced-form',
   }],
 }, {
@@ -64,6 +65,7 @@ const menuData = [{
   }, {
     name: '高级详情页',
     path: 'advanced',
+    authority: 'admin',
   }],
 }, {
   name: '结果页',
@@ -83,6 +85,7 @@ const menuData = [{
   children: [{
     name: '403',
     path: '403',
+    authority: 'user',
   }, {
     name: '404',
     path: '404',
@@ -97,6 +100,7 @@ const menuData = [{
   name: '账户',
   icon: 'user',
   path: 'user',
+  authority: 'guest',
   children: [{
     name: '登录',
     path: 'login',
@@ -114,14 +118,15 @@ const menuData = [{
   target: '_blank',
 }];
 
-function formatter(data, parentPath = '') {
+function formatter(data, parentPath = '', parentAuthority) {
   return data.map((item) => {
     const result = {
       ...item,
       path: `${parentPath}${item.path}`,
+      authority: item.authority || parentAuthority,
     };
     if (item.children) {
-      result.children = formatter(item.children, `${parentPath}${item.path}/`);
+      result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
     }
     return result;
   });

+ 12 - 9
src/common/router.js

@@ -21,17 +21,17 @@ function getFlatMenuData(menus) {
   let keys = {};
   menus.forEach((item) => {
     if (item.children) {
-      keys[item.path] = item.name;
+      keys[item.path] = { ...item };
       keys = { ...keys, ...getFlatMenuData(item.children) };
     } else {
-      keys[item.path] = item.name;
+      keys[item.path] = { ...item };
     }
   });
   return keys;
 }
 
 export const getRouterData = (app) => {
-  const routerData = {
+  const routerConfig = {
     '/': {
       component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
     },
@@ -45,6 +45,7 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
       // hideInBreadcrumb: true,
       // name: '工作台',
+      // authority: 'admin',
     },
     '/form/basic-form': {
       component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/BasicForm')),
@@ -127,12 +128,14 @@ export const getRouterData = (app) => {
   };
   // Get name from ./menu.js or just set it in the router data.
   const menuData = getFlatMenuData(getMenuData());
-  const routerDataWithName = {};
-  Object.keys(routerData).forEach((item) => {
-    routerDataWithName[item] = {
-      ...routerData[item],
-      name: routerData[item].name || menuData[item.replace(/^\//, '')],
+  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,
     };
   });
-  return routerDataWithName;
+  return routerData;
 };

+ 16 - 0
src/components/Authorized/Authorized.js

@@ -0,0 +1,16 @@
+import React from 'react';
+import CheckPermissions from './CheckPermissions';
+
+class Authorized extends React.Component {
+  render() {
+    const { children, authority, noMatch = null } = this.props;
+    const childrenRender = typeof children === 'undefined' ? null : children;
+    return CheckPermissions(
+      authority,
+      childrenRender,
+      noMatch
+    );
+  }
+}
+
+export default Authorized;

+ 23 - 0
src/components/Authorized/AuthorizedRoute.js

@@ -0,0 +1,23 @@
+import React from 'react';
+import { Route, Redirect } from 'dva/router';
+import Authorized from './Authorized';
+
+class AuthorizedRoute extends React.Component {
+  render() {
+    const { component: Component, render, authority,
+      redirectPath, ...rest } = this.props;
+    return (
+      <Authorized
+        authority={authority}
+        noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
+      >
+        <Route
+          {...rest}
+          render={props => (Component ? <Component {...props} /> : render(props))}
+        />
+      </Authorized>
+    );
+  }
+}
+
+export default AuthorizedRoute;

+ 62 - 0
src/components/Authorized/CheckPermissions.js

@@ -0,0 +1,62 @@
+import React from 'react';
+import PromiseRender from './PromiseRender';
+import { CURRENT } from './index';
+/**
+ * 通用权限检查方法
+ * Common check permissions method
+ * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
+ * @param { 你的权限 Your permission description  type:string} currentAuthority
+ * @param { 通过的组件 Passing components } target
+ * @param { 未通过的组件 no pass components } Exception
+ */
+const checkPermissions = (authority, currentAuthority, target, Exception) => {
+  // 没有判定权限.默认查看所有
+  // Retirement authority, return target;
+  if (!authority) {
+    return target;
+  }
+  // 数组处理
+  if (authority.constructor.name === 'Array') {
+    if (authority.includes(currentAuthority)) {
+      return target;
+    }
+    return Exception;
+  }
+
+  // string 处理
+  if (authority.constructor.name === 'String') {
+    if (authority === currentAuthority) {
+      return target;
+    }
+    return Exception;
+  }
+
+  // Promise 处理
+  if (authority.constructor.name === 'Promise') {
+    return () => (
+      <PromiseRender ok={target} error={Exception} promise={authority} />
+    );
+  }
+
+  // Function 处理
+  if (authority.constructor.name === 'Function') {
+    try {
+      const bool = authority();
+      if (bool) {
+        return target;
+      }
+      return Exception;
+    } catch (error) {
+      throw error;
+    }
+  }
+  throw new Error('unsupported parameters');
+};
+
+export { checkPermissions };
+
+const check = (authority, target, Exception) => {
+  return checkPermissions(authority, CURRENT, target, Exception);
+};
+
+export default check;

+ 42 - 0
src/components/Authorized/CheckPermissions.test.js

@@ -0,0 +1,42 @@
+import { checkPermissions } from './CheckPermissions.js';
+
+const target = 'ok';
+const error = 'error';
+
+describe('test CheckPermissions', () => {
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
+  });
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
+  });
+  it('authority is undefined , return ok', () => {
+    expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
+  });
+  it('Wrong string permission authentication', () => {
+    expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
+  });
+  it('Correct Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual(
+      'ok'
+    );
+  });
+  it('Wrong Array permission authentication,currentAuthority error', () => {
+    expect(
+      checkPermissions(['user', 'admin'], 'user,admin', target, error)
+    ).toEqual('error');
+  });
+  it('Wrong Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual(
+      'error'
+    );
+  });
+  it('Wrong Function permission authentication', () => {
+    expect(checkPermissions(() => false, 'guest', target, error)).toEqual(
+      'error'
+    );
+  });
+  it('Correct Function permission authentication', () => {
+    expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
+  });
+});

+ 39 - 0
src/components/Authorized/PromiseRender.js

@@ -0,0 +1,39 @@
+import React from 'react';
+import { Spin } from 'antd';
+
+export default class PromiseRender extends React.PureComponent {
+    state = {
+      component: false,
+    };
+    async componentDidMount() {
+      this.props.promise
+        .then(() => {
+          this.setState({
+            component: this.props.ok,
+          });
+        })
+        .catch(() => {
+          this.setState({
+            component: this.props.error,
+          });
+        });
+    }
+    render() {
+      const C = this.state.component;
+      return C ? (
+        <C {...this.props} />
+      ) : (
+        <div
+          style={{
+            width: '100%',
+            height: '100%',
+            margin: 'auto',
+            paddingTop: 50,
+            textAlign: 'center',
+          }}
+        >
+          <Spin size="large" />
+        </div>
+      );
+    }
+}

+ 49 - 0
src/components/Authorized/Secured.js

@@ -0,0 +1,49 @@
+import React from 'react';
+import Exception from '../Exception/index';
+import CheckPermissions from './CheckPermissions';
+/**
+ * 默认不能访问任何页面
+ * default is "NULL"
+ */
+const Exception403 = () => (
+  <Exception type="403" style={{ minHeight: 500, height: '80%' }} />
+);
+
+/**
+ * 用于判断是否拥有权限访问此view权限
+ * authority 支持传入  string ,funtion:()=>boolean|Promise
+ * e.g. 'user' 只有user用户能访问
+ * e.g. 'user,admin' user和 admin 都能访问
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
+ * e.g. Promise  then 能访问   catch不能访问
+ * e.g. authority support incoming string, funtion: () => boolean | Promise
+ * e.g. 'user' only user user can access
+ * e.g. 'user, admin' user and admin can access
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
+ * e.g. Promise then can not access the visit to catch
+ * @param {string | function | Promise} authority
+ * @param {ReactNode} error 非必需参数
+ */
+const authorize = (authority, error) => {
+  /**
+   * conversion into a class
+   * 防止传入字符串时找不到staticContext造成报错
+   * String parameters can cause staticContext not found error
+   */
+  let classError = false;
+  if (error) {
+    classError = () => error;
+  }
+  if (!authority) {
+    throw new Error('authority is required');
+  }
+  return function decideAuthority(targer) {
+    return CheckPermissions(
+      authority,
+      targer,
+      classError || Exception403
+    );
+  };
+};
+
+export default authorize;

+ 32 - 0
src/components/Authorized/index.js

@@ -0,0 +1,32 @@
+import Authorized from './Authorized';
+import AuthorizedRoute from './AuthorizedRoute';
+import Secured from './Secured';
+import check from './CheckPermissions.js';
+
+/* eslint-disable import/no-mutable-exports */
+let CURRENT = 'NULL';
+
+Authorized.Secured = Secured;
+Authorized.AuthorizedRoute = AuthorizedRoute;
+Authorized.check = check;
+
+/**
+ * use  authority or getAuthority
+ * @param {string|()=>String} currentAuthority
+ */
+const renderAuthorize = (currentAuthority) => {
+  if (currentAuthority) {
+    if (currentAuthority.constructor.name === 'Function') {
+      CURRENT = currentAuthority();
+    }
+    if (currentAuthority.constructor.name === 'String') {
+      CURRENT = currentAuthority;
+    }
+  } else {
+    CURRENT = 'NULL';
+  }
+  return Authorized;
+};
+
+export { CURRENT };
+export default renderAuthorize;

+ 84 - 48
src/components/SiderMenu/SiderMenu.js

@@ -77,62 +77,98 @@ export default class SiderMenu extends PureComponent {
       return itemRegExp.test(path.replace(/^\//, '').replace(/\/$/, ''));
     });
   }
-  getNavMenuItems(menusData) {
+  /**
+  * 判断是否是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);
+    const { target, name } = item;
+    // Is it a http link
+    if (/^https?:\/\//.test(itemPath)) {
+      return (
+        <a href={itemPath} target={target}>
+          {icon}<span>{name}</span>
+        </a>
+      );
+    }
+    return (
+      <Link
+        to={itemPath}
+        target={target}
+        replace={itemPath === this.props.location.pathname}
+        onClick={this.props.isMobile ? () => { this.props.onCollapse(true); } : undefined}
+      >
+        {icon}<span>{name}</span>
+      </Link>
+    );
+  }
+  /**
+   * get SubMenu or Item
+   */
+  getSubMenuOrItem=(item) => {
+    if (item.children && item.children.some(child => child.name)) {
+      return (
+        <SubMenu
+          title={
+            item.icon ? (
+              <span>
+                {getIcon(item.icon)}
+                <span>{item.name}</span>
+              </span>
+            ) : item.name
+            }
+          key={item.key || item.path}
+        >
+          {this.getNavMenuItems(item.children)}
+        </SubMenu>
+      );
+    } else {
+      return (
+        <Menu.Item key={item.key || item.path}>
+          {this.getMenuItemPath(item)}
+        </Menu.Item>
+      );
+    }
+  }
+  /**
+  * 获得菜单子节点
+  * @memberof SiderMenu
+  */
+  getNavMenuItems = (menusData) => {
     if (!menusData) {
       return [];
     }
     return menusData.map((item) => {
-      if (!item.name) {
+      if (!item.name || item.hideInMenu) {
         return null;
       }
-      let itemPath;
-      if (item.path && item.path.indexOf('http') === 0) {
-        itemPath = item.path;
-      } else {
-        itemPath = `/${item.path || ''}`.replace(/\/+/g, '/');
-      }
-      if (item.children && item.children.some(child => child.name)) {
-        return item.hideInMenu ? null :
-          (
-            <SubMenu
-              title={
-                item.icon ? (
-                  <span>
-                    {getIcon(item.icon)}
-                    <span>{item.name}</span>
-                  </span>
-                ) : item.name
-              }
-              key={item.key || item.path}
-            >
-              {this.getNavMenuItems(item.children)}
-            </SubMenu>
-          );
-      }
-      const icon = getIcon(item.icon);
-      return item.hideInMenu ? null :
-        (
-          <Menu.Item key={item.key || item.path}>
-            {
-              /^https?:\/\//.test(itemPath) ? (
-                <a href={itemPath} target={item.target}>
-                  {icon}<span>{item.name}</span>
-                </a>
-              ) : (
-                <Link
-                  to={itemPath}
-                  target={item.target}
-                  replace={itemPath === this.props.location.pathname}
-                  onClick={this.props.isMobile ? () => { this.props.onCollapse(true); } : undefined}
-                >
-                  {icon}<span>{item.name}</span>
-                </Link>
-              )
-            }
-          </Menu.Item>
-        );
+      const ItemDom = this.getSubMenuOrItem(item);
+      return this.checkPermissionItem(item.authority, ItemDom);
     });
   }
+  // conversion 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 ItemDom;
+  }
   handleOpenChange = (openKeys) => {
     const lastOpenKey = openKeys[openKeys.length - 1];
     const isMainMenu = this.menus.some(

+ 22 - 11
src/layouts/BasicLayout.js

@@ -12,9 +12,13 @@ import GlobalFooter from '../components/GlobalFooter';
 import SiderMenu from '../components/SiderMenu';
 import NotFound from '../routes/Exception/404';
 import { getRoutes } from '../utils/utils';
+import Authorized from '../utils/Authorized';
 import { getMenuData } from '../common/menu';
 import logo from '../assets/logo.svg';
 
+const { Content } = Layout;
+const { AuthorizedRoute } = Authorized;
+
 /**
  * 根据菜单取得重定向地址.
  */
@@ -34,7 +38,6 @@ const getRedirect = (item) => {
 };
 getMenuData().forEach(getRedirect);
 
-const { Content } = Layout;
 const query = {
   'screen-xs': {
     maxWidth: 575,
@@ -130,6 +133,10 @@ class BasicLayout extends React.PureComponent {
       <Layout>
         <SiderMenu
           logo={logo}
+          // 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
+          // If you do not have the Authorized parameter
+          // you will be forced to jump to the 403 interface without permission
+          Authorized={Authorized}
           menuData={getMenuData()}
           collapsed={collapsed}
           location={location}
@@ -153,19 +160,23 @@ class BasicLayout extends React.PureComponent {
             <div style={{ minHeight: 'calc(100vh - 260px)' }}>
               <Switch>
                 {
-                  redirectData.map(item =>
-                    <Redirect key={item.from} exact from={item.from} to={item.to} />
+                  getRoutes(match.path, routerData).map(item =>
+                    (
+                      <AuthorizedRoute
+                        key={item.key}
+                        path={item.path}
+                        component={item.component}
+                        exact={item.exact}
+                        authority={item.authority}
+                        redirectPath="/exception/403"
+                      />
+                    )
                   )
                 }
                 {
-                  getRoutes(match.path, routerData).map(item => (
-                    <Route
-                      key={item.key}
-                      path={item.path}
-                      component={item.component}
-                      exact={item.exact}
-                    />
-                  ))
+                  redirectData.map(item =>
+                    <Redirect key={item.from} exact from={item.from} to={item.to} />
+                  )
                 }
                 <Redirect exact from="/" to="/dashboard/analysis" />
                 <Route render={NotFound} />

+ 12 - 3
src/models/login.js

@@ -1,5 +1,5 @@
-import { routerRedux } from 'dva/router';
 import { fakeAccountLogin } from '../services/api';
+import { setAuthority } from '../utils/authority';
 
 export default {
   namespace: 'login',
@@ -21,7 +21,11 @@ export default {
       });
       // Login successfully
       if (response.status === 'ok') {
-        yield put(routerRedux.push('/'));
+        // 非常粗暴的跳转,登陆成功之后权限会变成user或admin,会自动重定向到主页
+        // Login success after permission changes to admin or user
+        // The refresh will automatically redirect to the home page
+        // yield put(routerRedux.push('/'));
+        location.reload();
       }
     },
     *logout(_, { put }) {
@@ -29,14 +33,19 @@ export default {
         type: 'changeLoginStatus',
         payload: {
           status: false,
+          currentAuthority: 'guest',
         },
       });
-      yield put(routerRedux.push('/user/login'));
+      // yield put(routerRedux.push('/user/login'));
+      // Login out after permission changes to admin or user
+      // The refresh will automatically redirect to the login page
+      location.reload();
     },
   },
 
   reducers: {
     changeLoginStatus(state, { payload }) {
+      setAuthority(payload.currentAuthority);
       return {
         ...state,
         status: payload.status,

+ 15 - 4
src/router.js

@@ -1,12 +1,13 @@
 import React from 'react';
-import { Router, Route, Switch } from 'dva/router';
+import { Router, Switch } from 'dva/router';
 import { LocaleProvider, Spin } from 'antd';
 import zhCN from 'antd/lib/locale-provider/zh_CN';
 import dynamic from 'dva/dynamic';
 import { getRouterData } from './common/router';
-
+import Authorized from './utils/Authorized';
 import styles from './index.less';
 
+const { AuthorizedRoute } = Authorized;
 dynamic.setDefaultLoadingComponent(() => {
   return <Spin size="large" className={styles.globalSpin} />;
 });
@@ -19,8 +20,18 @@ function RouterConfig({ history, app }) {
     <LocaleProvider locale={zhCN}>
       <Router history={history}>
         <Switch>
-          <Route path="/user" render={props => <UserLayout {...props} />} />
-          <Route path="/" render={props => <BasicLayout {...props} />} />
+          <AuthorizedRoute
+            path="/user"
+            render={props => <UserLayout {...props} />}
+            authority="guest"
+            redirectPath="/"
+          />
+          <AuthorizedRoute
+            path="/"
+            render={props => <BasicLayout {...props} />}
+            authority={['admin', 'user']}
+            redirectPath="/user/login"
+          />
         </Switch>
       </Router>
     </LocaleProvider>

+ 18 - 14
src/routes/Dashboard/Monitor.js

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { connect } from 'dva';
 import { Row, Col, Card, Tooltip } from 'antd';
 import numeral from 'numeral';
-
+import { Secured } from '../../utils/Authorized';
 import { Pie, WaterWave, Gauge, TagCloud } from '../../components/Charts';
 import NumberInfo from '../../components/NumberInfo';
 import CountDown from '../../components/CountDown';
@@ -12,6 +12,7 @@ import styles from './Monitor.less';
 
 const targetTime = new Date().getTime() + 3900000;
 
+@Secured('admin')
 @connect(state => ({
   monitor: state.monitor,
 }))
@@ -40,7 +41,10 @@ export default class Monitor extends PureComponent {
                   />
                 </Col>
                 <Col md={6} sm={12} xs={24}>
-                  <NumberInfo subTitle="销售目标完成率" total="92%" />
+                  <NumberInfo
+                    subTitle="销售目标完成率"
+                    total="92%"
+                  />
                 </Col>
                 <Col md={6} sm={12} xs={24}>
                   <NumberInfo subTitle="活动剩余时间" total={<CountDown target={targetTime} />} />
@@ -55,10 +59,7 @@ export default class Monitor extends PureComponent {
               </Row>
               <div className={styles.mapChart}>
                 <Tooltip title="等待后期实现">
-                  <img
-                    src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png"
-                    alt="map"
-                  />
+                  <img src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png" alt="map" />
                 </Tooltip>
               </div>
             </Card>
@@ -140,17 +141,20 @@ export default class Monitor extends PureComponent {
             </Card>
           </Col>
           <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
-            <Card title="热门搜索" bordered={false}>
-              <TagCloud data={tags} height={161} />
+            <Card title="热门搜索" bordered={false} bodyStyle={{ overflow: 'hidden' }}>
+              <TagCloud
+                data={tags}
+                height={161}
+              />
             </Card>
           </Col>
           <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
-            <Card
-              title="资源剩余"
-              bodyStyle={{ textAlign: 'center', fontSize: 0 }}
-              bordered={false}
-            >
-              <WaterWave height={161} title="补贴资金剩余" percent={34} />
+            <Card title="资源剩余" bodyStyle={{ textAlign: 'center', fontSize: 0 }} bordered={false}>
+              <WaterWave
+                height={161}
+                title="补贴资金剩余"
+                percent={34}
+              />
             </Card>
           </Col>
         </Row>

+ 2 - 2
src/routes/User/Login.js

@@ -62,8 +62,8 @@ export default class LoginPage extends Component {
               login.submitting === false &&
               this.renderMessage('账户或密码错误')
             }
-            <UserName name="userName" />
-            <Password name="password" />
+            <UserName name="userName" placeholder="admin/user" />
+            <Password name="password" placeholder="888888/123456" />
           </Tab>
           <Tab key="mobile" tab="手机号登录">
             {

+ 5 - 0
src/utils/Authorized.js

@@ -0,0 +1,5 @@
+import RenderAuthorized from '../components/Authorized';
+import { getAuthority } from './authority';
+
+const Authorized = RenderAuthorized(getAuthority());
+export default Authorized;

+ 8 - 0
src/utils/authority.js

@@ -0,0 +1,8 @@
+// use localStorage to store the authority info, which might be sent from server in actual project.
+export function getAuthority() {
+  return localStorage.getItem('antd-pro-authority') || 'guest';
+}
+
+export function setAuthority(authority) {
+  return localStorage.setItem('antd-pro-authority', authority);
+}

+ 1 - 1
src/utils/utils.js

@@ -124,9 +124,9 @@ export function getRoutes(path, routerData) {
   const renderRoutes = renderArr.map((item) => {
     const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
     return {
+      ...routerData[`${path}${item}`],
       key: `${path}${item}`,
       path: `${path}${item}`,
-      component: routerData[`${path}${item}`].component,
       exact,
     };
   });