Files
TERES_web_frontend/src/utils/request.ts
guangfei.zhao a1282de74f feat: add new interfaces, services, and utilities for API integration
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
2025-10-10 15:09:04 +08:00

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);
};