Explorar o código

refactor: v4 升级 v5

lanjianrong %!s(int64=5) %!d(string=hai) anos
pai
achega
af1f661369
Modificáronse 46 ficheiros con 686 adicións e 2031 borrados
  1. 2 1
      .stylelintrc.js
  2. 6 0
      config/config.js
  3. 0 2
      config/defaultSettings.js
  4. 126 144
      config/routes.js
  5. 6 0
      src/access.ts
  6. 48 19
      src/app.tsx
  7. 0 21
      src/components/Authorized/Authorized.jsx
  8. 0 25
      src/components/Authorized/AuthorizedRoute.jsx
  9. 0 80
      src/components/Authorized/CheckPermissions.jsx
  10. 0 78
      src/components/Authorized/PromiseRender.jsx
  11. 0 70
      src/components/Authorized/Secured.jsx
  12. 0 9
      src/components/Authorized/index.jsx
  13. 0 34
      src/components/Authorized/renderAuthorize.js
  14. 0 89
      src/components/GlobalHeader/AvatarDropdown.jsx
  15. 0 156
      src/components/GlobalHeader/NoticeIconView.jsx
  16. 0 77
      src/components/GlobalHeader/RightContent.jsx
  17. 0 10
      src/components/HeaderDropdown/index.jsx
  18. 18 0
      src/components/HeaderDropdown/index.tsx
  19. 0 86
      src/components/HeaderSearch/index.jsx
  20. 0 30
      src/components/HeaderSearch/index.less
  21. 0 95
      src/components/NoticeIcon/NoticeList.jsx
  22. 0 103
      src/components/NoticeIcon/NoticeList.less
  23. 0 113
      src/components/NoticeIcon/index.jsx
  24. 0 35
      src/components/NoticeIcon/index.less
  25. 119 0
      src/components/RightContent/AvatarDropdown.tsx
  26. 12 32
      src/components/GlobalHeader/index.less
  27. 76 0
      src/components/RightContent/index.tsx
  28. 0 148
      src/layouts/BasicLayout.jsx
  29. 0 27
      src/layouts/BasicLayout.less
  30. 0 5
      src/layouts/BlankLayout.jsx
  31. 0 53
      src/layouts/SecurityLayout.jsx
  32. 0 58
      src/layouts/UserLayout.jsx
  33. 0 69
      src/layouts/UserLayout.less
  34. 1 0
      src/locales/zh-CN/pages.js
  35. 0 49
      src/models/global.js
  36. 0 27
      src/models/setting.js
  37. 0 100
      src/models/user.js
  38. 5 5
      src/pages/Customer/Business/index.jsx
  39. 4 6
      src/pages/Customer/Client/index.jsx
  40. 4 4
      src/pages/Customer/Company/index.jsx
  41. 181 118
      src/pages/user/login/index.jsx
  42. 72 8
      src/pages/user/login/index.less
  43. 3 3
      src/pages/workbench/Dashboard/index.jsx
  44. 3 7
      src/services/user.js
  45. 0 19
      src/utils/auth/Authorized.js
  46. 0 16
      src/utils/auth/authority.js

+ 2 - 1
.stylelintrc.js

@@ -3,7 +3,7 @@ const fabric = require('@umijs/fabric')
 module.exports = {
   ...fabric.stylelint,
   rules: {
-    'indentation': 2,
+    indentation: 2,
     'block-no-empty': null,
     'color-no-invalid-hex': true,
     'declaration-colon-space-after': 'always',
@@ -15,5 +15,6 @@ module.exports = {
     'color-hex-length': 'long',
     'at-rule-no-unknown': null,
     'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global'] }],
+    'no-descending-specificity': null
   }
 }

+ 6 - 0
config/config.js

