/** * OMS admin 接口通用:与 /order 同源(开发环境走 Vite 代理路径前缀) */ import type { AxiosError } from 'axios' import { axiosInstance } from '@/api/rbac' import { resolveAdminApiPath, resolveAdminAssetOrigin } from '@/utils/adminApi' /** 从 axios / 业务响应中提取可读错误信息 */ export function extractOmsRequestError(err: unknown): string { const axiosErr = err as AxiosError<{ msg?: string; Msg?: string; message?: string; Message?: string }> const data = axiosErr?.response?.data if (data && typeof data === 'object') { const msg = data.msg ?? data.Msg ?? data.message ?? data.Message if (msg != null && String(msg).trim()) { return String(msg).trim() } } return err instanceof Error ? err.message : String(err) } export function omsUrl(path: string): string { return resolveAdminApiPath(path) } export function assertBusinessOk(body: any) { if (body == null) throw new Error('空响应') const code = body.code ?? body.Code const ok = code === undefined || code === null || code === 0 || code === 200 || code === '0' || code === '200' || body.success === true || body.Success === true if (!ok) { throw new Error(body.msg ?? body.Msg ?? body.message ?? body.Message ?? '请求失败') } } /** 分页列表载荷(兼容 camel/Pascal、items/list/records) */ export function parseListPayload(resBody: any): { items: any[]; total: number } { assertBusinessOk(resBody) const inner = resBody.data ?? resBody.Data ?? resBody if (Array.isArray(inner)) { return { items: inner, total: inner.length } } const payload = inner && typeof inner === 'object' && !Array.isArray(inner) ? inner : resBody const items = payload.items ?? payload.Items ?? payload.list ?? payload.List ?? payload.records ?? payload.Records ?? payload.rows ?? payload.Rows ?? payload.result ?? payload.Result ?? (Array.isArray(payload.data) ? payload.data : undefined) ?? (Array.isArray(payload.Data) ? payload.Data : undefined) ?? [] const totalRaw = payload.total ?? payload.Total ?? payload.totalCount ?? payload.TotalCount ?? payload.count ?? payload.Count const list = Array.isArray(items) ? items : [] const total = typeof totalRaw === 'number' ? totalRaw : totalRaw != null && totalRaw !== '' ? Number(totalRaw) : list.length return { items: list, total: Number.isFinite(total) ? total : list.length } } export function unwrapData(resBody: any): T { assertBusinessOk(resBody) const nested = resBody.data ?? resBody.Data if (nested !== undefined && nested !== null) { return nested as T } // 部分 OMS 接口直接返回业务 DTO,无 data 包裹 return resBody as T } export async function omsGet(path: string, params?: Record): Promise { const res = await axiosInstance.get(omsUrl(path), { params }) return unwrapData(res.data) } export async function omsPost(path: string, body?: Record): Promise { const res = await axiosInstance.post(omsUrl(path), body ?? {}) return unwrapData(res.data) } /** * COS 绝对地址 → 同源 `/__cos__//...`(开发 Vite / 生产 nginx 反代)。 * cos.ideship.com 等自定义域名 https 不可用,直链 http 又会在 https 页面触发混合内容。 */ function toCosSameOriginProxyUrl(absoluteUrl: string): string | null { try { const u = new URL(absoluteUrl) const host = u.hostname.toLowerCase() if (!host.startsWith('cos.') && !host.includes('myqcloud.com')) return null return `/__cos__/${u.host}${u.pathname}${u.search}` } catch { return null } } /** 将 /uploads/... 相对路径转为可访问 URL(同源相对,或 VITE_API_ASSET_ORIGIN 绝对前缀) */ export function resolveOmsAssetUrl(path?: string | null): string { if (!path) return '' const value = path.trim() if (!value) return '' if (/^https?:\/\//i.test(value) || value.startsWith('data:') || value.startsWith('blob:')) { const cosProxied = toCosSameOriginProxyUrl(value) if (cosProxied) return cosProxied // 纠正常见错链:api.nanafa.com/uploads → api.nanafa.com/admin/uploads return value.replace( /^(https?:\/\/api\.(?:nanafa|ideship)\.com)\/uploads\//i, '$1/admin/uploads/', ) } const origin = resolveAdminAssetOrigin() const rel = value.startsWith('/') ? value : `/${value}` return origin ? `${origin}${rel}` : rel }