diff --git a/src/utils/request.ts b/src/utils/request.ts index 03e7d49..98375b3 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,15 +1,17 @@ -import axios, { AxiosInstance } from 'axios'; +import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { ElMessage, ElMessageBox } from 'element-plus'; import { Session } from '/@/utils/storage'; import qs from 'qs'; +// 标记是否正在处理 token 过期,避免重复弹窗 +let isHandlingTokenExpired = false; + // 配置新建第一个 axios 实例(原来的主服务) const service: AxiosInstance = axios.create({ - baseURL: import.meta.env.VITE_API_URL, // 从环境变量获取基础URL http://192.168.3.49:8808/ - timeout: 50000, // 50秒超时 - headers: { 'Content-Type': 'application/json' }, // 默认JSON格式 + baseURL: import.meta.env.VITE_API_URL, + timeout: 50000, + headers: { 'Content-Type': 'application/json' }, paramsSerializer: { - // 参数序列化配置 serialize(params) { return qs.stringify(params, { allowDots: true, arrayFormat: 'brackets' }); }, @@ -18,86 +20,164 @@ const service: AxiosInstance = axios.create({ // 配置新建第二个 axios 实例(新功能服务) const newService: AxiosInstance = axios.create({ - baseURL: 'http://192.168.3.95:8000/', // 新后端地址 - // baseURL: 'http://192.168.3.49:8000/', // 后端地址 - timeout: 50000, // 50秒超时 - headers: { 'Content-Type': 'application/json' }, // 默认JSON格式 + baseURL: 'http://192.168.3.95:8000/', + timeout: 50000, + headers: { 'Content-Type': 'application/json' }, paramsSerializer: { - // 参数序列化配置 serialize(params) { return qs.stringify(params, { allowDots: true, arrayFormat: 'brackets' }); }, }, }); -// 通用的请求拦截器函数 -const requestInterceptor = (config: any) => { - // 在发送请求之前做些什么 token - if (Session.get('token')) { - config.headers!['Authorization'] = `Bearer ${Session.get('token')}`; +// token 过期处理函数 +const handleTokenExpired = () => { + if (isHandlingTokenExpired) return; + + isHandlingTokenExpired = true; + + ElMessageBox.alert('登录状态已过期,请重新登录', '提示', { + confirmButtonText: '确定', + showClose: false, + closeOnClickModal: false, + closeOnPressEscape: false, + beforeClose: (action, instance, done) => { + if (action === 'confirm') { + done(); + performLogout(); + } + }, + }) + .then(() => { + performLogout(); + }) + .catch(() => { + performLogout(); + }); +}; + +// 执行退出登录操作 +const performLogout = () => { + Session.clear(); + localStorage.clear(); + isHandlingTokenExpired = false; + // 跳转到登录页,确保完全刷新 + setTimeout(() => { + window.location.href = '/'; + }, 500); +}; + +// 请求拦截器 +const requestInterceptor = (config: InternalAxiosRequestConfig) => { + // 检查 token 是否有效 + const token = Session.get('token'); + if (token) { + // 可以在这里添加 token 有效性检查(如果需要) + config.headers!['Authorization'] = `Bearer ${token}`; } return config; }; -// 通用的请求错误处理函数 const requestErrorHandler = (error: any) => { - // 对请求错误做些什么 return Promise.reject(error); }; -// 通用的响应拦截器函数 -const responseInterceptor = (response: any) => { - // 对响应数据做点什么 - const res = response.data; - const code = response.data.code; - const message = response.data.message; - // 第65行附近添加:如果是文件流响应,直接返回整个response +// 响应拦截器 +const responseInterceptor = (response: AxiosResponse) => { + // 文件流响应直接返回 if ( response.config.responseType === 'blob' || response.headers['content-type']?.includes('application/zip') || response.headers['content-type']?.includes('application/octet-stream') ) { - return response; // 直接返回原始响应,不进行JSON解析 + return response; } - if (code === 401 || message === 'token is invalid') { - // 401未授权:token过期,跳转登录页 - ElMessageBox.alert('登录状态已过期,请重新登录', '提示', { confirmButtonText: '确定' }) - .then(() => { - Session.clear(); // 清除浏览器全部临时缓存 - window.location.href = '/'; // 去登录页 - }) - .catch(() => {}); - } else if (code !== 0) { - // 业务逻辑错误:显示错误消息 - ElMessage.error(res.message); - return Promise.reject(new Error(res.message)); - } else { - return res; + + const res = response.data; + const httpStatus = response.status; + const code = res?.code; + const message = res?.message; + + // 检查 token 相关错误 + if ( + httpStatus === 401 || + code === 401 || + message?.includes('token') || + message === 'token is invalid' || + message === 'token 解析失败' || + message?.includes('decrypt error') + ) { + handleTokenExpired(); + return Promise.reject(new Error('登录状态已过期')); } + + // 业务逻辑错误处理 + if (code !== undefined && code !== 0 && code !== 200) { + const errorMsg = message || `请求失败(${code})`; + ElMessage.error(errorMsg); + return Promise.reject(new Error(errorMsg)); + } + + return res; }; -// 通用的响应错误处理函数 +// 响应错误拦截器 const responseErrorHandler = (error: any) => { - // 对响应错误做点什么 - if (error.message.indexOf('timeout') != -1) { - ElMessage.error('网络超时'); - } else if (error.message == 'Network Error') { - ElMessage.error('网络连接错误'); - } else { - if (error.response?.data) ElMessage.error(error.response.statusText); - else ElMessage.error('接口路径找不到'); + console.error('API请求错误:', error); + + if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) { + ElMessage.error('请求超时,请检查网络连接'); + return Promise.reject(new Error('请求超时')); } + + if (!error.response) { + if (error.message === 'Network Error') { + ElMessage.error('网络连接错误,请检查网络设置'); + } else { + ElMessage.error('网络异常,请检查连接'); + } + return Promise.reject(error); + } + + const httpStatus = error.response.status; + const message = error.response.data?.message || error.response.statusText; + + // 处理 HTTP 错误状态 + switch (httpStatus) { + case 401: + handleTokenExpired(); + break; + case 403: + ElMessage.error('没有权限访问该资源'); + break; + case 404: + ElMessage.error('请求的资源不存在'); + break; + case 500: + ElMessage.error('服务器内部错误'); + break; + case 502: + ElMessage.error('网关错误'); + break; + case 503: + ElMessage.error('服务不可用'); + break; + default: + if (httpStatus >= 400) { + ElMessage.error(message || `请求失败(${httpStatus})`); + } + } + return Promise.reject(error); }; -// 为第一个实例添加拦截器 +// 为实例添加拦截器 service.interceptors.request.use(requestInterceptor, requestErrorHandler); service.interceptors.response.use(responseInterceptor, responseErrorHandler); -// 为第二个实例添加拦截器 newService.interceptors.request.use(requestInterceptor, requestErrorHandler); newService.interceptors.response.use(responseInterceptor, responseErrorHandler); -// 导出两个 axios 实例 -export default service; // 原来的主服务 -export { newService }; // 新功能服务 +// 导出 +export default service; +export { newService };