@@ -13,6 +13,12 @@ const { REACT_APP_ENV } = process.env
 export default defineConfig({
   hash: true,
   antd: {},
+  layout: {
+    // https://umijs.org/zh-CN/plugins/plugin-layout
+    locale: true,
+    siderWidth: 208,
+    ...defaultSettings
+  },
   dva: {
     hmr: true
   },

+ 0 - 2
config/defaultSettings.js

@@ -1,6 +1,4 @@
 const proSettings = {
-  // navTheme: 'dark',
-  // 拂晓蓝
   navTheme: 'light',
   primaryColor: '#886ab5',
   layout: 'mix',

+ 126 - 144
config/routes.js

@@ -1,166 +1,148 @@
 export default [
   {
+    path: '/user',
+    layout: false,
+    routes: [
+      {
+        name: 'login',
+        path: '/user/login',
+        component: './user/login'
+      }
+    ]
+  },
+  {
     path: '/',
-    component: '../layouts/BlankLayout',
+    redirect: '/workbench'
+  },
+  {
+    path: '/workbench',
+    name: 'workbench',
+    icon: 'icon-chess',
+    routes: [
+      {
+        path: '/workbench',
+        redirect: '/workbench/dashboard'
+      },
+      {
+        path: '/workbench/dashboard',
+        name: 'dashboard',
+        icon: 'icon-tachometer-alt',
+        component: './Workbench/Dashboard'
+      }
+    ]
+  },
+  {
+    path: '/customer',
+    name: 'customer',
+    icon: 'icon-address-book-o',
     routes: [
       {
-        path: '/user',
-        component: '../layouts/UserLayout',
+        path: '/customer',
+        redirect: '/customer/Company'
+        // authority: ['admin', 'company_access']
+      },
+      {
+        path: '/customer/company',
+        name: 'company',
+        icon: 'icon-building',
+        component: './Customer/Company'
+        // authority: ['admin', 'company_access']
+      },
+      {
+        path: '/customer/client',
+        name: 'client',
+        icon: 'icon-address-book',
+        component: './Customer/Client'
+        // authority: ['admin', 'client_access']
+      },
+      {
+        path: '/customer/business',
+        name: 'business',
+        icon: 'icon-usd-circle',
+        component: './Customer/Business'
+        // authority: ['admin', 'business_access']
+      },
+      {
+        path: '/customer/test',
+        name: 'test',
+        icon: 'icon-usd-circle',
+        component: './Customer/Test'
+      }
+    ]
+  },
+  {
+    path: '/product',
+    name: 'product',
+    icon: 'icon-box',
+    routes: [
+      {
+        path: '/product',
+        redirect: '/product/lock'
+      },
+      {
+        path: '/product/lock',
+        name: 'lock',
+        icon: 'icon-magic',
         routes: [
           {
-            name: 'login',
-            path: '/user/login',
-            component: './user/login'
+            path: '/product/lock',
+            redirect: '/product/lock/lockstore'
+          },
+          {
+            path: '/product/lock/lockstore',
+            name: 'lockstore',
+            component: './Product/Lock/LockStore'
           }
+          // {
+          //   path: '/product/lock/lockcount',
+          //   name: 'lockcount',
+          //   component: './Product/Lock/LockCount'
+          // }
         ]
       },
       {
-        path: '/',
-        component: '../layouts/SecurityLayout',
+        path: '/product/cloud',
+        name: 'cloud',
+        icon: 'icon-cloud',
         routes: [
           {
-            path: '/',
-            component: '../layouts/BasicLayout',
-            routes: [
-              {
-                path: '/',
-                redirect: '/workbench'
-              },
-              {
-                path: '/workbench',
-                name: 'workbench',
-                icon: 'icon-chess',
-                routes: [
-                  {
-                    path: '/workbench',
-                    redirect: '/workbench/dashboard'
-                  },
-                  {
-                    path: '/workbench/dashboard',
-                    name: 'dashboard',
-                    icon: 'icon-tachometer-alt',
-                    component: './Workbench/Dashboard'
-                  }
-                ]
-              },
-              {
-                path: '/customer',
-                name: 'customer',
-                icon: 'icon-address-book-o',
-                routes: [
-                  {
-                    path: '/customer',
-                    redirect: '/customer/Company',
-                    authority: ['admin', 'company_access']
-                  },
-                  {
-                    path: '/customer/company',
-                    name: 'company',
-                    icon: 'icon-building',
-                    component: './Customer/Company',
-                    authority: ['admin', 'company_access']
-                  },
-                  {
-                    path: '/customer/client',
-                    name: 'client',
-                    icon: 'icon-address-book',
-                    component: './Customer/Client',
-                    authority: ['admin', 'client_access']
-                  },
-                  {
-                    path: '/customer/business',
-                    name: 'business',
-                    icon: 'icon-usd-circle',
-                    component: './Customer/Business',
-                    authority: ['admin', 'business_access']
-                  },
-                  {
-                    path: '/customer/test',
-                    name: 'test',
-                    icon: 'icon-usd-circle',
-                    component: './Customer/Test'
-                  }
-                ]
-              },
-              {
-                path: '/product',
-                name: 'product',
-                icon: 'icon-box',
-                routes: [
-                  {
-                    path: '/product',
-                    redirect: '/product/lock'
-                  },
-                  {
-                    path: '/product/lock',
-                    name: 'lock',
-                    icon: 'icon-magic',
-                    routes: [
-                      {
-                        path: '/product/lock',
-                        redirect: '/product/lock/lockstore'
-                      },
-                      {
-                        path: '/product/lock/lockstore',
-                        name: 'lockstore',
-                        component: './Product/Lock/LockStore'
-                      }
-                      // {
-                      //   path: '/product/lock/lockcount',
-                      //   name: 'lockcount',
-                      //   component: './Product/Lock/LockCount'
-                      // }
-                    ]
-                  },
-                  {
-                    path: '/product/cloud',
-                    name: 'cloud',
-                    icon: 'icon-cloud',
-                    routes: [
-                      {
-                        path: '/product/cloud',
-                        redirect: '/product/cloud/building'
-                      },
-                      {
-                        path: '/product/cloud/building',
-                        name: 'build',
-                        component: './Product/Cloud/Building'
-                      },
-                      {
-                        path: '/product/cloud/curing',
-                        name: 'curing',
-                        component: './Product/Cloud/Curing'
-                      }
-                    ]
-                  }
-                ]
-              },
-              {
-                path: '/staff',
-                name: 'staff',
-                icon: 'icon-users',
-                routes: [
-                  {
-                    path: '/staff',
-                    redirect: '/staff/employee',
-                    authority: ['admin', 'department_access']
-                  },
-                  {
-                    path: '/staff/employee',
-                    name: 'employee',
-                    icon: 'icon-users',
-                    component: './Staff/Employee',
-                    authority: ['admin', 'department_access']
-                  }
-                ]
-              }
-            ]
+            path: '/product/cloud',
+            redirect: '/product/cloud/building'
+          },
+          {
+            path: '/product/cloud/building',
+            name: 'build',
+            component: './Product/Cloud/Building'
+          },
+          {
+            path: '/product/cloud/curing',
+            name: 'curing',
+            component: './Product/Cloud/Curing'
           }
         ]
       }
     ]
   },
   {
+    path: '/staff',
+    name: 'staff',
+    icon: 'icon-users',
+    routes: [
+      {
+        path: '/staff',
+        redirect: '/staff/employee'
+        // authority: ['admin', 'department_access']
+      },
+      {
+        path: '/staff/employee',
+        name: 'employee',
+        icon: 'icon-users',
+        component: './Staff/Employee'
+        // authority: ['admin', 'department_access']
+      }
+    ]
+  },
+  {
     component: './404'
   }
 ]

+ 6 - 0
src/access.ts

@@ -0,0 +1,6 @@
+export default function (initialState) {
+  const { currentUser } = initialState || {}
+  return {
+    canAdmin: currentUser && currentUser.access === 'admin'
+  }
+}

+ 48 - 19
src/app.tsx

@@ -1,22 +1,51 @@
-import { apiGetRoles } from './services/user'
-import { setAuthority } from './utils/auth/authority'
+import { queryCurrentUser } from './services/user'
+import { history } from 'umi'
+import RightContent from '@/components/RightContent'
 
-// const PermDataList = [
-//   {
-//     key: '/customer',
-//     value: 2
-//   }
-// ]
-export function render(oldRender) {
-  const path = window.location.pathname
-  if (path !== '/user/login') {
-    apiGetRoles().then(roles => {
-      setAuthority(roles.data)
-      // const permVal = PermDataList.find(item => path.startsWith(item.key))?.value
-      // if (permVal) {
-      //   getDataPerm().then(prem => setAuthCache(PERM_LIST_KEY, prem?.data || []))
-      // }
-    })
+const loginPath = '/user/login'
+
+export async function getInitialState() {
+  // 如果是登录页面,不执行
+  const fetchUserInfo = async () => {
+    try {
+      const res = await queryCurrentUser()
+      return res.data
+    } catch (error) {
+      history.push(loginPath)
+    }
+    return undefined
+  }
+  // 如果是登录页面,不执行
+  if (history.location.pathname !== loginPath) {
+    const currentUser = (await fetchUserInfo()) || {}
+
+    return {
+      fetchUserInfo,
+      ...currentUser,
+      settings: {}
+    }
+  }
+  return {
+    fetchUserInfo,
+    settings: {}
+  }
+}
+
+export const layout = ({ initialState }) => {
+  return {
+    rightContentRender: () => <RightContent />,
+    disableContentMargin: false,
+    waterMarkProps: {
+      content: initialState?.currentUser?.username
+    },
+    // onPageChange: () => {
+    //   const { location } = history
+    //   // 如果没有登录,重定向到 login
+    //   if (initialState?.currentUser?.staffId && location.pathname !== loginPath) {
+    //     history.push(loginPath)
+    //   }
+    // },
+    // menuHeaderRender: false,
+    ...initialState?.settings
   }
-  oldRender()
 }

+ 0 - 21
src/components/Authorized/Authorized.jsx

@@ -1,21 +0,0 @@
-import React from 'react'
-import { Result } from 'antd'
-import check from './CheckPermissions'
-
-const Authorized = ({
-  children,
-  authority,
-  noMatch = (
-    <Result
-      status="403"
-      title="403"
-      subTitle="Sorry, you are not authorized to access this page."
-    />
-  )
-}) => {
-  const childrenRender = typeof children === 'undefined' ? null : children
-  const dom = check(authority, childrenRender, noMatch)
-  return <>{dom}</>
-}
-
-export default Authorized

+ 0 - 25
src/components/Authorized/AuthorizedRoute.jsx

@@ -1,25 +0,0 @@
-import { Redirect, Route } from 'umi'
-import React from 'react'
-import Authorized from './Authorized'
-
-const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
-  <Authorized
-    authority={authority}
-    noMatch={
-      <Route
-        {...rest}
-        render={() => (
-          <Redirect
-            to={{
-              pathname: redirectPath
-            }}
-          />
-        )}
-      />
-    }
-  >
-    <Route {...rest} render={(props) => (Component ? <Component {...props} /> : render(props))} />
-  </Authorized>
-)
-
-export default AuthorizedRoute

+ 0 - 80
src/components/Authorized/CheckPermissions.jsx

@@ -1,80 +0,0 @@
-import React from 'react'
-import { CURRENT } from './renderAuthorize' // eslint-disable-next-line import/no-cycle
-
-import PromiseRender from './PromiseRender'
-
-/**
- * 通用权限检查方法
- * Common check permissions method
- * @param { 权限判定 | Permission judgment } authority
- * @param { 你的权限 | Your permission description } 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 (Array.isArray(authority)) {
-    if (currentAuthority instanceof Promise) {
-      return currentAuthority.then(Authority => {
-        if (Authority.some(item => authority.includes(item))) {
-          return target
-        }
-        return Exception
-      })
-    }
-    if (Array.isArray(currentAuthority)) {
-      if (currentAuthority.some(item => authority.includes(item))) {
-        return target
-      }
-    } else if (authority.includes(currentAuthority)) {
-      return target
-    }
-
-    return Exception
-  } // string 处理
-
-  if (typeof authority === 'string') {
-    if (Array.isArray(currentAuthority)) {
-      if (currentAuthority.some(item => authority === item)) {
-        return target
-      }
-    } else if (authority === currentAuthority) {
-      return target
-    }
-
-    return Exception
-  } // Promise 处理
-
-  if (authority instanceof Promise) {
-    return <PromiseRender ok={target} error={Exception} promise={authority} />
-  } // Function 处理
-
-  if (typeof authority === 'function') {
-    const bool = authority(currentAuthority) // 函数执行后返回值是 Promise
-
-    if (bool instanceof Promise) {
-      return <PromiseRender ok={target} error={Exception} promise={bool} />
-    }
-
-    if (bool) {
-      return target
-    }
-
-    return Exception
-  }
-
-  throw new Error('unsupported parameters')
-}
-
-export { checkPermissions }
-
-function check(authority, target, Exception) {
-  return checkPermissions(authority, CURRENT, target, Exception)
-}
-
-export default check

+ 0 - 78
src/components/Authorized/PromiseRender.jsx

@@ -1,78 +0,0 @@
-import React from 'react'
-import { Spin } from 'antd'
-import isEqual from 'lodash/isEqual'
-import { isComponentClass } from './Secured' // eslint-disable-next-line import/no-cycle
-
-export default class PromiseRender extends React.Component {
-  state = {
-    component: () => null
-  };
-
-  componentDidMount() {
-    this.setRenderComponent(this.props)
-  }
-
-  shouldComponentUpdate = (nextProps, nextState) => {
-    const { component } = this.state
-
-    if (!isEqual(nextProps, this.props)) {
-      this.setRenderComponent(nextProps)
-    }
-
-    if (nextState.component !== component) return true
-    return false
-  }; // set render Component : ok or error
-
-  setRenderComponent(props) {
-    const ok = this.checkIsInstantiation(props.ok)
-    const error = this.checkIsInstantiation(props.error)
-    props.promise
-      .then(() => {
-        this.setState({
-          component: ok
-        })
-        return true
-      })
-      .catch(() => {
-        this.setState({
-          component: error
-        })
-      })
-  } // Determine whether the incoming component has been instantiated
-  // AuthorizedRoute is already instantiated
-  // Authorized  render is already instantiated, children is no instantiated
-  // Secured is not instantiated
-
-  checkIsInstantiation = (target) => {
-    if (isComponentClass(target)) {
-      const Target = target
-      return (props) => <Target {...props} />
-    }
-
-    if (React.isValidElement(target)) {
-      return (props) => React.cloneElement(target, props)
-    }
-
-    return () => target
-  };
-
-  render() {
-    const { component: Component } = this.state
-    const { ok, error, promise, ...rest } = this.props
-    return Component ? (
-      <Component {...rest} />
-    ) : (
-      <div
-        style={{
-          width: '100%',
-          height: '100%',
-          margin: 'auto',
-          paddingTop: 50,
-          textAlign: 'center'
-        }}
-      >
-        <Spin size="large" />
-      </div>
-    )
-  }
-}

+ 0 - 70
src/components/Authorized/Secured.jsx

@@ -1,70 +0,0 @@
-import React from 'react'
-import CheckPermissions from './CheckPermissions'
-/**
- * 默认不能访问任何页面
- * default is "NULL"
- */
-
-const Exception403 = () => 403
-
-export const isComponentClass = (component) => {
-  if (!component) return false
-  const proto = Object.getPrototypeOf(component)
-  if (proto === React.Component || proto === Function.prototype) return true
-  return isComponentClass(proto)
-} // Determine whether the incoming component has been instantiated
-// AuthorizedRoute is already instantiated
-// Authorized  render is already instantiated, children is no instantiated
-// Secured is not instantiated
-
-const checkIsInstantiation = (target) => {
-  if (isComponentClass(target)) {
-    const Target = target
-    return (props) => <Target {...props} />
-  }
-
-  if (React.isValidElement(target)) {
-    return (props) => React.cloneElement(target, props)
-  }
-
-  return () => target
-}
-/**
- * 用于判断是否拥有权限访问此 view 权限
- * authority 支持传入 string, () => 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, () => 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(target) {
-    const component = CheckPermissions(authority, target, classError || Exception403)
-    return checkIsInstantiation(component)
-  }
-}
-
-export default authorize

+ 0 - 9
src/components/Authorized/index.jsx

@@ -1,9 +0,0 @@
-import Authorized from './Authorized'
-import Secured from './Secured'
-import check from './CheckPermissions'
-import renderAuthorize from './renderAuthorize'
-
-Authorized.Secured = Secured
-Authorized.check = check
-const RenderAuthorize = renderAuthorize(Authorized)
-export default RenderAuthorize

+ 0 - 34
src/components/Authorized/renderAuthorize.js

@@ -1,34 +0,0 @@
-/* eslint-disable eslint-comments/disable-enable-pair */
-
-/* eslint-disable import/no-mutable-exports */
-let CURRENT = 'NULL'
-
-/**
- * use  authority or getAuthority
- * @param {string|()=>String} currentAuthority
- */
-const renderAuthorize = Authorized => currentAuthority => {
-  if (currentAuthority) {
-    if (typeof currentAuthority === 'function') {
-      CURRENT = currentAuthority()
-    }
-    if (Object.prototype.toString.call(currentAuthority) === '[object Promise]') {
-      currentAuthority.then(value => {
-        CURRENT = value
-      })
-    }
-    if (
-      Object.prototype.toString.call(currentAuthority) === '[object String]' ||
-      Array.isArray(currentAuthority)
-    ) {
-      CURRENT = currentAuthority
-    }
-  } else {
-    CURRENT = 'NULL'
-  }
-
-  return Authorized
-}
-
-export { CURRENT }
-export default Authorized => renderAuthorize(Authorized)

+ 0 - 89
src/components/GlobalHeader/AvatarDropdown.jsx

@@ -1,89 +0,0 @@
-import { InfoCircleFilled, LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'
-import { Avatar, Menu, Spin, Modal } from 'antd'
-import React from 'react'
-import { history, connect } from 'umi'
-import HeaderDropdown from '../HeaderDropdown'
-import styles from './index.less'
-
-const AvatarDropdown = ({
-  dispatch,
-  currentUser = {
-    avatar: '',
-    name: ''
-  },
-  menu
-}) => {
-  const onMenuClick = event => {
-    const { key } = event
-
-    if (key === 'logout') {
-      Modal.confirm({
-        icon: <InfoCircleFilled />,
-        title: '温馨提示',
-        content: '是否确认退出系统?',
-        onOk: () => {
-          if (dispatch) {
-            dispatch({
-              type: 'user/logout'
-            })
-          }
-        }
-      })
-      return
-    }
-
-    history.push(`/account/${key}`)
-  }
-
-  const menuHeaderDropdown = (
-    <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
-      {menu && (
-        <Menu.Item key="center">
-          <UserOutlined />
-          个人中心
-        </Menu.Item>
-      )}
-      {menu && (
-        <Menu.Item key="settings">
-          <SettingOutlined />
-          个人设置
-        </Menu.Item>
-      )}
-      {menu && <Menu.Divider />}
-
-      <Menu.Item key="logout">
-        <LogoutOutlined />
-        退出登录
-      </Menu.Item>
-    </Menu>
-  )
-  return currentUser && currentUser.username ? (
-    <HeaderDropdown overlay={menuHeaderDropdown}>
-      <span className={`${styles.action} ${styles.account}`}>
-        <Avatar
-          size="small"
-          className={styles.avatar}
-          src="https://d4.smartcost.com.cn/1.jpg"
-          // src={'https://d4.smartcost.com.cn/1.jpg'}
-          alt="avatar"
-        />
-        {/* src={currentUser.avatar} */}
-        <span className={`${styles.name} anticon`}>{currentUser.username}</span>
-      </span>
-    </HeaderDropdown>
-  ) : (
-    <span className={`${styles.action} ${styles.account}`}>
-      <Spin
-        size="small"
-        style={{
-          marginLeft: 8,
-          marginRight: 8
-        }}
-      />
-    </span>
-  )
-}
-
-export default connect(({ user }) => ({
-  currentUser: user.currentUser
-}))(AvatarDropdown)

+ 0 - 156
src/components/GlobalHeader/NoticeIconView.jsx

@@ -1,156 +0,0 @@
-import React, { Component } from 'react'
-import { connect } from 'umi'
-import { Tag, message } from 'antd'
-import groupBy from 'lodash/groupBy'
-import moment from 'moment'
-import NoticeIcon from '../NoticeIcon'
-import styles from './index.less'
-
-class GlobalHeaderRight extends Component {
-  componentDidMount() {
-    const { dispatch } = this.props
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/fetchNotices'
-      })
-    }
-  }
-
-  changeReadState = (clickedItem) => {
-    const { id } = clickedItem
-    const { dispatch } = this.props
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/changeNoticeReadState',
-        payload: id
-      })
-    }
-  };
-
-  handleNoticeClear = (title, key) => {
-    const { dispatch } = this.props
-    message.success(`${'清空了'} ${title}`)
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/clearNotices',
-        payload: key
-      })
-    }
-  };
-
-  getNoticeData = () => {
-    const { notices = [] } = this.props
-
-    if (!notices || notices.length === 0 || !Array.isArray(notices)) {
-      return {}
-    }
-
-    const newNotices = notices.map((notice) => {
-      const newNotice = { ...notice }
-
-      if (newNotice.datetime) {
-        newNotice.datetime = moment(notice.datetime).fromNow()
-      }
-
-      if (newNotice.id) {
-        newNotice.key = newNotice.id
-      }
-
-      if (newNotice.extra && newNotice.status) {
-        const color = {
-          todo: '',
-          processing: 'blue',
-          urgent: 'red',
-          doing: 'gold'
-        }[newNotice.status]
-        newNotice.extra = (
-          <Tag
-            color={color}
-            style={{
-              marginRight: 0
-            }}
-          >
-            {newNotice.extra}
-          </Tag>
-        )
-      }
-
-      return newNotice
-    })
-    return groupBy(newNotices, 'type')
-  };
-
-  getUnreadData = (noticeData) => {
-    const unreadMsg = {}
-    Object.keys(noticeData).forEach((key) => {
-      const value = noticeData[key]
-
-      if (!unreadMsg[key]) {
-        unreadMsg[key] = 0
-      }
-
-      if (Array.isArray(value)) {
-        unreadMsg[key] = value.filter((item) => !item.read).length
-      }
-    })
-    return unreadMsg
-  };
-
-  render() {
-    const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props
-    const noticeData = this.getNoticeData()
-    const unreadMsg = this.getUnreadData(noticeData)
-    return (
-      <NoticeIcon
-        className={styles.action}
-        count={currentUser && currentUser.unreadCount}
-        onItemClick={(item) => {
-          this.changeReadState(item)
-        }}
-        loading={fetchingNotices}
-        clearText="清空"
-        viewMoreText="查看更多"
-        onClear={this.handleNoticeClear}
-        onPopupVisibleChange={onNoticeVisibleChange}
-        onViewMore={() => message.info('Click on view more')}
-        clearClose
-      >
-        <NoticeIcon.Tab
-          tabKey="notification"
-          count={unreadMsg.notification}
-          list={noticeData.notification}
-          title="通知"
-          emptyText="你已查看所有通知"
-          showViewMore
-        />
-        <NoticeIcon.Tab
-          tabKey="message"
-          count={unreadMsg.message}
-          list={noticeData.message}
-          title="消息"
-          emptyText="您已读完所有消息"
-          showViewMore
-        />
-        <NoticeIcon.Tab
-          tabKey="event"
-          title="待办"
-          emptyText="你已完成所有待办"
-          count={unreadMsg.event}
-          list={noticeData.event}
-          showViewMore
-        />
-      </NoticeIcon>
-    )
-  }
-}
-
-export default connect(({ user, global, loading }) => ({
-  currentUser: user.currentUser,
-  collapsed: global.collapsed,
-  fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
-  fetchingNotices: loading.effects['global/fetchNotices'],
-  notices: global.notices
-}))(GlobalHeaderRight)

