Просмотр исходного кода

feat: 权限判断逻辑更改

lanjianrong 5 лет назад
Родитель
Сommit
22020e1e36

+ 104 - 97
config/routes.js

@@ -1,7 +1,7 @@
 export default [
   {
     path: '/',
-    component: '../layouts/SecurityLayout',
+    component: '../layouts/BlankLayout',
     routes: [
       {
         path: '/user',
@@ -16,134 +16,141 @@ export default [
       },
       {
         path: '/',
-        component: '../layouts/BasicLayout',
-        authority: ['admin', 'clientAccess'],
+        component: '../layouts/SecurityLayout',
         routes: [
           {
             path: '/',
-            redirect: '/workbench/dashboard'
-          },
-          {
-            path: '/workbench',
-            name: 'workbench',
-            icon: 'icon-chess',
+            component: '../layouts/BasicLayout',
+            authority: ['admin', 'client_access'],
             routes: [
               {
-                path: '/workbench',
+                path: '/',
                 redirect: '/workbench/dashboard'
               },
               {
-                path: '/workbench/dashboard',
+                path: '/workbench',
                 name: 'workbench',
-                icon: 'icon-clipboardcheck',
-                component: './workbench/Dashboard'
-              }
-            ]
-          },
-          {
-            path: '/customer',
-            name: 'customer',
-            icon: 'icon-address-book-o',
-            routes: [
-              {
-                path: '/customer',
-                redirect: '/customer/contact'
-              },
-              {
-                path: '/customer/contact',
-                name: 'contact',
-                icon: 'icon-address-book',
-                component: './customer/Contact',
-                authority: ['admin', 'clientAccess']
-              },
-              {
-                path: '/customer/company',
-                name: 'company',
-                icon: 'icon-building',
-                component: './customer/Company',
-                authority: ['admin', 'companyAccess']
-              },
-              {
-                path: '/customer/finance',
-                name: 'finance',
-                icon: 'icon-usd-circle',
-                component: './customer/Finance'
-              },
-              {
-                path: '/customer/test',
-                name: 'test',
-                icon: 'icon-magic',
-                component: './customer/Test'
-              }
-            ]
-          },
-          {
-            path: '/product',
-            name: 'product',
-            icon: 'icon-box',
-            routes: [
-              {
-                path: '/product',
-                redirect: '/product/lock'
+                icon: 'icon-chess',
+                routes: [
+                  {
+                    path: '/workbench',
+                    redirect: '/workbench/dashboard'
+                  },
+                  {
+                    path: '/workbench/dashboard',
+                    name: 'workbench',
+                    icon: 'icon-clipboardcheck',
+                    component: './workbench/Dashboard'
+                  }
+                ]
               },
               {
-                path: '/product/lock',
-                name: 'lock',
-                icon: 'icon-magic',
+                path: '/customer',
+                name: 'customer',
+                icon: 'icon-address-book-o',
                 routes: [
                   {
-                    path: '/product/lock',
-                    redirect: '/product/lock/lockstore'
+                    path: '/customer',
+                    redirect: '/customer/contact',
+                    authority: ['admin', 'client_access']
+                  },
+                  {
+                    path: '/customer/contact',
+                    name: 'contact',
+                    icon: 'icon-address-book',
+                    component: './customer/Contact',
+                    authority: ['admin', 'client_access']
                   },
                   {
-                    path: '/product/lock/lockstore',
-                    name: 'lockstore',
-                    component: './product/lock/Lockstore'
+                    path: '/customer/company',
+                    name: 'company',
+                    icon: 'icon-building',
+                    component: './customer/Company',
+                    authority: ['admin', 'company_access']
                   },
                   {
-                    path: '/product/lock/lockcount',
-                    name: 'lockcount',
-                    component: './product/lock/Lockcount'
+                    path: '/customer/finance',
+                    name: 'finance',
+                    icon: 'icon-usd-circle',
+                    component: './customer/Finance'
+                  },
+                  {
+                    path: '/customer/test',
+                    name: 'test',
+                    icon: 'icon-magic',
+                    component: './customer/Test'
                   }
                 ]
               },
               {
-                path: '/product/cloud',
-                name: 'cloud',
-                icon: 'icon-cloud',
+                path: '/product',
+                name: 'product',
+                icon: 'icon-box',
                 routes: [
                   {
-                    path: '/product/cloud',
-                    redirect: '/product/cloud/building'
+                    path: '/product',
+                    redirect: '/product/lock'
                   },
                   {
-                    path: '/product/cloud/building',
-                    name: 'build',
-                    component: './product/cloud/Building'
+                    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/curing',
-                    name: 'curing',
-                    component: './product/cloud/Curing'
+                    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'
               },
               {
-                path: '/staff/employee',
-                name: 'employee',
+                path: '/staff',
+                name: 'staff',
                 icon: 'icon-users',
-                component: './staff/Employee'
+                routes: [
+                  {
+                    path: '/staff',
+                    redirect: '/staff/employee'
+                  },
+                  {
+                    path: '/staff/employee',
+                    name: 'employee',
+                    icon: 'icon-users',
+                    component: './staff/Employee'
+                  }
+                ]
               }
             ]
           }

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

@@ -19,6 +19,14 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
   } // 数组处理
 
   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

+ 5 - 1
src/components/Authorized/renderAuthorize.js

@@ -12,7 +12,11 @@ const renderAuthorize = Authorized => 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)

+ 65 - 63
src/components/GlobalHeader/AvatarDropdown.jsx

@@ -1,85 +1,87 @@
-import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'
-import { Avatar, Menu, Spin } from 'antd'
+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'
 
-class AvatarDropdown extends React.Component {
-  onMenuClick = event => {
+const AvatarDropdown = ({
+  dispatch,
+  currentUser = {
+    avatar: '',
+    name: ''
+  },
+  menu
+}) => {
+  const onMenuClick = event => {
     const { key } = event
 
     if (key === 'logout') {
-      const { dispatch } = this.props
-
-      if (dispatch) {
-        dispatch({
-          type: 'login/logout'
-        })
-      }
-
+      Modal.confirm({
+        icon: <InfoCircleFilled />,
+        title: '温馨提示',
+        content: '是否确认退出系统?',
+        onOk: () => {
+          if (dispatch) {
+            dispatch({
+              type: 'user/logout'
+            })
+          }
+        }
+      })
       return
     }
 
     history.push(`/account/${key}`)
   }
 
-  render() {
-    const {
-      currentUser = {
-        avatar: '',
-        name: ''
-      },
-      menu
-    } = this.props
-    const menuHeaderDropdown = (
-      <Menu className={styles.menu} selectedKeys={[]} onClick={this.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 />
-          退出登录
+  const menuHeaderDropdown = (
+    <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
+      {menu && (
+        <Menu.Item key="center">
+          <UserOutlined />
+          个人中心
         </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>
-    ) : (
+      )}
+      {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}`}>
-        <Spin
+        <Avatar
           size="small"
-          style={{
-            marginLeft: 8,
-            marginRight: 8
-          }}
+          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 }) => ({

+ 0 - 71
src/models/login.js

@@ -1,71 +0,0 @@
-import { stringify } from 'querystring'
-import { history } from 'umi'
-import { apiLogin } from '@/services/login'
-// import { setAuthority } from '@/utils/authority'
-import { getPageQuery } from '@/utils/utils'
-import { message } from 'antd'
-import consts from '@/consts'
-import { removeTokenId, saveTokenId } from '@/basic/utils/common'
-
-const Model = {
-  namespace: 'login',
-  state: {
-    status: undefined,
-    userInfo: {}
-  },
-  effects: {
-    *login({ payload }, { call, put }) {
-      // 获取登录接口数据
-      const response = yield call(apiLogin, payload)
-      yield put({
-        type: 'changeLoginStatus',
-        payload: response?.code
-      }) // Login successfully
-
-      if (response && response.code === consts.RET_CODE.SUCCESS) {
-        saveTokenId(response.data.token)
-        yield put({ type: 'user/fetchCurrent' })
-        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) {
-        removeTokenId()
-        history.replace({
-          pathname: '/user/login',
-          search: stringify({
-            redirect: window.location.href
-          })
-        })
-      }
-    }
-  },
-  reducers: {
-    changeLoginStatus(state, { payload }) {
-      return { ...state, status: payload.code }
-    }
-  }
-}
-export default Model

+ 67 - 25
src/models/user.js

@@ -1,23 +1,67 @@
-import { getTokenid } from '@/basic/utils/common'
+import { getTokenid, removeTokenId, saveTokenId } from '@/basic/utils/common'
 import consts from '@/consts'
-import { apiCurrent } from '@/services/user'
+import { apiLogin } from '@/services/login'
+import { apiCurrent, apiGetRoles } from '@/services/user'
+import { setAuthority } from '@/utils/authority'
+import { getPageQuery } from '@/utils/utils'
+import { message } from 'antd'
+import { stringify } from 'qs'
 import { history } from 'umi'
 
 const UserModel = {
   namespace: 'user',
   state: {
-    currentUser: {}
+    currentUser: {},
+    loginStatus: undefined
   },
   effects: {
-    // *fetch(_, { call, put }) {
-    //   const response = yield call(queryUsers)
-    //   yield put({
-    //     type: 'save',
-    //     payload: response
-    //   })
-    // },
+    *login({ payload }, { call, put }) {
+      // 获取登录接口数据
+      const response = yield call(apiLogin, payload)
+      saveTokenId(response.data.token)
+      const { data: currentAuthority = [] } = yield call(apiGetRoles)
+      yield put({
+        type: 'changeLoginStatus',
+        payload: { status: response?.code, currentAuthority }
+      }) // Login successfully
 
-    *fetchCurrent(_, { call, put }) {
+      if (response && response.code === consts.RET_CODE.SUCCESS) {
+        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) {
+        removeTokenId()
+        history.replace({
+          pathname: '/user/login',
+          search: stringify({
+            redirect: window.location.href
+          })
+        })
+      }
+      // window.location.reload()
+    },
+    *fetchCurrent(_, { call, put, select }) {
       if (window.location.pathname === '/user/login') {
         return
       }
@@ -32,6 +76,15 @@ const UserModel = {
           type: 'saveCurrentUser',
           payload: response.data
         })
+        const { loginStatus } = yield select(state => state.user)
+        console.log('loginStatus', loginStatus)
+        if (!loginStatus) {
+          const { data: currentAuthority = [] } = yield call(apiGetRoles)
+          yield put({
+            type: 'changeLoginStatus',
+            payload: { status: consts.RET_CODE.SUCCESS, currentAuthority }
+          })
+        }
       }
     }
   },
@@ -40,20 +93,9 @@ const UserModel = {
       return { ...state, currentUser: action.payload || {} }
     },
 
-    changeNotifyCount(
-      state = {
-        currentUser: {}
-      },
-      action
-    ) {
-      return {
-        ...state,
-        currentUser: {
-          ...state.currentUser,
-          notifyCount: action.payload.totalCount,
-          unreadCount: action.payload.unreadCount
-        }
-      }
+    changeLoginStatus(state, { payload }) {
+      setAuthority(payload.currentAuthority)
+      return { ...state, loginStatus: payload.status }
     }
   }
 }

+ 8 - 106
src/pages/user/login/index.jsx

@@ -20,14 +20,14 @@ const LoginMessage = ({ content }) => (
 
 const Login = props => {
   const { userLogin = {}, submitting } = props
-  const { status } = userLogin
+  const { loginStatus: status } = userLogin
   const [type, setType] = useState('account')
   const intl = useIntl()
 
   const handleSubmit = values => {
     const { dispatch } = props
     dispatch({
-      type: 'login/login',
+      type: 'user/login',
       payload: { ...values, type }
     })
     // dispatch({
@@ -39,9 +39,7 @@ const Login = props => {
     <div className={styles.main}>
       <ProForm
         initialValues={{
-          autoLogin: true,
-          username: '蔡频',
-          password: '123456'
+          autoLogin: true
         }}
         submitter={{
           render: (_, dom) => dom.pop(),
@@ -90,10 +88,7 @@ const Login = props => {
                 size: 'large',
                 prefix: <UserOutlined className={styles.prefixIcon} />
               }}
-              placeholder={intl.formatMessage({
-                id: 'pages.login.username.placeholder',
-                defaultMessage: '用户名: admin or user'
-              })}
+              placeholder={'cld账号'}
               rules={[
                 {
                   required: true,
@@ -112,10 +107,7 @@ const Login = props => {
                 size: 'large',
                 prefix: <LockTwoTone className={styles.prefixIcon} />
               }}
-              placeholder={intl.formatMessage({
-                id: 'pages.login.password.placeholder',
-                defaultMessage: '密码: ant.design'
-              })}
+              placeholder={'cld密码'}
               className="pi-bg-gray"
               rules={[
                 {
@@ -131,90 +123,6 @@ const Login = props => {
             />
           </>
         )}
-
-        {/* {status === 'error' && loginType === 'mobile' && !submitting && (
-          <LoginMessage content="验证码错误" />
-        )}
-        {type === 'mobile' && (
-          <>
-            <ProFormText
-              fieldProps={{
-                size: 'large',
-                prefix: <MobileTwoTone className={styles.prefixIcon} />
-              }}
-              name="mobile"
-              placeholder={intl.formatMessage({
-                id: 'pages.login.phoneNumber.placeholder',
-                defaultMessage: '手机号'
-              })}
-              rules={[
-                {
-                  required: true,
-                  message: (
-                    <FormattedMessage
-                      id="pages.login.phoneNumber.required"
-                      defaultMessage="请输入手机号!"
-                    />
-                  )
-                },
-                {
-                  pattern: /^1\d{10}$/,
-                  message: (
-                    <FormattedMessage
-                      id="pages.login.phoneNumber.invalid"
-                      defaultMessage="手机号格式错误!"
-                    />
-                  )
-                }
-              ]}
-            />
-            <ProFormCaptcha
-              fieldProps={{
-                size: 'large',
-                prefix: <MailTwoTone className={styles.prefixIcon} />
-              }}
-              captchaProps={{
-                size: 'large'
-              }}
-              placeholder={intl.formatMessage({
-                id: 'pages.login.captcha.placeholder',
-                defaultMessage: '请输入验证码'
-              })}
-              captchaTextRender={(timing, count) =>
-                timing
-                  ? `${count} ${intl.formatMessage({
-                      id: 'pages.getCaptchaSecondText',
-                      defaultMessage: '获取验证码'
-                    })}`
-                  : intl.formatMessage({
-                      id: 'pages.login.phoneLogin.getVerificationCode',
-                      defaultMessage: '获取验证码'
-                    })
-              }
-              name="captcha"
-              rules={[
-                {
-                  required: true,
-                  message: (
-                    <FormattedMessage
-                      id="pages.login.captcha.required"
-                      defaultMessage="请输入验证码!"
-                    />
-                  )
-                }
-              ]}
-              onGetCaptcha={async (mobile) => {
-                const result = await getFakeCaptcha(mobile)
-
-                if (result === false) {
-                  return
-                }
-
-                message.success('获取验证码成功!验证码为:1234')
-              }}
-            />
-          </>
-        )} */}
         <div
           style={{
             marginBottom: 24
@@ -230,17 +138,11 @@ const Login = props => {
           </a>
         </div>
       </ProForm>
-      {/* <Space className={styles.other}>
-        <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
-        <AlipayCircleOutlined className={styles.icon} />
-        <TaobaoCircleOutlined className={styles.icon} />
-        <WeiboCircleOutlined className={styles.icon} />
-      </Space> */}
     </div>
   )
 }
 
-export default connect(({ login, loading }) => ({
-  userLogin: login,
-  submitting: loading.effects['login/login']
+export default connect(({ user, loading }) => ({
+  userLogin: user,
+  submitting: loading.effects['user/login']
 }))(Login)

+ 4 - 4
src/services/user.js

@@ -8,10 +8,10 @@ export function query() {
  * 获取当前用户信息
  */
 export async function apiCurrent() {
-  const data = await request.get('/login/staff')
-  return data
+  return request.get('/login/staff')
 }
 
-export function queryNotices() {
-  return request('/notices')
+/** 获取用户权限 */
+export async function apiGetRoles() {
+  return request.get('/login/roles')
 }

+ 3 - 27
src/utils/authority.js

@@ -1,39 +1,15 @@
 import { reloadAuthorized } from './Authorized' // use localStorage to store the authority info, which might be sent from server in actual project.
-import { getDvaApp } from 'umi'
-
-export function getAuthority() {
-  // eslint-disable-next-line react-hooks/rules-of-hooks
-  const dva = getDvaApp()
-  // eslint-disable-next-line no-underscore-dangle
-  const currentUser = dva._store.getState()?.user?.currentUser
-  const roles = currentUser?.roles
-  const authority = []
-  if (currentUser.isAdmin) {
-    authority.push('admin')
-    return authority
-  }
-
-  // eslint-disable-next-line no-restricted-syntax
-  for (const key in roles) {
-    if (Object.hasOwnProperty.call(roles, key)) {
-      const item = roles[key]
-      if (item && Array.isArray(item)) {
-        item.forEach(i => {
-          authority.push(`${key + i.replace(i[0], i[0].toUpperCase())}`)
-        })
-      }
-    }
-  }
 
+export async function getAuthority() {
+  const authority = JSON.parse(localStorage.getItem('authority')) || []
   if (typeof authority === 'string') {
     return [authority]
   }
-
   return authority
 }
 export function setAuthority(authority) {
   const proAuthority = typeof authority === 'string' ? [authority] : authority
-  localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)) // auto reload
+  localStorage.setItem('authority', JSON.stringify(proAuthority)) // auto reload
 
   reloadAuthorized()
 }