import axios from 'axios' import { clearAdminSession, getAdminToken } from '@/utils/admin-auth' import { isWorkerSite } from '@/utils/site' import { clearWorkerSession, getWorkerToken } from '@/utils/worker-auth' export interface ApiEnvelope { code: number msg: string data: T errorCode?: string } const http = axios.create({ baseURL: '/', timeout: 50_000, headers: { 'Content-Type': 'application/json', }, }) http.interceptors.response.use( (response) => { const data = response.data as ApiEnvelope // Business-level error: HTTP 200 but code !== 0 if (typeof data?.code === 'number' && data.code !== 0) { const error = new Error(data.msg || '操作失败') as Error & { errorCode?: string code?: number } error.errorCode = data.errorCode error.code = data.code return Promise.reject(error) } return response.data }, (error) => { const responseMessage = typeof error?.response?.data?.msg === 'string' ? error.response.data.msg.trim() : '' const fallbackMessage = resolveFallbackHttpMessage(error) const normalizedError = new Error(responseMessage || fallbackMessage) as Error & { errorCode?: string status?: number } if (error?.response?.data?.errorCode) { normalizedError.errorCode = String(error.response.data.errorCode) } if (typeof error?.response?.status === 'number') { normalizedError.status = error.response.status } if (error?.config?.url && String(error.config.url).startsWith('/api/v1/admin')) { const status = Number(error?.response?.status || 0) const errorCode = String(error?.response?.data?.errorCode || '').trim() if (status === 401 && shouldClearAdminSession(errorCode)) { clearAdminSession() if ( window.location.hash.startsWith('#/admin') && !window.location.hash.startsWith('#/admin/login') ) { window.location.hash = '#/admin/login' } } } if (error?.config?.url && String(error.config.url).startsWith('/api/v1/worker')) { const status = Number(error?.response?.status || 0) const errorCode = String(error?.response?.data?.errorCode || '').trim() if (status === 401 && shouldClearWorkerSession(errorCode)) { clearWorkerSession() if (isWorkerSite() && window.location.pathname !== '/') { window.location.replace('/') } } } return Promise.reject(normalizedError) }, ) http.interceptors.request.use((config) => { if (String(config.url || '').startsWith('/api/v1/admin')) { const token = getAdminToken() if (token) { config.headers.Authorization = `Bearer ${token}` } } if (String(config.url || '').startsWith('/api/v1/worker')) { const token = getWorkerToken() if (token) { config.headers.Authorization = `Bearer ${token}` } } return config }) function resolveFallbackHttpMessage(error: unknown) { const message = String((error as { message?: string })?.message ?? '') if (message.includes('timeout')) { return '网络超时' } if (message === 'Network Error') { return '网络连接错误' } if (typeof (error as { response?: { statusText?: string } })?.response?.statusText === 'string') { return String((error as { response: { statusText: string } }).response.statusText).trim() } return '接口请求失败' } function shouldClearAdminSession(errorCode: string) { return [ 'admin_auth_required', 'admin_auth_invalid', 'admin_auth_expired', 'admin_auth_user_invalid', 'admin_auth_stale', 'admin_auth_revoked', ].includes(errorCode) } function shouldClearWorkerSession(errorCode: string) { return [ 'worker_auth_required', 'worker_auth_invalid', 'worker_auth_expired', 'worker_auth_user_invalid', 'worker_auth_stale', 'worker_auth_device_revoked', ].includes(errorCode) } function request(config: Parameters>>[0]) { return http.request, ApiEnvelope>(config) } export function apiGet(url: string, params?: Record) { return request({ method: 'GET', url, params }) } export function apiGetBlob(url: string, params?: Record) { return http.request({ method: 'GET', url, params, responseType: 'blob', }) } export function apiPost(url: string, data?: unknown) { return request({ method: 'POST', url, data: data as Record, }) } export function apiPut(url: string, data?: unknown) { return request({ method: 'PUT', url, data: data as Record, }) } export function apiPostForm(url: string, data: FormData) { return request({ method: 'POST', url, data, headers: { 'Content-Type': 'multipart/form-data' }, }) } export function apiDelete(url: string) { return request({ method: 'DELETE', url }) }