+ 0 - 77
src/components/GlobalHeader/RightContent.jsx

@@ -1,77 +0,0 @@
-import { Tag } from 'antd'
-// import { QuestionCircleOutlined } from '@ant-design/icons'
-import React from 'react'
-import { connect } from 'umi'
-import Avatar from './AvatarDropdown'
-// import HeaderSearch from '../HeaderSearch'
-import styles from './index.less'
-
-const ENVTagColor = {
-  dev: 'orange',
-  test: 'green',
-  pre: '#87d068'
-}
-
-const GlobalHeaderRight = props => {
-  const { theme, layout } = props
-  let className = styles.right
-
-  if (theme === 'dark' && layout === 'top') {
-    className = `${styles.right}  ${styles.dark}`
-  }
-
-  return (
-    <div className={className}>
-      {/* <HeaderSearch
-        className={`${styles.action} ${styles.search}`}
-        placeholder="站内搜索"
-        defaultValue="umi ui"
-        options={[
-          {
-            label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>,
-            value: 'umi ui',
-          },
-          {
-            label: <a href="next.ant.design">Ant Design</a>,
-            value: 'Ant Design',
-          },
-          {
-            label: <a href="https://protable.ant.design/">Pro Table</a>,
-            value: 'Pro Table',
-          },
-          {
-            label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
-            value: 'Pro Layout',
-          },
-        ]} // onSearch={value => {
-        //   //console.log('input', value);
-        // }}
-      /> */}
-      {/* <Tooltip title="使用文档">
-        <a
-          style={{
-            color: 'inherit',
-          }}
-          target="_blank"
-          href="https://pro.ant.design/docs/getting-started"
-          rel="noopener noreferrer"
-          className={styles.action}
-        >
-          <QuestionCircleOutlined />
-        </a>
-      </Tooltip> */}
-      <Avatar />
-      {REACT_APP_ENV && REACT_APP_ENV !== 'prod' && (
-        <span>
-          <Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
-        </span>
-      )}
-      {/* <SelectLang className={styles.action} /> */}
-    </div>
-  )
-}
-
-export default connect(({ settings }) => ({
-  theme: settings.navTheme,
-  layout: settings.layout
-}))(GlobalHeaderRight)

+ 0 - 10
src/components/HeaderDropdown/index.jsx

@@ -1,10 +0,0 @@
-import { Dropdown } from 'antd'
-import React from 'react'
-import classNames from 'classnames'
-import styles from './index.less'
-
-const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
-  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
-)
-
-export default HeaderDropdown

+ 18 - 0
src/components/HeaderDropdown/index.tsx

@@ -0,0 +1,18 @@
+/* eslint-disable */
+import type { DropDownProps } from 'antd/es/dropdown'
+import { Dropdown } from 'antd'
+import React from 'react'
+import classNames from 'classnames'
+import styles from './index.less'
+
+export type HeaderDropdownProps = {
+  overlayClassName?: string
+  overlay: React.ReactNode | (() => React.ReactNode) | any
+  placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'
+} & Omit<DropDownProps, 'overlay'>
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => (
+  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
+)
+
+export default HeaderDropdown

