Sfoglia il codice sorgente

refactor: all change refactor to umi4

lanjianrong 4 anni fa
parent
commit
a8cca93f32

+ 8 - 7
README.md

@@ -1,3 +1,11 @@
+## 开发注意:
+1. 尽量使用typescript进行开发,包括使用tsx(定义其组件prop类型)。
+2. 新增路由页面:
+ 1. 要使用PageContainer包裹并将title、breadcrumb置为false(不需要渲染标题以及面包屑),水印配置视具体页面调整(默认配置查看app.tsx中的layout配置)。
+ 2. locales/zh-CH/menu.js要加上对应国际化配置。
+3. 本项目使用@umijs/plugin-dva整合数据流进行全局状态管理, [详见](https://umijs.org/docs/max/dva)。
+4. 开发文档完全按照umi4官方规范进行, [详见](https://umijs.org/docs/guides/prepare)
+
 ## 安装依赖
 
 pnpm install
@@ -29,10 +37,3 @@ git reset --hard dev //并将 master 分支重置为 dev
 ## 强制推送
 
 git push origin master --force //将重置后的 master 分支强制推送更新
-
-## 开发注意:
-1. 尽量使用typescript进行开发,包括使用tsx(定义其组件prop类型)。
-2. 新增路由页面:
- 1. 要使用PageContainer包裹并将title、breadcrumb置为false(不需要渲染标题以及面包屑),水印配置视具体页面调整(默认配置查看app.tsx中的layout配置)。
- 2. locales/zh-CH/menu.js要加上对应国际化配置。
-3. 本项目使用@umijs/plugin-dva整合数据流进行全局状态管理, [详见](https://umijs.org/zh-CN/plugins/plugin-dva)。

+ 12 - 12
config/routes.js

@@ -188,32 +188,32 @@ export default [
         routes: [
           {
             path: '/statistic/product',
-            redirect: '/statistic/product/software-usage'
+            redirect: '/statistic/product/softwareusage'
           },
           {
-            path: 'software-usage',
-            name: 'software-usage',
+            path: 'usage',
+            name: 'usage',
             component: './Statistic/Product/SoftwareUsage',
             access: 'authRouteFilter',
             validatePerm: true
           },
           {
-            path: 'invoice-aggregate',
-            name: 'invoice-aggregate',
+            path: 'aggregate',
+            name: 'aggregate',
             component: './Statistic/Product/InvoiceAggregate',
             access: 'authRouteFilter',
             validatePerm: true
           },
           {
-            path: 'invoice-trends',
-            name: 'invoice-trends',
+            path: 'trends',
+            name: 'trends',
             component: './Statistic/Product/InvoiceTrends',
             access: 'authRouteFilter',
             validatePerm: true
           },
           {
-            path: 'invoice-receivables',
-            name: 'invoice-receivables',
+            path: 'receivables',
+            name: 'receivables',
             component: './Statistic/Product/InvoiceReceivables',
             access: 'authRouteFilter'
           }
@@ -226,11 +226,11 @@ export default [
         routes: [
           {
             path: '/statistic/customer',
-            redirect: '/statistic/customer/customer-additions'
+            redirect: '/statistic/customer/additions'
           },
           {
-            path: 'customer-additions',
-            name: 'customer-additions',
+            path: 'additions',
+            name: 'additions',
             component: './Statistic/Customer/CustomerAdditions',
             access: 'authRouteFilter'
           }

+ 3 - 4
package.json

@@ -33,8 +33,8 @@
     "@ant-design/pro-layout": "6.38.8",
     "@ant-design/pro-table": "2.76.3",
     "@icon-park/react": "^1.3.3",
-    "@umijs/max": "4.0.0-rc.22",
-    "@umijs/plugins": "4.0.0-rc.22",
+    "@umijs/max": "4.0.0",
+    "@umijs/plugins": "4.0.0",
     "ahooks": "^3.4.1",
     "antd": "4.21.0",
     "antd-img-crop": "^3.16.0",
@@ -45,7 +45,6 @@
     "moment": "^2.29.3",
     "omit.js": "^2.0.2",
     "qs": "^6.9.4",
-    "querystring": "^0.2.1",
     "rc-util": "^5.16.1",
     "rc-virtual-list": "^3.4.8",
     "react": "18.1.0",
@@ -78,6 +77,6 @@
     "typescript": "^4.7.3"
   },
   "engines": {
-    "node": ">=10.0.0"
+    "node": ">=14.0.0"
   }
 }

+ 65 - 59
src/app.tsx

@@ -1,6 +1,6 @@
 import { queryCurrentUser, queryPermData } from './services/user'
 import type { ErrorShowType, RequestConfig } from '@umijs/max'
-import { history } from '@umijs/max'
+import { history, createSearchParams } from '@umijs/max'
 import RightContent from '@/components/RightContent'
 import { message, notification } from 'antd'
 import logo from '../public/logo.svg'
@@ -80,7 +80,7 @@ export const layout = ({ initialState, setInitialState }) => {
       if (!initialState?.currentUser?.staffId && location.pathname !== loginPath) {
         history.replace({
           pathname: loginPath,
-          search: new URLSearchParams({ redirect: window.location.pathname }).toString()
+          search: createSearchParams({ redirect: window.location.pathname }).toString()
         })
       } else {
         location.pathname !== loginPath &&
@@ -95,58 +95,68 @@ export const layout = ({ initialState, setInitialState }) => {
 }
 
 let notificationLimit = 0 // 最大1
-const errorHandler = error => {
-  const { data, info } = error
-  if (info) {
-    const { errorMessage = '请求失败', showType, errorCode } = info
-    if (consts.TOKEN_INVALID_CODE.includes(errorCode) && window.location.pathname !== loginPath) {
-      history.replace({
-        pathname: loginPath,
-        search: new URLSearchParams({ redirect: window.location.pathname }).toString()
+const errorHandler = (error: any, opts: any) => {
+  if (opts?.skipErrorHandler) throw error
+  if (error.name === 'BizError') {
+    const errorInfo = error.info
+    if (errorInfo) {
+      if (notificationLimit >= 1) {
+        return
+      }
+      const { errorMessage = '请求失败', showType, errorCode } = info
+      if (consts.TOKEN_INVALID_CODE.includes(errorCode) && window.location.pathname !== loginPath) {
+        history.replace({
+          pathname: loginPath,
+          search: createSearchParams({ redirect: window.location.pathname }).toString()
+        })
+      }
+      switch (showType) {
+        case ErrorShowType.ERROR_MESSAGE:
+          message.error(errorMessage)
+          break
+        case ErrorShowType.WARN_MESSAGE:
+          message.warn(errorMessage)
+          break
+        case ErrorShowType.NOTIFICATION:
+          if (!notificationLimit) {
+            const title = consts.TOKEN_INVALID_CODE.includes(errorCode) ? '用户信息过期' : '请求失败'
+            notification.error({
+              message: title,
+              description: errorMessage
+            })
+            notificationLimit++
+          }
+          break
+        default:
+          break
+      }
+    } else if (error.response) {
+      // Axios 的错误
+      // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+      notification.error({
+        description: `状态码为${error.response.status}, 请联系管理员进行处理`,
+        message: '请求异常'
+      })
+    } else {
+      // 请求已经成功发起,但没有收到响应
+      // 或请求根本没有发送出去
+      notification.error({
+        description: '您的网络发生异常,无法连接服务器',
+        message: '网络异常'
       })
     }
-    switch (showType) {
-      case ErrorShowType.ERROR_MESSAGE:
-        message.error(errorMessage)
-        break
-      case ErrorShowType.WARN_MESSAGE:
-        message.warn(errorMessage)
-        break
-      case ErrorShowType.NOTIFICATION:
-        if (!notificationLimit) {
-          const title = consts.TOKEN_INVALID_CODE.includes(errorCode) ? '用户信息过期' : '请求失败'
-          notification.error({
-            message: title,
-            description: errorMessage
-          })
-          notificationLimit++
-        }
-        break
-      default:
-        break
-    }
-    return Promise.reject(data)
-  } else {
-    notification.error({
-      description: '您的网络发生异常,无法连接服务器',
-      message: '网络异常'
-    })
   }
-  return Promise.reject(error)
 }
-
-const authHeaderInterceptor = (url, options) => {
+const authHeaderInterceptor = (options: any) => {
   const token = getToken()
   if (token) {
     // 在白名单里的请求放过
-    if (consts.TOKEN_WHITE_LIST.includes(url)) {
-      return { url, options }
+    if (consts.TOKEN_WHITE_LIST.includes(options.url)) {
+      return options
     }
-    // eslint-disable-next-line no-param-reassign
     options.headers[consts.TOKEN_HEADER] = `bearer ${token}`
-    return { url, options }
   }
-  return { url, options }
+  return options
 }
 
 export const request: RequestConfig = {
@@ -164,22 +174,18 @@ export const request: RequestConfig = {
     errorHandler
   },
   baseURL: consts.PREFIX_URL,
-  // 默认错误处理
-  credentials: 'include', // 默认请求是否带上cookie
-  cache: 'no-cache',
-  errorConfig: {
-    adaptor: resData => {
-      if (notificationLimit !== 0 && resData.code === consts.RET_CODE.SUCCESS) {
+  requestInterceptors: [authHeaderInterceptor],
+  responseInterceptors: [
+    response => {
+      // 处理notificationLimit,请求成功应重置
+      if (
+        notificationLimit >= 1 &&
+        response.status === 200 &&
+        response.data?.code === consts.RET_CODE.SUCCESS
+      ) {
         notificationLimit = 0
       }
-      return {
-        success: resData.code === consts.RET_CODE.SUCCESS,
-        errorMessage: resData.msg,
-        errorCode: resData.code,
-        showType: resData.showType
-      }
+      return response
     }
-  },
-  requestInterceptors: [authHeaderInterceptor],
-  responseInterceptors: []
+  ]
 }

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

@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
 import { InfoCircleFilled, LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'
 import { Avatar, Menu, Spin, Modal } from 'antd'
 import { createSearchParams, history, useModel } from '@umijs/max'
-import { stringify } from 'querystring'
 import HeaderDropdown from '../HeaderDropdown'
 import styles from './index.less'
 import { clearAuthCache, removeToken } from '@/utils/auth'

+ 5 - 5
src/locales/zh-CN/menu.js

@@ -44,10 +44,10 @@ export default {
   'menu.settings': '个人中心',
   'menu.statistic': '统计',
   'menu.statistic.product': '产品销售',
-  'menu.statistic.product.software-usage': '软件锁用量',
-  'menu.statistic.product.invoice-aggregate': '发票收款入账汇总',
-  'menu.statistic.product.invoice-trends': '发票收款入账趋势',
-  'menu.statistic.product.invoice-receivables': '应收款统计',
+  'menu.statistic.product.usage': '软件锁用量',
+  'menu.statistic.product.aggregate': '发票收款入账汇总',
+  'menu.statistic.product.trends': '发票收款入账趋势',
+  'menu.statistic.product.receivables': '应收款统计',
   'menu.statistic.customer': '客户数据',
-  'menu.statistic.customer.customer-additions': '客户新增'
+  'menu.statistic.customer.additions': '客户新增'
 }

+ 13 - 12
src/pages/User/Login/index.tsx

@@ -42,18 +42,17 @@ const Login = () => {
   }
 
   /** 此方法会跳转到 redirect 参数所在的位置 */
-  const goto = username => {
-    if (!history) return
+  const goto = userInfo => {
+    if (!history || !userInfo) return
     const searchParams = createSearchParams(window.location.search)
     const redirect = searchParams.get('redirect')
-    startTransition(() => {
-      // 清除所有notification
-      notification.destroy()
-      notification.success({
-        message: intl.formatMessage({ id: 'pages.login.success.title' }),
-        description: redirect && `${intl.formatMessage({ id: 'pages.login.success.desc' })}: ${username}`,
-        duration: 3
-      })
+    // 清除所有notification
+    notification.destroy()
+    notification.success({
+      message: intl.formatMessage({ id: 'pages.login.success.title' }),
+      description:
+        redirect && `${intl.formatMessage({ id: 'pages.login.success.desc' })}: ${userInfo.username}`,
+      duration: 3
     })
     history.replace(redirect || '/')
   }
@@ -66,9 +65,11 @@ const Login = () => {
       setUserLoginState({ ...userLoginState, submitting: false })
     } else if (code === consts.RET_CODE.SUCCESS) {
       setAuthCache(TOKEN_KEY, token, DEFAULT_CACHE_TIME)
-      const userinfo = await fetchUserInfo()
+      const user = await fetchUserInfo()
       /** 此方法会跳转到 redirect 参数所在的位置 */
-      return goto(userinfo?.currentUser?.username)
+      startTransition(() => {
+        goto(user?.currentUser)
+      })
     } else {
       // 如果失败去设置用户错误信息
       setUserLoginState({ ...userLoginState, status: 'error', submitting: false })