import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' import { getAccessToken, setAccessToken, clearAccessToken } from '@/utils/token' const client = axios.create({ baseURL: '/api/v1', timeout: 10000, }) // 防止 401 时并发触发多次刷新 let refreshing = false client.interceptors.request.use((config) => { const token = getAccessToken() if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) client.interceptors.response.use( (res) => res.data, async (err) => { const status = err.response?.status const original = err.config const isAuthEndpoint = !!original?.url?.includes('/auth/operator/') // access token 过期:尝试用 HttpOnly refresh Cookie 静默刷新一次后重试原请求 // __retried 防止刷新后重试仍 401 时再次触发刷新,形成循环 if (status === 401 && !refreshing && !isAuthEndpoint && !(original as any)?.__retried) { refreshing = true try { const resp: any = await client.post('/auth/operator/refresh', {}) if (resp?.success) { const newToken = resp.data.accessToken setAccessToken(newToken) refreshing = false ;(original as any).__retried = true const headers = (original.headers || {}) as Record headers.Authorization = `Bearer ${newToken}` original.headers = headers as any return client(original) } } catch { // 刷新失败:清除并跳登录 } finally { refreshing = false } clearAccessToken() ElMessage.error('登录已过期,请重新登录') router.push('/login') return Promise.reject(err) } // 刷新/登录等鉴权端点自身 401:交由调用方处理文案,不全局弹窗跳转 if (status === 401) { if (!isAuthEndpoint) { clearAccessToken() ElMessage.error('登录已过期,请重新登录') router.push('/login') } } else if (err.response?.data?.message) { ElMessage.error(err.response.data.message) } else { ElMessage.error('请求失败') } return Promise.reject(err) } ) export const get = (url: string, params?: Record, headers?: Record) => client.get(url, { params, headers }) export const post = (url: string, data?: any, headers?: Record) => client.post(url, data, { headers }) export const put = (url: string, data?: any, headers?: Record) => client.put(url, data, { headers }) export const del = (url: string, headers?: Record) => client.delete(url, { headers }) export default client