+ 0 - 86
src/components/HeaderSearch/index.jsx

@@ -1,86 +0,0 @@
-import { SearchOutlined } from '@ant-design/icons'
-import { AutoComplete, Input } from 'antd'
-import useMergeValue from 'use-merge-value'
-import React, { useRef } from 'react'
-import classNames from 'classnames'
-import styles from './index.less'
-
-const HeaderSearch = (props) => {
-  const {
-    className,
-    defaultValue,
-    onVisibleChange,
-    placeholder,
-    open,
-    defaultOpen,
-    ...restProps
-  } = props
-  const inputRef = useRef(null)
-  const [ value, setValue ] = useMergeValue(defaultValue, {
-    value: props.value,
-    onChange: props.onChange
-  })
-  const [ searchMode, setSearchMode ] = useMergeValue(defaultOpen || false, {
-    value: props.open,
-    onChange: onVisibleChange
-  })
-  const inputClass = classNames(styles.input, {
-    [styles.show]: searchMode
-  })
-  return (
-    <div
-      className={classNames(className, styles.headerSearch)}
-      onClick={() => {
-        setSearchMode(true)
-
-        if (searchMode && inputRef.current) {
-          inputRef.current.focus()
-        }
-      }}
-      onTransitionEnd={({ propertyName }) => {
-        if (propertyName === 'width' && !searchMode) {
-          if (onVisibleChange) {
-            onVisibleChange(searchMode)
-          }
-        }
-      }}
-    >
-      <SearchOutlined
-        key="Icon"
-        style={{
-          cursor: 'pointer'
-        }}
-      />
-      <AutoComplete
-        key="AutoComplete"
-        className={inputClass}
-        value={value}
-        style={{
-          height: 28,
-          marginTop: -6
-        }}
-        options={restProps.options}
-        onChange={setValue}
-      >
-        <Input
-          ref={inputRef}
-          defaultValue={defaultValue}
-          aria-label={placeholder}
-          placeholder={placeholder}
-          onKeyDown={(e) => {
-            if (e.key === 'Enter') {
-              if (restProps.onSearch) {
-                restProps.onSearch(value)
-              }
-            }
-          }}
-          onBlur={() => {
-            setSearchMode(false)
-          }}
-        />
-      </AutoComplete>
-    </div>
-  )
-}
-
-export default HeaderSearch

+ 0 - 30
src/components/HeaderSearch/index.less

@@ -1,30 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.headerSearch {
-  .input {
-    width: 0;
-    min-width: 0;
-    overflow: hidden;
-    background: transparent;
-    border-radius: 0;
-    transition: width 0.3s, margin-left 0.3s;
-    :global(.ant-select-selection) {
-      background: transparent;
-    }
-    input {
-      padding-right: 0;
-      padding-left: 0;
-      border: 0;
-      box-shadow: none !important;
-    }
-    &,
-    &:hover,
-    &:focus {
-      border-bottom: 1px solid @border-color-base;
-    }
-    &.show {
-      width: 210px;
-      margin-left: 8px;
-    }
-  }
-}

+ 0 - 95
src/components/NoticeIcon/NoticeList.jsx

@@ -1,95 +0,0 @@
-import { Avatar, List } from 'antd'
-import React from 'react'
-import classNames from 'classnames'
-import styles from './NoticeList.less'
-
-const NoticeList = ({
-  data = [],
-  onClick,
-  onClear,
-  title,
-  onViewMore,
-  emptyText,
-  showClear = true,
-  clearText,
-  viewMoreText,
-  showViewMore = false
-}) => {
-  if (!data || data.length === 0) {
-    return (
-      <div className={styles.notFound}>
-        <img
-          src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
-          alt="not found"
-        />
-        <div>{emptyText}</div>
-      </div>
-    )
-  }
-
-  return (
-    <div>
-      <List
-        className={styles.list}
-        dataSource={data}
-        renderItem={(item, i) => {
-          const itemCls = classNames(styles.item, {
-            [styles.read]: item.read
-          }) // eslint-disable-next-line no-nested-ternary
-
-          const leftIcon = item.avatar ? (
-            typeof item.avatar === 'string' ? (
-              <Avatar className={styles.avatar} src={item.avatar} />
-            ) : (
-              <span className={styles.iconElement}>{item.avatar}</span>
-            )
-          ) : null
-          return (
-            <List.Item
-              className={itemCls}
-              key={item.key || i}
-              onClick={() => onClick && onClick(item)}
-            >
-              <List.Item.Meta
-                className={styles.meta}
-                avatar={leftIcon}
-                title={
-                  <div className={styles.title}>
-                    {item.title}
-                    <div className={styles.extra}>{item.extra}</div>
-                  </div>
-                }
-                description={
-                  <div>
-                    <div className={styles.description}>{item.description}</div>
-                    <div className={styles.datetime}>{item.datetime}</div>
-                  </div>
-                }
-              />
-            </List.Item>
-          )
-        }}
-      />
-      <div className={styles.bottomBar}>
-        {showClear ? (
-          <div onClick={onClear}>
-            {clearText} {title}
-          </div>
-        ) : null}
-        {showViewMore ? (
-          <div
-            onClick={(e) => {
-              if (onViewMore) {
-                onViewMore(e)
-              }
-            }}
-          >
-            {viewMoreText}
-          </div>
-        ) : null}
-      </div>
-    </div>
-  )
-}
-
-export default NoticeList

+ 0 - 103
src/components/NoticeIcon/NoticeList.less

@@ -1,103 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.list {
-  max-height: 400px;
-  overflow: auto;
-  &::-webkit-scrollbar {
-    display: none;
-  }
-  .item {
-    padding-right: 24px;
-    padding-left: 24px;
-    overflow: hidden;
-    cursor: pointer;
-    transition: all 0.3s;
-
-    .meta {
-      width: 100%;
-    }
-
-    .avatar {
-      margin-top: 4px;
-      background: @component-background;
-    }
-    .iconElement {
-      font-size: 32px;
-    }
-
-    &.read {
-      opacity: 0.4;
-    }
-    &:last-child {
-      border-bottom: 0;
-    }
-    &:hover {
-      background: @primary-1;
-    }
-    .title {
-      margin-bottom: 8px;
-      font-weight: normal;
-    }
-    .description {
-      font-size: 12px;
-      line-height: @line-height-base;
-    }
-    .datetime {
-      margin-top: 4px;
-      font-size: 12px;
-      line-height: @line-height-base;
-    }
-    .extra {
-      float: right;
-      margin-top: -1.5px;
-      margin-right: 0;
-      color: @text-color-secondary;
-      font-weight: normal;
-    }
-  }
-  .loadMore {
-    padding: 8px 0;
-    color: @primary-6;
-    text-align: center;
-    cursor: pointer;
-    &.loadedAll {
-      color: rgba(0, 0, 0, 0.25);
-      cursor: unset;
-    }
-  }
-}
-
-.notFound {
-  padding: 73px 0 88px;
-  color: @text-color-secondary;
-  text-align: center;
-  img {
-    display: inline-block;
-    height: 76px;
-    margin-bottom: 16px;
-  }
-}
-
-.bottomBar {
-  height: 46px;
-  color: @text-color;
-  line-height: 46px;
-  text-align: center;
-  border-top: 1px solid @border-color-split;
-  border-radius: 0 0 @border-radius-base @border-radius-base;
-  transition: all 0.3s;
-  div {
-    display: inline-block;
-    width: 50%;
-    cursor: pointer;
-    transition: all 0.3s;
-    user-select: none;
-
-    &:only-child {
-      width: 100%;
-    }
-    &:not(:only-child):last-child {
-      border-left: 1px solid @border-color-split;
-    }
-  }
-}

+ 0 - 113
src/components/NoticeIcon/index.jsx

@@ -1,113 +0,0 @@
-import { BellOutlined } from '@ant-design/icons'
-import { Badge, Spin, Tabs } from 'antd'
-import useMergeValue from 'use-merge-value'
-import React from 'react'
-import classNames from 'classnames'
-import NoticeList from './NoticeList'
-import HeaderDropdown from '../HeaderDropdown'
-import styles from './index.less'
-
-const { TabPane } = Tabs
-
-const NoticeIcon = (props) => {
-  const getNotificationBox = () => {
-    const {
-      children,
-      loading,
-      onClear,
-      onTabChange,
-      onItemClick,
-      onViewMore,
-      clearText,
-      viewMoreText
-    } = props
-
-    if (!children) {
-      return null
-    }
-
-    const panes = []
-    React.Children.forEach(children, (child) => {
-      if (!child) {
-        return
-      }
-
-      const { list, title, count, tabKey, showClear, showViewMore } = child.props
-      const len = list && list.length ? list.length : 0
-      const msgCount = count || count === 0 ? count : len
-      const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title
-      panes.push(
-        <TabPane tab={tabTitle} key={tabKey}>
-          <NoticeList
-            {...child.props}
-            clearText={clearText}
-            viewMoreText={viewMoreText}
-            data={list}
-            onClear={() => onClear && onClear(title, tabKey)}
-            onClick={(item) => onItemClick && onItemClick(item, child.props)}
-            onViewMore={(event) => onViewMore && onViewMore(child.props, event)}
-            showClear={showClear}
-            showViewMore={showViewMore}
-            title={title}
-          />
-        </TabPane>
-      )
-    })
-    return (
-      <Spin spinning={loading} delay={300}>
-        <Tabs className={styles.tabs} onChange={onTabChange}>
-          {panes}
-        </Tabs>
-      </Spin>
-    )
-  }
-
-  const { className, count, bell } = props
-  const [ visible, setVisible ] = useMergeValue(false, {
-    value: props.popupVisible,
-    onChange: props.onPopupVisibleChange
-  })
-  const noticeButtonClass = classNames(className, styles.noticeButton)
-  const notificationBox = getNotificationBox()
-  const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />
-  const trigger = (
-    <span
-      className={classNames(noticeButtonClass, {
-        opened: visible
-      })}
-    >
-      <Badge
-        count={count}
-        style={{
-          boxShadow: 'none'
-        }}
-        className={styles.badge}
-      >
-        {NoticeBellIcon}
-      </Badge>
-    </span>
-  )
-
-  if (!notificationBox) {
-    return trigger
-  }
-
-  return (
-    <HeaderDropdown
-      placement="bottomRight"
-      overlay={notificationBox}
-      overlayClassName={styles.popover}
-      trigger={[ 'click' ]}
-      visible={visible}
-      onVisibleChange={setVisible}
-    >
-      {trigger}
-    </HeaderDropdown>
-  )
-}
-
-NoticeIcon.defaultProps = {
-  emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg'
-}
-NoticeIcon.Tab = NoticeList
-export default NoticeIcon

