import axios, { AxiosError, type AxiosRequestConfig } from 'axios' export type ApiError = { status: number message: string data?: unknown } export type ApiResponse = { code: number msg: string data?: T | null } const defaultBaseUrl = import.meta.env.PROD ? '/api/v1' : 'http://localhost:8000/api/v1' const baseURL = import.meta.env.VITE_API_BASE_URL || defaultBaseUrl export const http = axios.create({ baseURL, timeout: 30000, }) function normalizePath(path: string) { if (path.startsWith('http://') || path.startsWith('https://')) return path return path.replace(/^\/+/, '') } function extractWrappedErrorMessage(data: unknown) { if (!data || typeof data !== 'object') return undefined const obj = data as Record const msg = obj.msg const code = obj.code if (typeof msg === 'string' && typeof code === 'number') return `API ${code}: ${msg}` if (typeof msg === 'string') return msg return undefined } function toApiError(error: unknown): ApiError { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError const status = axiosError.response?.status ?? 0 const wrappedMsg = extractWrappedErrorMessage(axiosError.response?.data) const message = wrappedMsg || (typeof axiosError.response?.data === 'string' ? axiosError.response.data : axiosError.message) return { status, message, data: axiosError.response?.data, } } return { status: 0, message: error instanceof Error ? error.message : String(error), } } export async function apiGet(path: string, config?: AxiosRequestConfig) { try { const res = await http.get(normalizePath(path), config) return res.data } catch (error) { throw toApiError(error) } } export function unwrapApiResponse(value: unknown) { if (!value || typeof value !== 'object') return { ok: false as const, error: '响应不是对象' } const obj = value as Record if (typeof obj.code !== 'number' || typeof obj.msg !== 'string') { return { ok: false as const, error: '响应缺少 code/msg 字段' } } const code = obj.code const msg = obj.msg const data = obj.data as T | null | undefined if (code !== 200) { return { ok: false as const, error: `API ${code}: ${msg}`, code, msg, data } } return { ok: true as const, data } } export async function apiGetWrapped(path: string, config?: AxiosRequestConfig) { const raw = await apiGet(path, config) const parsed = unwrapApiResponse(raw) if (parsed.ok) return parsed.data as T if (parsed.error === '响应缺少 code/msg 字段') return raw as T const apiError: ApiError = { status: 0, message: parsed.error, data: raw, } throw apiError } export async function apiPost( path: string, data: TReq, config?: AxiosRequestConfig, ) { try { const res = await http.post(normalizePath(path), data, config) return res.data } catch (error) { throw toApiError(error) } } export async function apiPostWrapped( path: string, data: TReq, config?: AxiosRequestConfig, ) { const raw = await apiPost(path, data, config) const parsed = unwrapApiResponse(raw) if (parsed.ok) return parsed.data as TRes if (parsed.error === '响应缺少 code/msg 字段') return raw as TRes const apiError: ApiError = { status: 0, message: parsed.error, data: raw, } throw apiError } export async function apiGetText(path: string, config?: AxiosRequestConfig) { try { const res = await http.get(normalizePath(path), { ...config, responseType: 'text', }) return res.data } catch (error) { throw toApiError(error) } }