From 41e8bc44f889132e552ebbd9ee274970cf7db679 Mon Sep 17 00:00:00 2001 From: Soybean Date: Sun, 24 Mar 2024 06:23:56 +0800 Subject: [PATCH] feat(projects): add request exception example page --- .env | 12 +++++ src/locales/langs/en-us.ts | 10 ++++ src/locales/langs/zh-cn.ts | 10 ++++ src/router/elegant/imports.ts | 1 + src/router/elegant/routes.ts | 10 ++++ src/router/elegant/transform.ts | 1 + src/service/request/index.ts | 74 ++++++++++++++++++++++------ src/typings/app.d.ts | 9 ++++ src/typings/elegant-router.d.ts | 2 + src/typings/env.d.ts | 30 +++++++++++ src/views/function/request/index.vue | 32 ++++++++++++ 11 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 src/views/function/request/index.vue diff --git a/.env b/.env index 033e004c..76b49edc 100644 --- a/.env +++ b/.env @@ -25,3 +25,15 @@ VITE_HTTP_PROXY=Y # vue-router mode: hash | history | memory VITE_ROUTER_HISTORY_MODE=history + +# success code of backend service, when the code is received, the request is successful +VITE_SERVICE_SUCCESS_CODE=0000 + +# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page +VITE_SERVICE_LOGOUT_CODES=8888,8889 + +# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal +VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778 + +# token expired codes of backend service, when the code is received, it will refresh the token and resend the request +VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998 diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 4e1af859..6ddd19b2 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -33,6 +33,7 @@ const local: App.I18n.Schema = { search: 'Search', switch: 'Switch', tip: 'Tip', + trigger: 'Trigger', update: 'Update', updateSuccess: 'Update Success', userCenter: 'User Center', @@ -41,6 +42,14 @@ const local: App.I18n.Schema = { no: 'No' } }, + request: { + logout: 'Logout user after request failed', + logoutMsg: 'User status is invalid, please log in again', + logoutWithModal: 'Pop up modal after request failed and then log out user', + logoutWithModalMsg: 'User status is invalid, please log in again', + refreshToken: 'The requested token has expired, refresh the token', + tokenExpired: 'The requested token has expired' + }, theme: { themeSchema: { title: 'Theme Schema', @@ -138,6 +147,7 @@ const local: App.I18n.Schema = { 'function_hide-child_one': 'Hide Child', 'function_hide-child_two': 'Two', 'function_hide-child_three': 'Three', + function_request: 'Request', manage: 'System Manage', manage_user: 'User Manage', 'manage_user-detail': 'User Detail', diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index af45fc03..2c35cab9 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -33,6 +33,7 @@ const local: App.I18n.Schema = { search: '搜索', switch: '切换', tip: '提示', + trigger: '触发', update: '更新', updateSuccess: '更新成功', userCenter: '个人中心', @@ -41,6 +42,14 @@ const local: App.I18n.Schema = { no: '否' } }, + request: { + logout: '请求失败后登出用户', + logoutMsg: '用户状态失效,请重新登录', + logoutWithModal: '请求失败后弹出模态框再登出用户', + logoutWithModalMsg: '用户状态失效,请重新登录', + refreshToken: '请求的token已过期,刷新token', + tokenExpired: 'token已过期' + }, theme: { themeSchema: { title: '主题模式', @@ -138,6 +147,7 @@ const local: App.I18n.Schema = { 'function_hide-child_one': '隐藏子菜单', 'function_hide-child_two': '菜单二', 'function_hide-child_three': '菜单三', + function_request: '请求', manage: '系统管理', manage_user: '用户管理', 'manage_user-detail': '用户详情', diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts index 3ae5976b..ee70d386 100644 --- a/src/router/elegant/imports.ts +++ b/src/router/elegant/imports.ts @@ -24,6 +24,7 @@ export const views: Record Promise import("@/views/function/hide-child/three/index.vue"), "function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"), "function_multi-tab": () => import("@/views/function/multi-tab/index.vue"), + function_request: () => import("@/views/function/request/index.vue"), function_tab: () => import("@/views/function/tab/index.vue"), home: () => import("@/views/home/index.vue"), manage_menu: () => import("@/views/manage/menu/index.vue"), diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index 85a47e14..77944477 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -117,6 +117,16 @@ export const generatedRoutes: GeneratedRoute[] = [ activeMenu: 'function_tab' } }, + { + name: 'function_request', + path: '/function/request', + component: 'view.function_request', + meta: { + title: 'function_request', + i18nKey: 'route.function_request', + icon: 'carbon:network-overlay' + } + }, { name: 'function_tab', path: '/function/tab', diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts index 40c1f2ae..2170c1f8 100644 --- a/src/router/elegant/transform.ts +++ b/src/router/elegant/transform.ts @@ -157,6 +157,7 @@ const routeMap: RouteMap = { "function_hide-child_three": "/function/hide-child/three", "function_hide-child_two": "/function/hide-child/two", "function_multi-tab": "/function/multi-tab", + "function_request": "/function/request", "function_tab": "/function/tab", "home": "/home", "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", diff --git a/src/service/request/index.ts b/src/service/request/index.ts index 82752fbc..240707df 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -3,6 +3,7 @@ import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios' import { useAuthStore } from '@/store/modules/auth'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; +import { $t } from '@/locales'; import { handleRefreshToken } from './shared'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; @@ -32,28 +33,55 @@ export const request = createFlatRequest( return config; }, isBackendSuccess(response) { - // when the backend response code is "0000", it means the request is success - // you can change this logic by yourself - return response.data.code === '0000'; + // when the backend response code is "0000"(default), it means the request is success + // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file + return response.data.code === import.meta.env.VITE_SERVICE_SUCCESS_CODE; }, async onBackendFail(response, instance) { - // when the backend response code is not "0000", it means the request is fail - // for example: the token is expired, refresh token and retry request - const authStore = useAuthStore(); - // when the backend response code is "8888", it means logout the system and redirect to login page - // the following code is an example, you can change it by yourself - const logoutCodes: (string | number)[] = ['8888']; - if (logoutCodes.includes(response.data.code)) { + function handleLogout() { authStore.resetStore(); + } + + function logoutAndCleanup() { + handleLogout(); + window.removeEventListener('beforeunload', handleLogout); + } + + // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page + const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; + if (logoutCodes.includes(response.data.code)) { + handleLogout(); return null; } - // when the backend response code is "9999", it means the token is expired, and refresh token - // the api `refreshToken` can not return the code "9999", otherwise it will be a dead loop, can return `logoutCodes` - const refreshTokenCodes: (string | number)[] = ['9999']; - if (refreshTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { + // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal + const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; + if (modalLogoutCodes.includes(response.data.code)) { + // prevent the user from refreshing the page + window.addEventListener('beforeunload', handleLogout); + + window.$dialog?.error({ + title: 'Error', + content: response.data.msg, + positiveText: $t('common.confirm'), + maskClosable: false, + onPositiveClick() { + logoutAndCleanup(); + }, + onClose() { + logoutAndCleanup(); + } + }); + + return null; + } + + // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token + // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` + const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; + if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { request.state.isRefreshingToken = true; const refreshConfig = await handleRefreshToken(response.config); @@ -74,13 +102,27 @@ export const request = createFlatRequest( // when the request is fail, you can show error message let message = error.message; + let backendErrorCode = ''; - // show backend error message + // get backend error message and code if (error.code === BACKEND_ERROR_CODE) { message = error.response?.data?.msg || message; + backendErrorCode = error.response?.data?.code || ''; } - window.$message?.error(message); + // the error message is displayed in the modal + const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; + if (modalLogoutCodes.includes(backendErrorCode)) { + return; + } + + // when the token is expired, refresh token and retry request, so no need to show error message + const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; + if (expiredTokenCodes.includes(backendErrorCode)) { + return; + } + + window.$message?.error?.(message); } } ); diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index 8bd0246e..bb64592a 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -279,6 +279,7 @@ declare namespace App { search: string; switch: string; tip: string; + trigger: string; update: string; updateSuccess: string; userCenter: string; @@ -287,6 +288,14 @@ declare namespace App { no: string; }; }; + request: { + logout: string; + logoutMsg: string; + logoutWithModal: string; + logoutWithModalMsg: string; + refreshToken: string; + tokenExpired: string; + }; theme: { themeSchema: { title: string } & Record; layoutMode: { title: string } & Record; diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts index b6f5ab67..2868add2 100644 --- a/src/typings/elegant-router.d.ts +++ b/src/typings/elegant-router.d.ts @@ -31,6 +31,7 @@ declare module "@elegant-router/types" { "function_hide-child_three": "/function/hide-child/three"; "function_hide-child_two": "/function/hide-child/two"; "function_multi-tab": "/function/multi-tab"; + "function_request": "/function/request"; "function_tab": "/function/tab"; "home": "/home"; "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; @@ -117,6 +118,7 @@ declare module "@elegant-router/types" { | "function_hide-child_three" | "function_hide-child_two" | "function_multi-tab" + | "function_request" | "function_tab" | "home" | "manage_menu" diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts index d1f1d49c..68f42b06 100644 --- a/src/typings/env.d.ts +++ b/src/typings/env.d.ts @@ -27,6 +27,36 @@ declare namespace Env { readonly VITE_ICON_LOCAL_PREFIX: 'local-icon'; /** backend service base url */ readonly VITE_SERVICE_BASE_URL: string; + /** + * success code of backend service + * + * when the code is received, the request is successful + */ + readonly VITE_SERVICE_SUCCESS_CODE: string; + /** + * logout codes of backend service + * + * when the code is received, the user will be logged out and redirected to login page + * + * use "," to separate multiple codes + */ + readonly VITE_SERVICE_LOGOUT_CODES: string; + /** + * modal logout codes of backend service + * + * when the code is received, the user will be logged out by displaying a modal + * + * use "," to separate multiple codes + */ + readonly VITE_SERVICE_MODAL_LOGOUT_CODES: string; + /** + * token expired codes of backend service + * + * when the code is received, it will refresh the token and resend the request + * + * use "," to separate multiple codes + */ + readonly VITE_SERVICE_EXPIRED_TOKEN_CODES: string; /** * other backend service base url * diff --git a/src/views/function/request/index.vue b/src/views/function/request/index.vue new file mode 100644 index 00000000..be388c08 --- /dev/null +++ b/src/views/function/request/index.vue @@ -0,0 +1,32 @@ + + + + +