+ 0 - 35
src/components/NoticeIcon/index.less

@@ -1,35 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.popover {
-  position: relative;
-  width: 336px;
-}
-
-.noticeButton {
-  display: inline-block;
-  cursor: pointer;
-  transition: all 0.3s;
-}
-.icon {
-  padding: 4px;
-  vertical-align: middle;
-}
-
-.badge {
-  font-size: 16px;
-}
-
-.tabs {
-  :global {
-    .ant-tabs-nav-list {
-      margin: auto;
-    }
-
-    .ant-tabs-nav-scroll {
-      text-align: center;
-    }
-    .ant-tabs-bar {
-      margin-bottom: 0;
-    }
-  }
-}

+ 119 - 0
src/components/RightContent/AvatarDropdown.tsx

@@ -0,0 +1,119 @@
+/* eslint-disable */
+import React, { useCallback } from 'react'
+import { InfoCircleFilled, LogoutOutlined } from '@ant-design/icons'
+import { Avatar, Menu, Spin, Modal } from 'antd'
+import { history, useModel } from 'umi'
+import { stringify } from 'querystring'
+import HeaderDropdown from '../HeaderDropdown'
+import styles from './index.less'
+
+export type GlobalHeaderRightProps = {
+  menu?: boolean
+}
+
+/**
+ * 退出登录,并且将当前的 url 保存
+ */
+const loginOut = async () => {
+  const { query = {}, pathname } = history.location
+  const { redirect } = query
+  // Note: There may be security issues, please note
+  if (window.location.pathname !== '/user/login' && !redirect) {
+    history.replace({
+      pathname: '/user/login',
+      search: stringify({
+        redirect: pathname
+      })
+    })
+  }
+}
+
+const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
+  const { initialState, setInitialState } = useModel('@@initialState')
+
+  const onMenuClick = useCallback(
+    (event: {
+      key: React.Key
+      keyPath: React.Key[]
+      item: React.ReactInstance
+      domEvent: React.MouseEvent<HTMLElement>
+    }) => {
+      const { key } = event
+      if (key === 'logout' && initialState) {
+        Modal.confirm({
+          icon: <InfoCircleFilled />,
+          title: '温馨提示',
+          content: '是否确认退出系统?',
+          onOk: () => {
+            setInitialState({ ...initialState, currentUser: undefined, roles: [], permData: {} })
+            loginOut()
+            return
+          }
+        })
+      }
+      history.push(`/account/${key}`)
+    },
+    [initialState, setInitialState]
+  )
+
+  const loading = (
+    <span className={`${styles.action} ${styles.account}`}>
+      <Spin
+        size="small"
+        style={{
+          marginLeft: 8,
+          marginRight: 8
+        }}
+      />
+    </span>
+  )
+
+  if (!initialState) {
+    return loading
+  }
+
+  const { currentUser } = initialState
+
+  if (!currentUser || !currentUser.staffId) {
+    return loading
+  }
+
+  const menuHeaderDropdown = (
+    <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
+      {/* {menu && (
+        <Menu.Item key="center">
+          <UserOutlined />
+          个人中心
+        </Menu.Item>
+      )}
+      {menu && (
+        <Menu.Item key="settings">
+          <SettingOutlined />
+          个人设置
+        </Menu.Item>
+      )}
+      {menu && <Menu.Divider />} */}
+
+      <Menu.Item key="logout">
+        <LogoutOutlined />
+        退出登录
+      </Menu.Item>
+    </Menu>
+  )
+
+  return (
+    <HeaderDropdown overlay={menuHeaderDropdown}>
+      <span className={`${styles.action} ${styles.account}`}>
+        <Avatar
+          size="small"
+          className={styles.avatar}
+          src={`http://cld.smartcost.com.cn${currentUser.avatar}_2.jpg`}
+          alt="avatar"
+        />
+        <span className={`${styles.name} anticon`}>{currentUser.username}</span>
+      </span>
+    </HeaderDropdown>
+  )
+}
+
+export default AvatarDropdown

+ 12 - 32
src/components/GlobalHeader/index.less

@@ -11,6 +11,17 @@
   }
 }
 
+.dark {
+  .action {
+    &:hover {
+      background: #252a3d;
+    }
+    &:global(.opened) {
+      background: #252a3d;
+    }
+  }
+}
+
 .right {
   display: flex;
   float: right;
@@ -20,7 +31,7 @@
   .action {
     display: flex;
     align-items: center;
-    height: 100%;
+    height: 48px;
     padding: 0 12px;
     cursor: pointer;
     transition: all 0.3s;
@@ -42,7 +53,6 @@
   }
   .account {
     .avatar {
-      margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
       margin-right: 8px;
       color: @primary-color;
       vertical-align: top;
@@ -50,33 +60,3 @@
     }
   }
 }
-
-.dark {
-  .action {
-    color: rgba(255, 255, 255, 0.85);
-    > span {
-      color: rgba(255, 255, 255, 0.85);
-    }
-    &:hover,
-    &:global(.opened) {
-      background: @primary-color;
-    }
-  }
-}
-
-:global(.ant-pro-global-header) {
-  .dark {
-    .action {
-      color: @text-color;
-      > span {
-        color: @text-color;
-      }
-      &:hover {
-        color: rgba(255, 255, 255, 0.85);
-        > span {
-          color: rgba(255, 255, 255, 0.85);
-        }
-      }
-    }
-  }
-}

+ 76 - 0
src/components/RightContent/index.tsx

@@ -0,0 +1,76 @@
+/* eslint-disable */
+import { Tag, Space } from 'antd'
+// import { QuestionCircleOutlined } from '@ant-design/icons'
+import React from 'react'
+import { useModel, SelectLang } from 'umi'
+import Avatar from './AvatarDropdown'
+// import HeaderSearch from '../HeaderSearch'
+import styles from './index.less'
+import BasicModal, { BasicDrawer } from '@/components/Modal'
+
+const ENVTagColor = {
+  dev: 'orange',
+  test: 'green',
+  pre: '#87d068'
+}
+
+const GlobalHeaderRight: React.FC = () => {
+  const { initialState } = useModel('@@initialState')
+
+  if (!initialState || !initialState.settings) {
+    return null
+  }
+
+  const { navTheme, layout } = initialState.settings
+  let className = styles.right
+
+  if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') {
+    className = `${styles.right}  ${styles.dark}`
+  }
+  return (
+    <Space className={className}>
+      {/* <HeaderSearch
+        className={`${styles.action} ${styles.search}`}
+        placeholder="站内搜索"
+        defaultValue="umi ui"
+        options={[
+          { label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
+          {
+            label: <a href="next.ant.design">Ant Design</a>,
+            value: 'Ant Design',
+          },
+          {
+            label: <a href="https://protable.ant.design/">Pro Table</a>,
+            value: 'Pro Table',
+          },
+          {
+            label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
+            value: 'Pro Layout',
+          },
+        ]}
+        onSearch={value => {
+          console.log('input', value);
+        }}
+      /> */}
+      {/* <span
+        className={styles.action}
+        onClick={() => {
+          window.open('https://pro.ant.design/docs/getting-started');
+        }}
+      >
+        <QuestionCircleOutlined />
+      </span> */}
+      <Avatar />
+      <BasicModal />
+      <BasicDrawer />
+      {REACT_APP_ENV && (
+        <span>
+          <Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
+        </span>
+      )}
+      <SelectLang className={styles.action} />
+    </Space>
+  )
+}
+export default GlobalHeaderRight
+export type SiderTheme = 'light' | 'dark'

+ 0 - 148
src/layouts/BasicLayout.jsx

@@ -1,148 +0,0 @@
-/**
- * Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
- * You can view component api by:
- * https://github.com/ant-design/ant-design-pro-layout
- */
-import ProLayout from '@ant-design/pro-layout'
-import zhCN from 'antd/lib/locale/zh_CN'
-import React, { useMemo, useRef, useEffect } from 'react'
-import { Link, useIntl, connect, history } from 'umi'
-import { Result, ConfigProvider } from 'antd'
-import Authorized from '@/utils/auth/Authorized'
-import RightContent from '@/components/GlobalHeader/RightContent'
-import logo from '../../public/logo.svg'
-import styles from './BasicLayout.less'
-import BasicModal, { BasicDrawer } from '@/components/Modal'
-import { matchPath } from 'umi'
-
-const noMatch = <Result status={403} title="403" subTitle="抱歉, 您没有权限访问此页面" />
-
-/**
- * use Authorized check all menu item
- */
-const menuDataRender = menuList =>
-  menuList.map(item => {
-    const localItem = {
-      ...item,
-      children: item.children ? menuDataRender(item.children) : undefined
-    }
-    // if (item.icon) {
-    //   localItem.icon = <iconpark-icon name={item.icon} size="16" class="anticon anticon-form" />
-    // }
-    return Authorized.check(item.authority, localItem, null)
-  })
-
-const BasicLayout = props => {
-  const {
-    dispatch,
-    children,
-    settings,
-    location = {
-      pathname: '/',
-      key: ''
-    },
-    route: routeForAuthorized
-  } = props
-  const menuDataRef = useRef([])
-  /**
-   * init variables
-   */
-
-  const handleMenuCollapse = payload => {
-    if (dispatch) {
-      dispatch({
-        type: 'global/changeLayoutCollapsed',
-        payload
-      })
-    }
-  } // get children authority
-
-  // const authorized = useMemo(() => {
-  //   return (
-  //     getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
-  //       authority: undefined
-  //     }
-  //   )
-  // }, [location.pathname])
-
-  useEffect(() => {
-    dispatch({
-      type: 'global/fetchPermData'
-    })
-  }, [])
-  const authorized = useMemo(() => {
-    const path = location.pathname
-    // 必然匹配了
-    const list = [routeForAuthorized]
-    let current = routeForAuthorized
-    while (current) {
-      if (!current.children) break
-      current = current.children
-        .filter(it => it.path)
-        .sort((a, b) => b.path.length - a.path.length)
-        .find(it => matchPath(path, it))
-      if (!current) break
-      list.unshift(current)
-    }
-    return {
-      authority: list.map(it => it.authority).find(it => it)
-    }
-  }, [routeForAuthorized, location.pathname])
-
-  const { formatMessage } = useIntl()
-  return (
-    <ProLayout
-      className={styles.layoutBg}
-      logo={logo}
-      formatMessage={formatMessage}
-      {...props}
-      {...settings}
-      onCollapse={handleMenuCollapse}
-      onMenuHeaderClick={() => history.push('/')}
-      menuItemRender={(menuItemProps, defaultDom) => {
-        if (menuItemProps.isUrl || !menuItemProps.path) {
-          return defaultDom
-        }
-
-        return <Link to={menuItemProps.path}>{defaultDom}</Link>
-      }}
-      breadcrumbRender={(routers = []) => [
-        {
-          path: '/',
-          breadcrumbName: formatMessage({
-            id: 'menu.home'
-          })
-        },
-        ...routers
-      ]}
-      itemRender={(route, params, routes, paths) => {
-        const first = routes.indexOf(route) === 0
-        return first ? (
-          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
-        ) : (
-          <span>{route.breadcrumbName}</span>
-        )
-      }}
-      // footerRender={() => defaultFooterDom}
-      menuDataRender={menuDataRender}
-      rightContentRender={() => <RightContent />}
-      postMenuData={menuData => {
-        // console.log('menuData', menuData)
-        menuDataRef.current = menuData || []
-        return menuData || []
-      }}>
-      <ConfigProvider locale={zhCN}>
-        <Authorized authority={authorized.authority} noMatch={noMatch}>
-          {children}
-        </Authorized>
-      </ConfigProvider>
-      <BasicDrawer />
-      <BasicModal />
-    </ProLayout>
-  )
-}
-
-export default connect(({ global, settings }) => ({
-  collapsed: global.collapsed,
-  settings
-}))(BasicLayout)

