feat(projects): add request exception example page
This commit is contained in:
parent
11a6a3bd80
commit
41e8bc44f8
12
.env
12
.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
|
||||
|
@ -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',
|
||||
|
@ -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': '用户详情',
|
||||
|
@ -24,6 +24,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
"function_hide-child_three": () => 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"),
|
||||
|
@ -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',
|
||||
|
@ -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)?",
|
||||
|
@ -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<App.Service.Response, InstanceState>(
|
||||
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<App.Service.Response, InstanceState>(
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
9
src/typings/app.d.ts
vendored
9
src/typings/app.d.ts
vendored
@ -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<UnionKey.ThemeScheme, string>;
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
|
2
src/typings/elegant-router.d.ts
vendored
2
src/typings/elegant-router.d.ts
vendored
@ -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"
|
||||
|
30
src/typings/env.d.ts
vendored
30
src/typings/env.d.ts
vendored
@ -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
|
||||
*
|
||||
|
32
src/views/function/request/index.vue
Normal file
32
src/views/function/request/index.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales';
|
||||
import { fetchCustomBackendError } from '@/service/api';
|
||||
|
||||
async function logout() {
|
||||
await fetchCustomBackendError('8888', $t('request.logoutMsg'));
|
||||
}
|
||||
|
||||
async function logoutWithModal() {
|
||||
await fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'));
|
||||
}
|
||||
|
||||
async function refreshToken() {
|
||||
await fetchCustomBackendError('9999', $t('request.tokenExpired'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<NCard :title="$t('request.logout')" :bordered="false" size="small" segmented class="card-wrapper">
|
||||
<NButton @click="logout">{{ $t('common.trigger') }}</NButton>
|
||||
</NCard>
|
||||
<NCard :title="$t('request.logoutWithModal')" :bordered="false" size="small" segmented class="card-wrapper">
|
||||
<NButton @click="logoutWithModal">{{ $t('common.trigger') }}</NButton>
|
||||
</NCard>
|
||||
<NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper">
|
||||
<NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user