feat(projects): support repeated request errors occur once in a short time. close #368, close #369

This commit is contained in:
Soybean 2024-04-27 02:29:39 +08:00
parent 42f950f819
commit e3bd397248
7 changed files with 90 additions and 12 deletions

View File

@ -287,6 +287,12 @@ const local: App.I18n.Schema = {
superAdminVisible: 'Super Admin Visible', superAdminVisible: 'Super Admin Visible',
adminVisible: 'Admin Visible', adminVisible: 'Admin Visible',
adminOrUserVisible: 'Admin and User Visible' adminOrUserVisible: 'Admin and User Visible'
},
request: {
repeatedErrorOccurOnce: 'Repeated Request Error Occurs Once',
repeatedError: 'Repeated Request Error',
repeatedErrorMsg1: 'Custom Request Error 1',
repeatedErrorMsg2: 'Custom Request Error 2'
} }
}, },
manage: { manage: {

View File

@ -287,6 +287,12 @@ const local: App.I18n.Schema = {
superAdminVisible: '超级管理员可见', superAdminVisible: '超级管理员可见',
adminVisible: '管理员可见', adminVisible: '管理员可见',
adminOrUserVisible: '管理员和用户可见' adminOrUserVisible: '管理员和用户可见'
},
request: {
repeatedErrorOccurOnce: '重复请求错误只出现一次',
repeatedError: '重复请求错误',
repeatedErrorMsg1: '自定义请求错误 1',
repeatedErrorMsg2: '自定义请求错误 2'
} }
}, },
manage: { manage: {

View File

@ -1,20 +1,16 @@
import type { AxiosResponse } from 'axios'; import type { AxiosResponse } from 'axios';
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
import { useAuthStore } from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import { getServiceBaseURL } from '@/utils/service'; import { getServiceBaseURL } from '@/utils/service';
import { $t } from '@/locales'; import { handleRefreshToken, showErrorMsg } from './shared';
import { handleRefreshToken } from './shared'; import type { RequestInstanceState } from './type';
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
interface InstanceState { export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
/** whether the request is refreshing token */
isRefreshingToken: boolean;
}
export const request = createFlatRequest<App.Service.Response, InstanceState>(
{ {
baseURL, baseURL,
headers: { headers: {
@ -35,7 +31,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
isBackendSuccess(response) { isBackendSuccess(response) {
// when the backend response code is "0000"(default), it means the request is success // 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 // 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; return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
}, },
async onBackendFail(response, instance) { async onBackendFail(response, instance) {
const authStore = useAuthStore(); const authStore = useAuthStore();
@ -47,6 +43,8 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
function logoutAndCleanup() { function logoutAndCleanup() {
handleLogout(); handleLogout();
window.removeEventListener('beforeunload', handleLogout); window.removeEventListener('beforeunload', handleLogout);
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
} }
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
@ -58,13 +56,15 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal // 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(',') || []; const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(response.data.code)) { if (modalLogoutCodes.includes(response.data.code) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
// prevent the user from refreshing the page // prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout); window.addEventListener('beforeunload', handleLogout);
window.$dialog?.error({ window.$dialog?.error({
title: 'Error', title: 'Error',
content: response.data.msg, content: response.data.code,
positiveText: $t('common.confirm'), positiveText: $t('common.confirm'),
maskClosable: false, maskClosable: false,
onPositiveClick() { onPositiveClick() {
@ -122,7 +122,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
return; return;
} }
window.$message?.error?.(message); showErrorMsg(request.state, message);
} }
} }
); );

View File

@ -2,6 +2,7 @@ import type { AxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import { fetchRefreshToken } from '../api'; import { fetchRefreshToken } from '../api';
import type { RequestInstanceState } from './type';
/** /**
* refresh token * refresh token
@ -29,3 +30,25 @@ export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
return null; return null;
} }
export function showErrorMsg(state: RequestInstanceState, message: string) {
if (!state.errMsgStack?.length) {
state.errMsgStack = [];
}
const isExist = state.errMsgStack.includes(message);
if (!isExist) {
state.errMsgStack.push(message);
window.$message?.error(message, {
onLeave: () => {
state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);
setTimeout(() => {
state.errMsgStack = [];
}, 5000);
}
});
}
}

View File

@ -0,0 +1,6 @@
export interface RequestInstanceState {
/** whether the request is refreshing token */
isRefreshingToken: boolean;
/** the request error message stack */
errMsgStack: string[];
}

View File

@ -472,6 +472,12 @@ declare namespace App {
adminVisible: string; adminVisible: string;
adminOrUserVisible: string; adminOrUserVisible: string;
}; };
request: {
repeatedErrorOccurOnce: string;
repeatedError: string;
repeatedErrorMsg1: string;
repeatedErrorMsg2: string;
};
}; };
manage: { manage: {
common: { common: {

View File

@ -13,6 +13,25 @@ async function logoutWithModal() {
async function refreshToken() { async function refreshToken() {
await fetchCustomBackendError('9999', $t('request.tokenExpired')); await fetchCustomBackendError('9999', $t('request.tokenExpired'));
} }
async function handleRepeatedMessageError() {
await Promise.all([
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2'))
]);
}
async function handleRepeatedModalError() {
await Promise.all([
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'))
]);
}
</script> </script>
<template> <template>
@ -26,6 +45,18 @@ async function refreshToken() {
<NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper"> <NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton> <NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton>
</NCard> </NCard>
<NCard
:title="$t('page.function.request.repeatedErrorOccurOnce')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NButton @click="handleRepeatedMessageError">{{ $t('page.function.request.repeatedError') }}(Message)</NButton>
<NButton class="ml-12px" @click="handleRepeatedModalError">
{{ $t('page.function.request.repeatedError') }}(Modal)
</NButton>
</NCard>
</NSpace> </NSpace>
</template> </template>