+ 0 - 27
src/layouts/BasicLayout.less

@@ -1,27 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-.layoutBg {
-  :global(.ant-pro-sider) {
-    color: #333333;
-    background: #ffffff;
-  }
-  :global(.ant-drawer-body) {
-    padding: 0;
-  }
-
-  :global(.ant-pro-top-nav-header-logo h1) {
-    font-weight: 600;
-    font-size: 18px;
-  }
-
-  // :global(.ant-pro-basicLayout .ant-layout-header.ant-pro-fixed-header) {
-  //   background-color: linear-gradient(to right, #584475 0%, #7c62a4 50%, #7c62a4 50%, #584475 100%);
-  // }
-
-  :global(.ant-menu-dark .ant-menu-inline.ant-menu-sub) {
-    color: #333333;
-    background: #ffffff;
-  }
-  // :global(.ant-table-body) {
-  //   height: calc(100vh - 48px - 48px - 64px - 46px - 60px);
-  // }
-}

+ 0 - 5
src/layouts/BlankLayout.jsx

@@ -1,5 +0,0 @@
-import React from 'react'
-
-const Layout = ({ children }) => <>{children}</>
-
-export default Layout

+ 0 - 53
src/layouts/SecurityLayout.jsx

@@ -1,53 +0,0 @@
-import React from 'react'
-import { PageLoading } from '@ant-design/pro-layout'
-import { Redirect, connect } from 'umi'
-import { stringify } from 'querystring'
-
-class SecurityLayout extends React.Component {
-  state = {
-    isReady: false
-  }
-
-  componentDidMount() {
-    this.setState({
-      isReady: true
-    })
-    const { dispatch } = this.props
-
-    if (dispatch) {
-      dispatch({
-        type: 'user/fetchCurrent'
-      })
-      dispatch({
-        type: 'global/fetchPermData'
-      })
-    }
-  }
-
-  render() {
-    const { isReady } = this.state
-    const { children, loading, currentUser } = this.props // You can replace it to your authentication rule (such as check token exists)
-    // 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)
-
-    const isLogin = currentUser && currentUser.staffId
-
-    const queryString = stringify({
-      redirect: window.location.href
-    })
-
-    if ((!isLogin && loading) || !isReady) {
-      return <PageLoading />
-    }
-
-    if (!isLogin && window.location.pathname !== '/user/login') {
-      return <Redirect to={`/user/login?${queryString}`} />
-    }
-
-    return children
-  }
-}
-
-export default connect(({ user, loading }) => ({
-  currentUser: user.currentUser,
-  loading: loading.models.user
-}))(SecurityLayout)

+ 0 - 58
src/layouts/UserLayout.jsx

@@ -1,58 +0,0 @@
-import { getMenuData, getPageTitle } from '@ant-design/pro-layout'
-import { Helmet, HelmetProvider } from 'react-helmet-async'
-import { SelectLang, useIntl, connect, FormattedMessage } from 'umi'
-import React from 'react'
-import logo from '../assets/logo.svg'
-import styles from './UserLayout.less'
-
-const UserLayout = props => {
-  const {
-    route = {
-      routes: []
-    }
-  } = props
-  const { routes = [] } = route
-  const {
-    children,
-    location = {
-      pathname: ''
-    }
-  } = props
-  const { formatMessage } = useIntl()
-  const { breadcrumb } = getMenuData(routes)
-  const title = getPageTitle({
-    pathname: location.pathname,
-    formatMessage,
-    breadcrumb,
-    ...props
-  })
-  return (
-    <HelmetProvider>
-      <Helmet>
-        <title>{title}</title>
-        <meta name="description" content={title} />
-      </Helmet>
-
-      <div className={styles.container}>
-        <div className={styles.lang}>
-          <SelectLang />
-        </div>
-        <div className={styles.content}>
-          <div className={styles.top}>
-            <div className={styles.header}>
-              <img alt="logo" className={styles.logo} src={logo} />
-              <span className={styles.title}>CLD.SystemV2</span>
-            </div>
-            <div className={styles.desc}>
-              <FormattedMessage id="pages.layouts.userLayout.title" defaultMessage="纵横办公系统" />
-            </div>
-          </div>
-          {children}
-        </div>
-        {/* <DefaultFooter /> */}
-      </div>
-    </HelmetProvider>
-  )
-}
-
-export default connect(({ settings }) => ({ ...settings }))(UserLayout)

+ 0 - 69
src/layouts/UserLayout.less

@@ -1,69 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.container {
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-  overflow: auto;
-  background: @layout-body-background;
-}
-
-.lang {
-  width: 100%;
-  height: 40px;
-  line-height: 44px;
-  text-align: right;
-  :global(.ant-dropdown-trigger) {
-    margin-right: 24px;
-  }
-}
-
-.content {
-  flex: 1;
-  padding: 32px 0;
-}
-
-@media (min-width: @screen-md-min) {
-  .container {
-    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
-    background-repeat: no-repeat;
-    background-position: center 110px;
-    background-size: 100%;
-  }
-
-  .content {
-    padding: 32px 0 24px;
-  }
-}
-
-.top {
-  text-align: center;
-}
-
-.header {
-  height: 44px;
-  line-height: 44px;
-  @apply flex justify-center items-center;
-}
-
-.logo {
-  height: 44px;
-  margin-right: 16px;
-  vertical-align: top;
-}
-
-.title {
-  position: relative;
-  top: 2px;
-  color: @heading-color;
-  font-weight: 600;
-  font-size: 33px;
-  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
-}
-
-.desc {
-  margin-top: 12px;
-  margin-bottom: 40px;
-  color: @text-color-secondary;
-  font-size: @font-size-base;
-}

+ 1 - 0
src/locales/zh-CN/pages.js

@@ -19,5 +19,6 @@ export default {
   'pages.login.forgotPassword': '忘记密码 ?',
   'pages.login.submit': '提交',
   'pages.login.loginWith': '其他登录方式 :',
+  'pages.login.success': '登录成功',
   'pages.login.registerAccount': '注册账户'
 }

+ 0 - 49
src/models/global.js

@@ -1,49 +0,0 @@
-// import { queryNotices } from '@/services/user'
-
-import consts from '@/basic/consts'
-import { getDataPerm } from '@/services/user'
-import { history } from 'umi'
-import { getToken } from '@/utils/auth'
-
-const GlobalModel = {
-  namespace: 'global',
-  state: {
-    collapsed: false,
-    notices: [],
-    premData: {}
-  },
-  effects: {
-    *fetchPermData(_, { put }) {
-      if (window.location.pathname === '/user/login') {
-        return
-      }
-      const token = getToken()
-      if (!token) {
-        history.replace('/user/login')
-        return
-      }
-      const response = yield getDataPerm()
-      if (response?.code === consts.RET_CODE.SUCCESS) {
-        yield put({
-          type: 'ChangePermData',
-          payload: response.data || {}
-        })
-      }
-    }
-  },
-  reducers: {
-    changeLayoutCollapsed(
-      state = {
-        notices: [],
-        collapsed: true
-      },
-      { payload }
-    ) {
-      return { ...state, collapsed: payload }
-    },
-    ChangePermData(state, { payload }) {
-      return { ...state, premData: payload }
-    }
-  }
-}
-export default GlobalModel

+ 0 - 27
src/models/setting.js

@@ -1,27 +0,0 @@
-import defaultSettings from '../../config/defaultSettings'
-
-const updateColorWeak = (colorWeak) => {
-  const root = document.getElementById('root')
-
-  if (root) {
-    root.className = colorWeak ? 'colorWeak' : ''
-  }
-}
-
-const SettingModel = {
-  namespace: 'settings',
-  state: defaultSettings,
-  reducers: {
-    changeSetting(state = defaultSettings, { payload }) {
-      const { colorWeak, contentWidth } = payload
-
-      if (state.contentWidth !== contentWidth && window.dispatchEvent) {
-        window.dispatchEvent(new Event('resize'))
-      }
-
-      updateColorWeak(!!colorWeak)
-      return { ...state, ...payload }
-    }
-  }
-}
-export default SettingModel

+ 0 - 100
src/models/user.js

