XHS / frontend /src /lib /api.ts
Trae Bot
Fix frontend API base URL for production build
41f6d7f
import axios, { AxiosError, type AxiosRequestConfig } from 'axios'
export type ApiError = {
status: number
message: string
data?: unknown
}
export type ApiResponse<T> = {
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<string, unknown>
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<T>(path: string, config?: AxiosRequestConfig) {
try {
const res = await http.get<T>(normalizePath(path), config)
return res.data
} catch (error) {
throw toApiError(error)
}
}
export function unwrapApiResponse<T>(value: unknown) {
if (!value || typeof value !== 'object') return { ok: false as const, error: '响应不是对象' }
const obj = value as Record<string, unknown>
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<T>(path: string, config?: AxiosRequestConfig) {
const raw = await apiGet<unknown>(path, config)
const parsed = unwrapApiResponse<T>(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<TRes, TReq>(
path: string,
data: TReq,
config?: AxiosRequestConfig,
) {
try {
const res = await http.post<TRes>(normalizePath(path), data, config)
return res.data
} catch (error) {
throw toApiError(error)
}
}
export async function apiPostWrapped<TRes, TReq>(
path: string,
data: TReq,
config?: AxiosRequestConfig,
) {
const raw = await apiPost<unknown, TReq>(path, data, config)
const parsed = unwrapApiResponse<TRes>(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<string>(normalizePath(path), {
...config,
responseType: 'text',
})
return res.data
} catch (error) {
throw toApiError(error)
}
}