export type FriendlyApiError = { summary: string detail?: string } type MaybeAxiosError = { isAxiosError?: boolean response?: { status?: number; data?: unknown } code?: string message?: string } function stringifyMaybe(value: unknown): string { if (value == null) return '' if (typeof value === 'string') return value try { return JSON.stringify(value) } catch { return String(value) } } export function toFriendlyApiError(err: unknown): FriendlyApiError { const maybeAxios = err as MaybeAxiosError | null const isAxiosError = Boolean(maybeAxios && typeof maybeAxios === 'object' && maybeAxios.isAxiosError) if (!isAxiosError) { const detail = err instanceof Error ? err.message : stringifyMaybe(err) return { summary: '请求失败', detail: detail || undefined } } const status = maybeAxios?.response?.status const data: any = maybeAxios?.response?.data const serverMessage = stringifyMaybe(data?.message || data?.error || data) if (!status) { if (maybeAxios?.code === 'ECONNABORTED') return { summary: '请求超时', detail: '服务器响应超时,请稍后重试。' } if (maybeAxios?.code === 'ERR_NETWORK' || maybeAxios?.message === 'Network Error') { return { summary: '无法连接到服务器', detail: '请确认后端服务已启动,或检查网络/CORS 配置。' } } if (maybeAxios?.code === 'ERR_CANCELED') return { summary: '请求已取消' } return { summary: '网络异常', detail: '请检查网络连接或稍后重试。' } } if (status === 401) return { summary: '登录已失效', detail: serverMessage || '请重新登录后继续操作。' } if (status === 403) return { summary: '没有权限', detail: serverMessage || '当前账号无权限访问该资源。' } if (status === 404) return { summary: '资源不存在', detail: serverMessage || '接口不存在或已下线。' } if (status >= 500) return { summary: '服务器异常', detail: serverMessage || '服务端发生错误,请稍后重试。' } if (status === 400) return { summary: '请求参数错误', detail: serverMessage || '请检查输入内容后重试。' } return { summary: `请求失败 (${status})`, detail: serverMessage || undefined } }