@@ -1,100 +0,0 @@
-import consts from '@/consts'
-import { apiLogin } from '@/services/login'
-import { apiCurrent, apiGetRoles } from '@/services/user'
-import { clearAuthCache, getToken, setAuthCache } from '@/utils/auth'
-import { getPageQuery } from '@/utils/utils'
-import { message } from 'antd'
-import { stringify } from 'qs'
-import { history } from 'umi'
-import { TOKEN_KEY } from '@/utils/cache/cacheEnum'
-import { setAuthority } from '@/utils/auth/authority'
-
-const UserModel = {
-  namespace: 'user',
-  state: {
-    currentUser: {},
-    loginStatus: undefined
-  },
-  effects: {
-    *login({ payload }, { call, put }) {
-      // 获取登录接口数据
-      const response = yield call(apiLogin, payload)
-      if (response?.code !== consts.RET_CODE.SUCCESS) {
-        yield put({
-          type: 'changeLoginStatus',
-          payload: { status: response?.code || -1 }
-        })
-        return
-      }
-      if (response?.code === consts.RET_CODE.SUCCESS) {
-        // Login successfully
-        setAuthCache(TOKEN_KEY, response.data.token)
-        const { data: currentAuthority = [] } = yield call(apiGetRoles)
-        yield put({
-          type: 'changeLoginStatus',
-          payload: { status: response?.code, currentAuthority }
-        })
-        const urlParams = new URL(window.location.href)
-        const params = getPageQuery()
-        message.success('🎉 🎉 🎉  登录成功')
-        let { redirect } = params
-        if (redirect) {
-          const redirectUrlParams = new URL(redirect)
-          if (redirectUrlParams.origin === urlParams.origin) {
-            redirect = redirect.substr(urlParams.origin.length)
-
-            if (redirect.match(/^\/.*#/)) {
-              redirect = redirect.substr(redirect.indexOf('#') + 1)
-            }
-          } else {
-            window.location.href = '/'
-            return
-          }
-        }
-        history.replace(redirect || '/')
-      }
-    },
-
-    logout() {
-      const { redirect } = getPageQuery() // Note: There may be security issues, please note
-      if (window.location.pathname !== '/user/login' && !redirect) {
-        clearAuthCache()
-        history.replace({
-          pathname: '/user/login',
-          search: stringify({
-            redirect: window.location.href
-          })
-        })
-      }
-      // window.location.reload()
-    },
-    *fetchCurrent(_, { call, put }) {
-      if (window.location.pathname === '/user/login') {
-        return
-      }
-      const token = getToken()
-      if (!token) {
-        history.replace('/user/login')
-        return
-      }
-      const response = yield call(apiCurrent)
-      if (response?.code === consts.RET_CODE.SUCCESS) {
-        yield put({
-          type: 'saveCurrentUser',
-          payload: response.data
-        })
-      }
-    }
-  },
-  reducers: {
-    saveCurrentUser(state, action) {
-      return { ...state, currentUser: action.payload || {} }
-    },
-
-    changeLoginStatus(state, { payload }) {
-      setAuthority(payload.currentAuthority)
-      return { ...state, loginStatus: payload.status }
-    }
-  }
-}
-export default UserModel

+ 5 - 5
src/pages/Customer/Business/index.jsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect, useRef } from 'react'
-import { connect } from 'umi'
+import { connect, useModel } from 'umi'
 import { getBusinessList } from '@/services/customer'
 import ProTable from '@ant-design/pro-table'
 import consts from '@/consts'
@@ -11,7 +11,8 @@ import { useTableScroll } from '@/hooks/useAutoTable'
 import CompanyDetail from '../Company/components/CompanyDetail'
 import { Funnel } from '@ant-design/charts'
 
