import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios"; import { ElMessage } from "element-plus"; import router from "../router"; import { getToken } from "../utils/auth"; import type { ApiError } from "../types/api"; import { useStudyStore } from "../store/study"; import { TEXT } from "../locales"; import { extendAccessToken, forceLogout, lockSession, markNetworkActive } from "../session/sessionManager"; import { useSessionStore } from "../store/session"; const instance: AxiosInstance = axios.create({ baseURL: "/", timeout: 15000, }); export type ApiRequestConfig = AxiosRequestConfig & { ignoreIdle?: boolean; allowWhileLocked?: boolean; suppressErrorMessage?: boolean; _retry?: boolean; }; class LockedError extends Error { constructor() { super("LOCKED"); this.name = "LockedError"; } } instance.interceptors.request.use((config: InternalAxiosRequestConfig & ApiRequestConfig) => { const session = useSessionStore(); const reqUrl = config.url || ""; const allowWhileLocked = config.allowWhileLocked || reqUrl.includes("/api/v1/auth/login") || reqUrl.includes("/api/v1/auth/register") || reqUrl.includes("/api/v1/auth/unlock"); if (session.locked && !allowWhileLocked) { return Promise.reject(new LockedError()); } const token = getToken(); if (token) { config.headers = config.headers || {}; (config.headers as Record).Authorization = `Bearer ${token}`; } if (!config.ignoreIdle) { markNetworkActive(); } return config; }); instance.interceptors.response.use( (response: AxiosResponse) => response, async (error: AxiosError) => { if (error instanceof LockedError) { const lockedConfig = error.config as ApiRequestConfig | undefined; if (!lockedConfig?.suppressErrorMessage) { ElMessage.warning(TEXT.common.messages.sessionLocked || "请解锁后继续操作"); } return Promise.reject(error); } const status = error.response?.status; const data = error.response?.data; const reqUrl = error.config?.url || ""; const isAuthEndpoint = reqUrl.includes("/api/v1/auth/login") || reqUrl.includes("/api/v1/auth/register") || reqUrl.includes("/api/v1/auth/extend") || reqUrl.includes("/api/v1/auth/unlock"); if (isAuthEndpoint) { // 认证相关的错误由具体页面自行处理,避免重复提示 return Promise.reject(error); } // 如果当前项目不存在或已失效,清理项目并跳到工作台 if (status === 404 && typeof data?.message === "string" && data.message.toLowerCase().includes("study")) { try { const studyStore = useStudyStore(); studyStore.clearCurrentStudy(); } catch { /* ignore */ } router.push("/workbench"); } if (status === 401) { const config = error.config as ApiRequestConfig; if (config && !config._retry) { const result = await extendAccessToken("response-401"); if (result.token) { config._retry = true; config.headers = config.headers || {}; config.headers.Authorization = `Bearer ${result.token}`; return instance(config); } if (result.authFailed) { lockSession("token_invalid"); } } } else if (status === 403 && (data as any)?.detail === "账号已停用") { forceLogout(); } else { const suppressErrorMessage = (error.config as ApiRequestConfig | undefined)?.suppressErrorMessage; if (!suppressErrorMessage) { if (data?.message || (data as any)?.detail) { const detail = (data as any)?.detail; const detailMessage = typeof detail === "string" ? detail : typeof detail?.message === "string" ? detail.message : undefined; ElMessage.error((data as any)?.message || detailMessage || TEXT.common.messages.requestFailed); } else { ElMessage.error(TEXT.common.messages.requestFailed); } } } return Promise.reject(error); } ); export const apiGet = (url: string, config?: ApiRequestConfig) => instance.get(url, config); export const apiPost = (url: string, data?: unknown, config?: ApiRequestConfig) => instance.post(url, data, config); export const apiPatch = (url: string, data?: unknown, config?: ApiRequestConfig) => instance.patch(url, data, config); export const apiPut = (url: string, data?: unknown, config?: ApiRequestConfig) => instance.put(url, data, config); export const apiDelete = (url: string, config?: ApiRequestConfig) => instance.delete(url, config); export default instance;