refactor: reorganize type definitions and improve type safety build: add lodash and @types/lodash as dependencies chore: update tsconfig and vite config for path aliases style: improve code organization and add documentation comments fix: correct type usage in LanguageSwitcher component perf: implement snackbar provider for global notifications test: add new test interfaces and utility functions ci: update pnpm-lock.yaml with new dependencies
188 lines
4.9 KiB
TypeScript
188 lines
4.9 KiB
TypeScript
import { Authorization } from '@/constants/authorization';
|
|
import type { ResponseType } from '@/interfaces/database/base';
|
|
import i18n from '@/locales';
|
|
import axios from 'axios';
|
|
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
|
|
|
import { snackbar, notification } from '@/utils/snackbarInstance';
|
|
|
|
const FAILED_TO_FETCH = 'Failed to fetch';
|
|
|
|
export const RetcodeMessage = {
|
|
200: i18n.t('message.200'),
|
|
201: i18n.t('message.201'),
|
|
202: i18n.t('message.202'),
|
|
204: i18n.t('message.204'),
|
|
400: i18n.t('message.400'),
|
|
401: i18n.t('message.401'),
|
|
403: i18n.t('message.403'),
|
|
404: i18n.t('message.404'),
|
|
406: i18n.t('message.406'),
|
|
410: i18n.t('message.410'),
|
|
413: i18n.t('message.413'),
|
|
422: i18n.t('message.422'),
|
|
500: i18n.t('message.500'),
|
|
502: i18n.t('message.502'),
|
|
503: i18n.t('message.503'),
|
|
504: i18n.t('message.504'),
|
|
};
|
|
|
|
export type ResultCode =
|
|
| 200
|
|
| 201
|
|
| 202
|
|
| 204
|
|
| 400
|
|
| 401
|
|
| 403
|
|
| 404
|
|
| 406
|
|
| 410
|
|
| 413
|
|
| 422
|
|
| 500
|
|
| 502
|
|
| 503
|
|
| 504;
|
|
|
|
// 获取授权token
|
|
const getAuthorization = (): string => {
|
|
return localStorage.getItem('token') || '';
|
|
};
|
|
|
|
// 重定向到登录页
|
|
const redirectToLogin = (): void => {
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/login';
|
|
};
|
|
|
|
// 转换对象键为snake_case
|
|
const convertTheKeysOfTheObjectToSnake = (obj: any): any => {
|
|
if (!obj || typeof obj !== 'object') return obj;
|
|
|
|
if (Array.isArray(obj)) {
|
|
return obj.map(convertTheKeysOfTheObjectToSnake);
|
|
}
|
|
|
|
const result: any = {};
|
|
Object.keys(obj).forEach(key => {
|
|
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
result[snakeKey] = convertTheKeysOfTheObjectToSnake(obj[key]);
|
|
});
|
|
|
|
return result;
|
|
};
|
|
|
|
// 创建axios实例
|
|
const request: AxiosInstance = axios.create({
|
|
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
timeout: 300000,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
// 请求拦截器
|
|
request.interceptors.request.use(
|
|
(config: InternalAxiosRequestConfig) => {
|
|
// 转换数据格式
|
|
if (config.data) {
|
|
config.data = convertTheKeysOfTheObjectToSnake(config.data);
|
|
}
|
|
if (config.params) {
|
|
config.params = convertTheKeysOfTheObjectToSnake(config.params);
|
|
}
|
|
|
|
// 添加授权头
|
|
const token = getAuthorization();
|
|
if (token && !config.headers?.skipToken) {
|
|
config.headers[Authorization] = token;
|
|
}
|
|
|
|
return config;
|
|
},
|
|
(error) => {
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// 响应拦截器
|
|
request.interceptors.response.use(
|
|
(response: AxiosResponse) => {
|
|
const { status } = response;
|
|
|
|
// 处理特定状态码
|
|
if (status === 413 || status === 504) {
|
|
snackbar.error(RetcodeMessage[status as ResultCode]);
|
|
}
|
|
|
|
// 处理blob类型响应
|
|
if (response.config.responseType === 'blob') {
|
|
return response;
|
|
}
|
|
|
|
const data: ResponseType = response.data;
|
|
|
|
// 处理业务错误码
|
|
if (data?.code === 100) {
|
|
snackbar.error(data?.message);
|
|
} else if (data?.code === 401) {
|
|
// notification.error({
|
|
// message: data?.message,
|
|
// description: data?.message,
|
|
// duration: 3,
|
|
// });
|
|
notification.error(data?.message);
|
|
redirectToLogin();
|
|
} else if (data?.code !== 0) {
|
|
// notification.error({
|
|
// message: `${i18n.t('message.hint')} : ${data?.code}`,
|
|
// description: data?.message,
|
|
// duration: 3,
|
|
// });
|
|
notification.error(`${i18n.t('message.hint')} : ${data?.code}`, data?.message);
|
|
}
|
|
|
|
return response;
|
|
},
|
|
(error) => {
|
|
// 处理网络错误
|
|
if (error.message === FAILED_TO_FETCH || !error.response) {
|
|
// notification.error({
|
|
// description: i18n.t('message.networkAnomalyDescription'),
|
|
// message: i18n.t('message.networkAnomaly'),
|
|
// });
|
|
notification.error(i18n.t('message.networkAnomaly'), i18n.t('message.networkAnomalyDescription'));
|
|
} else if (error.response) {
|
|
const { status, statusText } = error.response;
|
|
const errorText = RetcodeMessage[status as ResultCode] || statusText;
|
|
|
|
// notification.error({
|
|
// message: `${i18n.t('message.requestError')} ${status}`,
|
|
// description: errorText,
|
|
// });
|
|
notification.error(`${i18n.t('message.requestError')} ${status}`, errorText);
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export default request;
|
|
|
|
// 便捷方法
|
|
export const get = (url: string, config?: AxiosRequestConfig) => {
|
|
return request.get(url, config);
|
|
};
|
|
|
|
export const post = (url: string, data?: any, config?: AxiosRequestConfig) => {
|
|
return request.post(url, data, config);
|
|
};
|
|
|
|
export const put = (url: string, data?: any, config?: AxiosRequestConfig) => {
|
|
return request.put(url, data, config);
|
|
};
|
|
|
|
export const del = (url: string, config?: AxiosRequestConfig) => {
|
|
return request.delete(url, config);
|
|
}; |