-const Business = ({ dispatch, groupList, shouldUpdate, permData, loading }) => {
+const Business = ({ dispatch, groupList, shouldUpdate, loading }) => {
+  const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
   const tRef = useRef(null)
   const { toggleDrawer, setDrawerProps } = useModal()
   const [state, setState] = useState({
@@ -213,9 +214,8 @@ const Business = ({ dispatch, groupList, shouldUpdate, permData, loading }) => {
   )
 }
 
-export default connect(({ business, refresh, global, loading }) => ({
+export default connect(({ business, refresh, loading }) => ({
   groupList: business.groupList,
   loading: loading.models.business,
-  shouldUpdate: refresh.business,
-  permData: global.premData
+  shouldUpdate: refresh.business
 }))(Business)

+ 4 - 6
src/pages/Customer/Client/index.jsx

@@ -3,7 +3,7 @@ import CompanyDetail from '@/pages/Customer/Company/components/CompanyDetail'
 import AddContact from '@/pages/Customer/Client/components/AddClient'
 import ProTable from '@ant-design/pro-table'
 import { Input, Button, message, Tooltip, Select } from 'antd'
-import { connect } from 'umi'
+import { connect, useModel } from 'umi'
 import React, { useEffect, useState, useRef, useMemo } from 'react'
 import consts from '@/consts'
 import LazyCascader from '@/components/LazyCascader'
@@ -30,10 +30,9 @@ const Client = props => {
     teamTags,
     dispatch,
     shouldUpdate,
-    permData,
     loading
   } = props
-
+  const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
   const tRef = useRef()
   const [tagState, setTagState] = useState({
     tagIds: [],
@@ -615,12 +614,11 @@ const Client = props => {
   )
 }
 
-export default connect(({ client, refresh, global, loading }) => ({
+export default connect(({ client, refresh, loading }) => ({
   personTagColorMap: client.personTagColorMap,
   personTags: client.personTags,
   teamTagColorMap: client.teamTagColorMap,
   teamTags: client.teamTags,
   loading: loading.models.client,
-  shouldUpdate: refresh.client,
-  permData: global.premData
+  shouldUpdate: refresh.client
 }))(Client)

+ 4 - 4
src/pages/Customer/Company/index.jsx

@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'
 import ProTable from '@ant-design/pro-table'
 import { useTableScroll } from '@/hooks/useAutoTable'
 import { Input, Button, message, Select, Tooltip } from 'antd'
-import { connect } from 'dva'
+import { connect, useModel } from 'umi'
 import consts from '@/consts'
 import { queryCompany, apiAddCompany } from '@/services/customer'
 import LazyCascader from '@/components/LazyCascader'
@@ -24,9 +24,10 @@ const Company = props => {
     teamTags,
     dispatch,
     shouldUpdate,
-    permData,
     loading
   } = props
+  const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
+  console.log('permData', permData)
   const { toggleDrawer, setDrawerProps } = useModal()
   const tRef = useRef(null)
   const [selectKeys, setSelectKeys] = useState([])
@@ -500,12 +501,11 @@ const Company = props => {
   )
 }
 
-export default connect(({ company, refresh, global, loading }) => ({
+export default connect(({ company, refresh, loading }) => ({
   personTagColorMap: company.personTagColorMap,
   personTags: company.personTags,
   teamTagColorMap: company.teamTagColorMap,
   teamTags: company.teamTags,
   loading: loading.models.company,
-  permData: global.premData,
   shouldUpdate: refresh.company
 }))(Company)

+ 181 - 118
src/pages/user/login/index.jsx

@@ -1,11 +1,11 @@
-import { LockTwoTone, UserOutlined } from '@ant-design/icons'
-import { Alert, Tabs } from 'antd'
+import { LockOutlined, UserOutlined } from '@ant-design/icons'
+import { Alert, message, Tabs } from 'antd'
 import React, { useState } from 'react'
 import ProForm, { ProFormCheckbox, ProFormText } from '@ant-design/pro-form'
-import { connect, useIntl, FormattedMessage } from 'umi'
-// import { getFakeCaptcha } from '@/services/login'
+import { useIntl, Link, history, FormattedMessage, SelectLang, useModel } from 'umi'
+
 import styles from './index.less'
-import consts from '@/consts'
+import { apiLogin } from '@/services/login'
 
 const LoginMessage = ({ content }) => (
   <Alert
@@ -18,131 +18,194 @@ const LoginMessage = ({ content }) => (
   />
 )
 
-const Login = props => {
-  const { userLogin = {}, submitting } = props
-  const { loginStatus: status } = userLogin
+/** 此方法会跳转到 redirect 参数所在的位置 */
+const goto = () => {
+  if (!history) return
+  setTimeout(() => {
+    const { query } = history.location
+    const { redirect } = query
+    history.push(redirect || '/')
+  }, 10)
+}
+
+const Login = () => {
+  const [submitting, setSubmitting] = useState(false)
+  const [userLoginState, setUserLoginState] = useState({})
   const [type, setType] = useState('account')
+  const { initialState, setInitialState } = useModel('@@initialState')
+
   const intl = useIntl()
 
-  const handleSubmit = values => {
-    const { dispatch } = props
-    dispatch({
-      type: 'user/login',
-      payload: { ...values, type }
-    })
-    // dispatch({
-    //   type: 'user/fetchCurrent'
-    // })
+  const fetchUserInfo = async () => {
+    const userInfo = await initialState?.fetchUserInfo?.()
+    if (userInfo) {
+      setInitialState({ ...initialState, ...userInfo })
+    }
   }
 
+  const handleSubmit = async values => {
+    setSubmitting(true)
+    try {
+      // 登录
+      const msg = await apiLogin({ ...values })
+      if (msg.code === 0) {
+        const defaultLoginSuccessMessage = intl.formatMessage({
+          id: 'pages.login.success',
+          defaultMessage: '登录成功!'
+        })
+        message.success(defaultLoginSuccessMessage)
+        await fetchUserInfo()
+        /** 此方法会跳转到 redirect 参数所在的位置 */
+        return goto()
+      }
+      // 如果失败去设置用户错误信息
+      setUserLoginState(msg)
+    } catch (error) {
+      const defaultLoginFailureMessage = intl.formatMessage({
+        id: 'pages.login.failure',
+        defaultMessage: '登录失败,请重试!'
+      })
+
+      return message.error(defaultLoginFailureMessage)
+    }
+    return setSubmitting(false)
+  }
+  const { status, type: loginType } = userLoginState
+
   return (
-    <div className={styles.main}>
-      <ProForm
-        initialValues={{
-          autoLogin: true
-        }}
-        submitter={{
-          render: (_, dom) => dom.pop(),
-          submitButtonProps: {
-            loading: submitting,
-            size: 'large',
-            style: {
-              width: '100%'
-            }
-          }
-        }}
-        onFinish={async values => {
-          handleSubmit(values)
-          return Promise.resolve()
-        }}>
-        <Tabs activeKey={type} onChange={setType} className={styles.tabMargin}>
-          <Tabs.TabPane
-            key="account"
-            tab={intl.formatMessage({
-              id: 'pages.login.accountLogin.tab',
-              defaultMessage: '账户密码登录'
-            })}
-          />
-          {/* <Tabs.TabPane
-            key="mobile"
-            tab={intl.formatMessage({
-              id: 'pages.login.phoneLogin.tab',
-              defaultMessage: '手机号登录',
-            })}
-          /> */}
-        </Tabs>
+    <div className={styles.container}>
+      <div className={styles.lang} data-lang>
+        {SelectLang && <SelectLang />}
+      </div>
+      <div className={styles.content}>
+        <div className={styles.top}>
+          <div className={styles.header}>
+            <Link to="/">
+              <img alt="logo" className={styles.logo} src="/logo.svg" />
+              <span className={styles.title}>Ant Design</span>
+            </Link>
+          </div>
+          <div className={styles.desc}>
+            {intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
+          </div>
+        </div>
 
-        {status && status !== consts.RET_CODE.SUCCESS && !submitting ? (
-          <LoginMessage
-            content={intl.formatMessage({
-              id: 'pages.login.accountLogin.errorMessage',
-              defaultMessage: '账户或密码错误'
-            })}
-          />
-        ) : null}
-        {type === 'account' && (
-          <>
-            <ProFormText
-              name="username"
-              fieldProps={{
+        <div className={styles.main}>
+          <ProForm
+            initialValues={{
+              autoLogin: true
+            }}
+            submitter={{
+              searchConfig: {
+                submitText: intl.formatMessage({
+                  id: 'pages.login.submit',
+                  defaultMessage: '登录'
+                })
+              },
+              render: (_, dom) => dom.pop(),
+              submitButtonProps: {
+                loading: submitting,
                 size: 'large',
-                prefix: <UserOutlined className={styles.prefixIcon} />
-              }}
-              placeholder={'账号:'}
-              rules={[
-                {
-                  required: true,
-                  message: (
-                    <FormattedMessage
-                      id="pages.login.username.required"
-                      defaultMessage="请输入用户名!"
-                    />
-                  )
+                style: {
+                  width: '100%'
                 }
-              ]}
-            />
-            <ProFormText.Password
-              name="password"
-              fieldProps={{
-                size: 'large',
-                prefix: <LockTwoTone className={styles.prefixIcon} />
-              }}
-              placeholder={'密码:'}
-              className="pi-bg-gray"
-              rules={[
-                {
-                  required: true,
-                  message: (
-                    <FormattedMessage
-                      id="pages.login.password.required"
-                      defaultMessage="请输入密码!"
-                    />
-                  )
-                }
-              ]}
-            />
-          </>
-        )}
-        <div
-          style={{
-            marginBottom: 24
-          }}>
-          <ProFormCheckbox noStyle name="autoLogin">
-            <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
-          </ProFormCheckbox>
-          <a
-            style={{
-              float: 'right'
+              }
+            }}
+            onFinish={async values => {
+              handleSubmit(values)
             }}>
-            <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
-          </a>
+            <Tabs activeKey={type} onChange={setType}>
+              <Tabs.TabPane
+                key="account"
+                tab={intl.formatMessage({
+                  id: 'pages.login.accountLogin.tab',
+                  defaultMessage: '账户密码登录'
+                })}
+              />
+              {/* <Tabs.TabPane
+                key="mobile"
+                tab={intl.formatMessage({
+                  id: 'pages.login.phoneLogin.tab',
+                  defaultMessage: '手机号登录',
+                })}
+              /> */}
+            </Tabs>
+
+            {status === 'error' && loginType === 'account' && (
+              <LoginMessage
+                content={intl.formatMessage({
+                  id: 'pages.login.accountLogin.errorMessage',
+                  defaultMessage: '账户或密码错误'
+                })}
+              />
+            )}
+            {type === 'account' && (
+              <>
+                <ProFormText
+                  name="username"
+                  fieldProps={{
+                    size: 'large',
+                    prefix: <UserOutlined className={styles.prefixIcon} />
+                  }}
+                  placeholder={intl.formatMessage({
+                    id: 'pages.login.username.placeholder',
+                    defaultMessage: '用户名: admin or user'
+                  })}
+                  rules={[
+                    {
+                      required: true,
+                      message: (
+                        <FormattedMessage
+                          id="pages.login.username.required"
+                          defaultMessage="请输入用户名!"
+                        />
+                      )
+                    }
+                  ]}
+                />
+                <ProFormText.Password
+                  name="password"
+                  fieldProps={{
+                    size: 'large',
+                    prefix: <LockOutlined className={styles.prefixIcon} />
+                  }}
+                  placeholder={intl.formatMessage({
+                    id: 'pages.login.password.placeholder',
+                    defaultMessage: '密码: ant.design'
+                  })}
+                  rules={[
+                    {
+                      required: true,
+                      message: (
+                        <FormattedMessage
+                          id="pages.login.password.required"
+                          defaultMessage="请输入密码!"
+                        />
+                      )
+                    }
+                  ]}
+                />
+              </>
+            )}
+            <div
+              style={{
+                marginBottom: 24
+              }}>
+              <ProFormCheckbox noStyle name="autoLogin">
+                <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+              </ProFormCheckbox>
+              <a
+                style={{
+                  float: 'right'
+                }}>
+                <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
+              </a>
+            </div>
+          </ProForm>
         </div>
-      </ProForm>
+      </div>
     </div>
   )
 }
 
-export default connect(({ user, loading }) => ({
-  userLogin: user,
-  submitting: loading.effects['user/login']
-}))(Login)
+export default Login

+ 72 - 8
src/pages/user/login/index.less

@@ -1,5 +1,75 @@
 @import '~antd/es/style/themes/default.less';
 
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: auto;
+  background: @layout-body-background;
+}
+
+.lang {
+  width: 100%;
+  height: 40px;
+  line-height: 44px;
+  text-align: right;
+  :global(.ant-dropdown-trigger) {
+    margin-right: 24px;
+  }
+}
+
+.content {
+  flex: 1;
+  padding: 32px 0;
+}
+
+@media (min-width: @screen-md-min) {
+  .container {
+    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
+    background-repeat: no-repeat;
+    background-position: center 110px;
+    background-size: 100%;
+  }
+
+  .content {
+    padding: 32px 0 24px;
+  }
+}
+
+.top {
+  text-align: center;
+}
+
+.header {
+  height: 44px;
+  line-height: 44px;
+  a {
+    text-decoration: none;
+  }
+}
+
+.logo {
+  height: 44px;
+  margin-right: 16px;
+  vertical-align: top;
+}
+
+.title {
+  position: relative;
+  top: 2px;
+  color: @heading-color;
+  font-weight: 600;
+  font-size: 33px;
+  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+}
+
+.desc {
+  margin-top: 12px;
+  margin-bottom: 40px;
+  color: @text-color-secondary;
+  font-size: @font-size-base;
+}
+
 .main {
   width: 328px;
   margin: 0 auto;
@@ -11,7 +81,7 @@
   :global {
     .@{ant-prefix}-tabs-nav-list {
       margin: auto;
-      font-size: 16rpx;
+      font-size: 16px;
     }
   }
 
@@ -41,10 +111,4 @@
     color: @primary-color;
     font-size: @font-size-base;
   }
-
-  .tabMargin{
-    :global(.ant-tabs-tab){
-      margin: 0;
-    }
-  }
-}
+}

+ 3 - 3
src/pages/workbench/Dashboard/index.jsx

@@ -11,7 +11,8 @@ import ReminderList from './components/ReminderList'
 import LeaderBoard from './components/LeaderBoard'
 import { cyclicalOp } from './consts'
 
-const Dashboard = ({ dispatch, permData = {}, groupList = [] }) => {
+const Dashboard = ({ dispatch, groupList = [] }) => {
+  const permData = {}
   const [state, setState] = useState({
     visible: false,
     reminderData: [],
@@ -380,7 +381,6 @@ const Dashboard = ({ dispatch, permData = {}, groupList = [] }) => {
   )
 }
 
-export default connect(({ business, global }) => ({
-  permData: global.premData,
+export default connect(({ business }) => ({
   groupList: business.groupList
 }))(Dashboard)

+ 3 - 7
src/services/user.js

@@ -1,23 +1,19 @@
 import request from '@/basic/utils/request'
 
-export function query() {
-  return request('/users')
-}
-
 /**
  * 获取当前用户信息
  */
-export async function apiCurrent() {
+export async function queryCurrentUser() {
   return request.get('/login/staff')
 }
 
 /** 获取用户权限 */
-export async function apiGetRoles() {
+export async function queryRoles() {
   return request.get('/login/roles')
 }
 
 /** 获取数据权限下拉菜单 */
-export async function getDataPerm(params) {
+export async function queryPermData(params) {
   return request.get('/DataPermission/list', {
     params
   })

+ 0 - 19
src/utils/auth/Authorized.js

@@ -1,19 +0,0 @@
-import RenderAuthorize from '@/components/Authorized'
-import { getAuthority } from './authority'
-/* eslint-disable eslint-comments/disable-enable-pair */
-
-/* eslint-disable import/no-mutable-exports */
-
-let Authorized = RenderAuthorize(getAuthority()) // Reload the rights component
-
-const reloadAuthorized = () => {
-  Authorized = RenderAuthorize(getAuthority())
-}
-/**
- * hard code
- * block need it。
- */
-
-window.reloadAuthorized = reloadAuthorized
-export { reloadAuthorized }
-export default Authorized

+ 0 - 16
src/utils/auth/authority.js

@@ -1,16 +0,0 @@
-import { getAuthCache, setAuthCache } from './index'
-import { ROLES_KEY } from '../cache/cacheEnum'
-import { reloadAuthorized } from './Authorized' // use localStorage to store the authority info, which might be sent from server in actual project.
-
-export async function getAuthority() {
-  const authority = getAuthCache(ROLES_KEY)
-  if (typeof authority === 'string') {
-    return [authority]
-  }
-  return authority
-}
-export function setAuthority(authority) {
-  const proAuthority = typeof authority === 'string' ? [authority] : authority
-  setAuthCache(ROLES_KEY, proAuthority)
-  reloadAuthorized()
-}