import axios, { type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' import type { ApiResponse } from './types' /** * 扩展 AxiosInstance 类型,修正返回值类型 * 响应拦截器直接返回 res.result,所以实际返回 T 而非 AxiosResponse */ interface CustomAxiosInstance extends Omit { get(url: string, config?: InternalAxiosRequestConfig): Promise post(url: string, data?: any, config?: InternalAxiosRequestConfig): Promise put(url: string, data?: any, config?: InternalAxiosRequestConfig): Promise delete(url: string, config?: InternalAxiosRequestConfig): Promise patch(url: string, data?: any, config?: InternalAxiosRequestConfig): Promise } // 创建axios实例 const axiosInstance: AxiosInstance = axios.create({ baseURL: '/api', timeout: 30000, headers: { 'Content-Type': 'application/json', }, }) // 请求拦截器 axiosInstance.interceptors.request.use( (config) => { // 从localStorage获取token,统一注入 Authorization 头 // 说明:后端已强制从 JWT 解析用户身份,X-User-ID 头不再被读取,故不再发送 const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } ) // 401 兜底处理:会话过期时统一登出并跳登录页。 // 用 debounce 标记 + 懒加载 store/router,避免并发 401 反复跳转、以及与 auth/router 形成循环依赖 let handlingUnauthorized = false async function handleUnauthorized() { if (handlingUnauthorized) return handlingUnauthorized = true try { const [authModule, routerModule] = await Promise.all([ import('@/stores/auth'), import('@/router'), ]) try { authModule.useAuthStore().logout() } catch (e) { console.warn('handleUnauthorized: logout 失败', e) } const router = routerModule.default if (router.currentRoute.value.path !== '/login') { router.push('/login').catch(() => {}) } } finally { // 留一点时间让路由跳转完成,避免这段时间内的并发 401 又触发一次 setTimeout(() => { handlingUnauthorized = false }, 1500) } } // 响应拦截器 axiosInstance.interceptors.response.use( (response: AxiosResponse) => { const res = response.data // 统一响应格式处理 // 后端所有接口返回HTTP 200,错误通过code字段标识 if (res.code !== 0) { // 兼容部分接口在 200 体里返回 code=401 的情况 if (res.code === 401) { handleUnauthorized() return Promise.reject(new Error(res.message || '登录已过期,请重新登录')) } // 错误处理 console.error('API Error:', res.message) return Promise.reject(new Error(res.message || '请求失败')) } // 返回result字段的数据 return res.result as any }, (error) => { // 鉴权中间件返回 HTTP 401(携带统一响应体):会走到这里,触发登出兜底。 // 原来注释里说"由 401 拦截器兜底",但实际并没有实现,导致会话过期后所有请求静默失败、用户被困在界面里 if (error?.response?.status === 401) { handleUnauthorized() } console.error('Request Error:', error) return Promise.reject(error) } ) // 导出类型正确的实例 const request = axiosInstance as CustomAxiosInstance export default request