// REQ-USR-004: 统一 Axios 实例 + Result 拆包 + 错误拦截(docs/04 § 2.3 / § 2.4) import axios, { AxiosError, type AxiosInstance, type InternalAxiosRequestConfig, } from 'axios'; /** token 持久化键名(D6,跨 task 引用此常量,不写字面量) */ export const TOKEN_STORAGE_KEY = 'xly_erp_token'; /** 网络 / 超时 / 5xx 无可用业务码时的统一标识 */ export const NETWORK_ERROR_CODE = -1; /** 后端统一响应体 Result(docs/04 § 1.4) */ export interface Result { code: number; message: string; data: T; } /** 业务 / 网络错误,携带后端业务码供页面分流文案 */ export class ApiError extends Error { code: number; constructor(code: number, message: string) { super(message); this.name = 'ApiError'; this.code = code; } } const request: AxiosInstance = axios.create({ baseURL: '/api', timeout: 15000, headers: { 'Content-Type': 'application/json' }, }); // 请求拦截器:已登录态注入 Authorization;登录 / 版本端点无 token 时自然跳过 request.interceptors.request.use((config: InternalAxiosRequestConfig) => { const token = localStorage.getItem(TOKEN_STORAGE_KEY); if (token) { config.headers.set('Authorization', `Bearer ${token}`); } return config; }); // 响应拦截器:拆 Result。code=0 取 data;非 0 抛 ApiError;无响应(网络/超时/5xx)兜底 request.interceptors.response.use( (response) => { const body = response.data as Result; if (body && typeof body === 'object' && 'code' in body) { if (body.code === 0) { return body.data; } return Promise.reject(new ApiError(body.code, body.message || '请求失败')); } // 非 Result 结构(理论上不应出现),原样返回 data return response.data; }, (error: AxiosError) => { // 已是 ApiError 直接透传(极少数情况) if (error instanceof ApiError) { return Promise.reject(error); } const body = error.response?.data as Result | undefined; if (body && typeof body === 'object' && 'code' in body) { return Promise.reject(new ApiError(body.code, body.message || '请求失败')); } // 无响应:网络异常 / 超时 / 5xx 无 Result 体 return Promise.reject(new ApiError(NETWORK_ERROR_CODE, '网络异常,请稍后重试')); }, ); export default request;