/** * OMS 订单接口(Swagger:OMS-Order,与 admin 同源) */ import { axiosInstance } from '@/api/rbac' import { resolveAdminApiPath } from '@/utils/adminApi' function orderUrl(path: string): string { return resolveAdminApiPath(path) } export interface OrderListRequest { page: number pageSize: number /** all 或各状态英文 key,与文档一致 */ status?: string /** 子状态:all / pending / processed(问题件、已理赔 Tab) */ handleStatus?: string keyword?: string createTimeStart?: string createTimeEnd?: string } export interface OrderHandleStatusCounts { pending: number processed: number total?: number } export interface OrderListResult { items: any[] total: number } export interface OrderStatusCountsResult { total: number counts: Record handleCounts?: Record } 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 ?? '请求失败') } } /** 兼容 data 包裹或顶层直出(camel/Pascal) */ function unwrapApiData>(body: any): T { if (body == null) return {} as T const wrapped = body.data ?? body.Data if (wrapped !== undefined && wrapped !== null) { return wrapped as T } return body as T } /** 取出分页载荷(兼容 data 包裹或顶层直出、items/list/records、camel/Pascal) */ function parseOrderListPayload(resBody: any): OrderListResult { 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 ?? (Array.isArray(payload) ? payload : []) 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 : 0, } } /** 分页查询订单列表 POST /order/list */ export async function postOrderList(params: OrderListRequest): Promise { const body: OrderListRequest = { page: params.page, pageSize: params.pageSize, status: params.status ?? 'all', keyword: params.keyword ?? '', } if (params.handleStatus?.trim()) { body.handleStatus = params.handleStatus.trim() } if (params.createTimeStart?.trim()) { body.createTimeStart = params.createTimeStart.trim() } if (params.createTimeEnd?.trim()) { body.createTimeEnd = params.createTimeEnd.trim() } const res = await axiosInstance.post(orderUrl('/order/list'), body) return parseOrderListPayload(res.data) } /** 各状态订单数量聚合 GET /order/status-counts(单次查询,非逐状态分页) */ export async function getOrderStatusCounts(): Promise { const res = await axiosInstance.get(orderUrl('/order/status-counts')) assertBusinessOk(res.data) const inner = res.data?.data ?? res.data?.Data ?? res.data ?? {} const totalRaw = inner.total ?? inner.Total ?? 0 const counts = inner.counts ?? inner.Counts ?? {} const handleCountsRaw = inner.handleCounts ?? inner.HandleCounts ?? {} const handleCounts: Record = {} if (handleCountsRaw && typeof handleCountsRaw === 'object') { for (const [key, value] of Object.entries(handleCountsRaw as Record)) { handleCounts[key] = { pending: Number(value?.pending ?? value?.Pending ?? 0) || 0, processed: Number(value?.processed ?? value?.Processed ?? 0) || 0, total: Number(value?.total ?? value?.Total ?? 0) || undefined, } } } return { total: Number(totalRaw) || 0, counts: typeof counts === 'object' && counts !== null ? counts : {}, handleCounts, } } /** 订单详情 GET /order/detail?id= */ export async function getOrderDetail(id: number | string): Promise { const res = await axiosInstance.get(orderUrl('/order/detail'), { params: { id }, }) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data } export interface OrderIdRequest { id: number | string } export interface OrderExportRequest { status?: string keyword?: string createTimeStart?: string createTimeEnd?: string } export interface OrderPrintResult { printUrl?: string } export interface OrderExportResult { downloadUrl?: string expireAt?: string } async function postOrderById(path: string, body: OrderIdRequest): Promise { const res = await axiosInstance.post(orderUrl(path), { id: body.id }) assertBusinessOk(res.data) } /** POST /order/pay */ export function postOrderPay(body: OrderIdRequest) { return postOrderById('/order/pay', body) } /** POST /order/pre-declare */ export function postOrderPreDeclare(body: OrderIdRequest) { return postOrderById('/order/pre-declare', body) } /** POST /order/intercept */ export function postOrderIntercept(body: OrderIdRequest) { return postOrderById('/order/intercept', body) } /** POST /order/cancel-intercept */ export function postOrderCancelIntercept(body: OrderIdRequest) { return postOrderById('/order/cancel-intercept', body) } /** POST /order/delete */ export function postOrderDelete(body: OrderIdRequest) { return postOrderById('/order/delete', body) } export interface OrderTrackUpsertResult { skipped?: boolean skipReason?: string success?: boolean url?: string requestJson?: string curlCommand?: string statusCode?: number responseBody?: string } /** POST /order/track-upsert-small-bag → 同步调用轨迹 UpsertSmallBag(联调/手动触发) */ export async function postOrderTrackUpsertSmallBag(body: OrderIdRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/track-upsert-small-bag'), { id: body.id }) assertBusinessOk(res.data) const d = res.data?.data ?? res.data?.Data ?? res.data ?? {} return { skipped: d.skipped ?? d.Skipped, skipReason: d.skipReason ?? d.SkipReason, success: d.success ?? d.Success, url: d.url ?? d.Url, requestJson: d.requestJson ?? d.RequestJson, curlCommand: d.curlCommand ?? d.CurlCommand, statusCode: d.statusCode ?? d.StatusCode, responseBody: d.responseBody ?? d.ResponseBody, } } export interface OrderDownstreamForwardResult { success?: boolean supplierCode?: string url?: string requestJson?: string statusCode?: number responseBody?: string } /** POST /order/downstream-forward-create-one → 后端查订单表组装并转发 createOrderOne */ export async function postOrderDownstreamForwardCreateOne( body: OrderIdRequest ): Promise { const res = await axiosInstance.post(orderUrl('/order/downstream-forward-create-one'), { id: body.id }) assertBusinessOk(res.data) const d = res.data?.data ?? res.data?.Data ?? res.data ?? {} return { success: d.success ?? d.Success, supplierCode: d.supplierCode ?? d.SupplierCode, url: d.url ?? d.Url, requestJson: d.requestJson ?? d.RequestJson, statusCode: d.statusCode ?? d.StatusCode, responseBody: d.responseBody ?? d.ResponseBody, } } export interface OrderBatchActionFailure { orderId?: number OrderId?: number serviceNo?: string ServiceNo?: string reason?: string Reason?: string } export interface OrderBatchActionResult { successCount?: number SuccessCount?: number failures?: OrderBatchActionFailure[] Failures?: OrderBatchActionFailure[] } export interface OrderBatchJobOutput { jobId?: number JobId?: number action?: string Action?: string status?: string Status?: string total?: number Total?: number successCount?: number SuccessCount?: number failCount?: number FailCount?: number startedAt?: string StartedAt?: string finishedAt?: string FinishedAt?: string createTime?: string CreateTime?: string } export interface OrderBatchJobItemOutput { id?: number Id?: number orderId?: number OrderId?: number serviceNo?: string ServiceNo?: string status?: string Status?: string failReason?: string FailReason?: string startedAt?: string StartedAt?: string finishedAt?: string FinishedAt?: string } function parseOrderBatchJob(raw: any): { jobId: number action: string status: string total: number successCount: number failCount: number startedAt?: string finishedAt?: string createTime?: string } { const d = unwrapApiData(raw) return { jobId: Number(d.jobId ?? d.JobId ?? 0), action: String(d.action ?? d.Action ?? ''), status: String(d.status ?? d.Status ?? ''), total: Number(d.total ?? d.Total ?? 0), successCount: Number(d.successCount ?? d.SuccessCount ?? 0), failCount: Number(d.failCount ?? d.FailCount ?? 0), startedAt: d.startedAt ?? d.StartedAt, finishedAt: d.finishedAt ?? d.FinishedAt, createTime: d.createTime ?? d.CreateTime, } } function parseOrderBatchJobItem(raw: any): { id: number orderId: number serviceNo?: string status: string failReason?: string resultJson?: string } { return { id: Number(raw.id ?? raw.Id ?? 0), orderId: Number(raw.orderId ?? raw.OrderId ?? 0), serviceNo: raw.serviceNo ?? raw.ServiceNo, status: String(raw.status ?? raw.Status ?? ''), failReason: raw.failReason ?? raw.FailReason, resultJson: raw.resultJson ?? raw.ResultJson, } } export function parseBatchJobPrintUrl(item: { resultJson?: string }): string { if (!item.resultJson) return '' try { const obj = JSON.parse(item.resultJson) return String(obj.printUrl ?? obj.PrintUrl ?? '').trim() } catch { return '' } } const ORDER_BATCH_ACTION_ROUTES: Record = { pay: '/order/batch/pay', 'pre-declare': '/order/batch/pre-declare', intercept: '/order/batch/intercept', 'cancel-intercept': '/order/batch/cancel-intercept', delete: '/order/batch/delete', print: '/order/batch/print', 'mark-problematic': '/order/batch/mark-problematic', 'mark-claimed': '/order/batch/mark-claimed', 'handle-status-update': '/order/batch/handle-status-update', 'track-upsert-small-bag': '/order/batch/track-upsert', 'downstream-forward-create-one': '/order/batch/downstream-forward', } /** POST /order/batch/jobs 或按 action 路由的批量任务 */ export async function postOrderBatchJob(body: { action: string orderIds: Array payloadJson?: string requestId?: string }): Promise> { const route = ORDER_BATCH_ACTION_ROUTES[body.action] ?? '/order/batch/jobs' const payload = { action: body.action, orderIds: body.orderIds.map((id) => Number(id)), payloadJson: body.payloadJson, requestId: body.requestId, } const res = await axiosInstance.post(orderUrl(route), payload) assertBusinessOk(res.data) return parseOrderBatchJob(res.data) } export function formatBatchJobFailureMessage( job: { successCount: number; failCount: number }, items: Array<{ orderId: number; serviceNo?: string; status: string; failReason?: string }>, rows?: any[], getRowLabel?: (row: any, item: { orderId: number; serviceNo?: string }) => string ): string { const failed = items.filter((item) => item.status === 'failed') if (!failed.length) { return `完成:成功 ${job.successCount} 条` } const lines = failed.slice(0, 8).map((item) => { const row = rows?.find((r) => Number(r?.id ?? r?.Id) === item.orderId) const label = getRowLabel?.(row, item) ?? item.serviceNo ?? String(item.orderId) return `【${label}】${item.failReason || '失败'}` }) return [`完成:成功 ${job.successCount} 条,失败 ${job.failCount} 条`, ...lines].join('\n') } const BATCH_JOB_TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']) export interface OrderBatchDlxStats { enabled: boolean deadLetterMessageCount: number dlxAlertThreshold: number alert: boolean } export interface OrderBatchJobManageResult { jobId: number retriedCount: number cancelledCount: number status: string } function parseOrderBatchDlxStats(raw: any): OrderBatchDlxStats { const d = raw?.data ?? raw?.Data ?? raw ?? {} return { enabled: Boolean(d.enabled ?? d.Enabled), deadLetterMessageCount: Number(d.deadLetterMessageCount ?? d.DeadLetterMessageCount ?? 0), dlxAlertThreshold: Number(d.dlxAlertThreshold ?? d.DlxAlertThreshold ?? 0), alert: Boolean(d.alert ?? d.Alert), } } function parseOrderBatchJobManage(raw: any): OrderBatchJobManageResult { const d = raw?.data ?? raw?.Data ?? raw ?? {} return { jobId: Number(d.jobId ?? d.JobId ?? 0), retriedCount: Number(d.retriedCount ?? d.RetriedCount ?? 0), cancelledCount: Number(d.cancelledCount ?? d.CancelledCount ?? 0), status: String(d.status ?? d.Status ?? ''), } } /** GET /order/batch/jobs */ export async function getOrderBatchJobList(params: { page?: number pageSize?: number status?: string action?: string jobId?: number | string }): Promise<{ items: Array>; total: number }> { const res = await axiosInstance.get(orderUrl('/order/batch/jobs'), { params: { Page: params.page ?? 1, PageSize: params.pageSize ?? 30, Status: params.status || undefined, Action: params.action || undefined, JobId: params.jobId ? Number(params.jobId) : undefined, }, }) const parsed = parseOrderListPayload(res.data) return { items: (parsed.items || []).map(parseOrderBatchJob), total: parsed.total, } } /** GET /order/batch/dlx/stats */ export async function getOrderBatchDlxStats(): Promise { const res = await axiosInstance.get(orderUrl('/order/batch/dlx/stats')) assertBusinessOk(res.data) return parseOrderBatchDlxStats(res.data) } /** POST /order/batch/jobs/{id}/retry-failed */ export async function postOrderBatchJobRetryFailed( jobId: number | string, body?: { itemIds?: number[] } ): Promise { const res = await axiosInstance.post(orderUrl(`/order/batch/jobs/${jobId}/retry-failed`), body ?? {}) assertBusinessOk(res.data) return parseOrderBatchJobManage(res.data) } /** POST /order/batch/jobs/{id}/cancel */ export async function postOrderBatchJobCancel(jobId: number | string): Promise { const res = await axiosInstance.post(orderUrl(`/order/batch/jobs/${jobId}/cancel`)) assertBusinessOk(res.data) return parseOrderBatchJobManage(res.data) } function sleep(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } /** POST /order/batch/track-upsert */ export async function postOrderBatchTrackUpsert(body: { orderIds: Array requestId?: string }): Promise> { const res = await axiosInstance.post(orderUrl('/order/batch/track-upsert'), { orderIds: body.orderIds.map((id) => Number(id)), requestId: body.requestId, }) assertBusinessOk(res.data) return parseOrderBatchJob(res.data) } /** POST /order/batch/downstream-forward */ export async function postOrderBatchDownstreamForward(body: { orderIds: Array requestId?: string }): Promise> { const res = await axiosInstance.post(orderUrl('/order/batch/downstream-forward'), { orderIds: body.orderIds.map((id) => Number(id)), requestId: body.requestId, }) assertBusinessOk(res.data) return parseOrderBatchJob(res.data) } /** GET /order/batch/jobs/{id} */ export async function getOrderBatchJob(jobId: number | string): Promise> { const res = await axiosInstance.get(orderUrl(`/order/batch/jobs/${jobId}`)) assertBusinessOk(res.data) return parseOrderBatchJob(res.data) } /** GET /order/batch/jobs/{id}/items */ export async function getOrderBatchJobItems( jobId: number | string ): Promise>> { const res = await axiosInstance.get(orderUrl(`/order/batch/jobs/${jobId}/items`)) assertBusinessOk(res.data) const payload = unwrapApiData(res.data) const list = Array.isArray(payload) ? payload : payload.items ?? payload.Items ?? [] return (Array.isArray(list) ? list : []).map(parseOrderBatchJobItem) } /** 轮询批量任务直至结束或超时 */ export async function waitOrderBatchJob( jobId: number | string, options?: { intervalMs?: number; timeoutMs?: number } ): Promise<{ job: ReturnType items: Array> }> { const intervalMs = options?.intervalMs ?? 1500 const timeoutMs = options?.timeoutMs ?? 300_000 const started = Date.now() while (Date.now() - started < timeoutMs) { const job = await getOrderBatchJob(jobId) if (BATCH_JOB_TERMINAL_STATUSES.has(job.status.toLowerCase())) { const items = await getOrderBatchJobItems(jobId) return { job, items } } await sleep(intervalMs) } throw new Error('批量任务执行超时,请稍后在任务列表中查看进度') } export function isOrderBatchQueueDisabledError(error: unknown): boolean { const msg = String((error as any)?.message ?? (error as any)?.msg ?? '') return msg.includes('未启用') || msg.toLowerCase().includes('rabbitmq') } export interface OrderMarkProblematicRequest { orderIds: Array issueTemplateId?: number | string issueType?: string title?: string content?: string remark?: string } export interface OrderMarkClaimedRequest { orderIds: Array claimType?: string claimAmount?: number claimCurrency?: string claimReason?: string remark?: string } export interface OrderHandleStatusUpdateRequest { orderIds: Array handleStatus?: string remark?: string } function parseBatchActionResult(raw: any): { successCount: number; failures: Array<{ orderId: number; serviceNo?: string; reason: string }> } { const d = raw?.data ?? raw?.Data ?? raw ?? {} const failuresRaw = d.failures ?? d.Failures ?? [] const failures = (Array.isArray(failuresRaw) ? failuresRaw : []).map((item: any) => ({ orderId: Number(item.orderId ?? item.OrderId ?? 0), serviceNo: item.serviceNo ?? item.ServiceNo, reason: String(item.reason ?? item.Reason ?? ''), })) return { successCount: Number(d.successCount ?? d.SuccessCount ?? 0), failures, } } /** POST /order/mark-problematic */ export async function postOrderMarkProblematic(body: OrderMarkProblematicRequest): Promise<{ successCount: number; failures: Array<{ orderId: number; serviceNo?: string; reason: string }> }> { const res = await axiosInstance.post(orderUrl('/order/mark-problematic'), { orderIds: body.orderIds.map((id) => Number(id)), issueTemplateId: body.issueTemplateId != null ? Number(body.issueTemplateId) : undefined, issueType: body.issueType, title: body.title, content: body.content, remark: body.remark, }) assertBusinessOk(res.data) return parseBatchActionResult(res.data) } /** POST /order/mark-claimed */ export async function postOrderMarkClaimed(body: OrderMarkClaimedRequest) { const res = await axiosInstance.post(orderUrl('/order/mark-claimed'), { orderIds: body.orderIds.map((id) => Number(id)), claimType: body.claimType, claimAmount: body.claimAmount, claimCurrency: body.claimCurrency, claimReason: body.claimReason, remark: body.remark, }) assertBusinessOk(res.data) return parseBatchActionResult(res.data) } /** POST /order/handle-status/update */ export async function postOrderHandleStatusUpdate(body: OrderHandleStatusUpdateRequest) { const res = await axiosInstance.post(orderUrl('/order/handle-status/update'), { orderIds: body.orderIds.map((id) => Number(id)), handleStatus: body.handleStatus ?? 'processed', remark: body.remark, }) assertBusinessOk(res.data) return parseBatchActionResult(res.data) } /** POST /order/print → data.printUrl */ export async function postOrderPrint(body: OrderIdRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/print'), { id: body.id }) assertBusinessOk(res.data) const d = res.data?.data ?? res.data?.Data ?? {} return { printUrl: d.printUrl ?? d.PrintUrl ?? '', } } /** POST /order/export → data.downloadUrl、expireAt */ export async function postOrderExport(params: OrderExportRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/export'), { status: params.status ?? 'all', keyword: params.keyword ?? '', createTimeStart: params.createTimeStart ?? '', createTimeEnd: params.createTimeEnd ?? '', }) assertBusinessOk(res.data) const d = res.data?.data ?? res.data?.Data ?? {} return { downloadUrl: d.downloadUrl ?? d.DownloadUrl ?? '', expireAt: d.expireAt ?? d.ExpireAt ?? '', } } /** POST /order/price-check → data.items + data.failures */ export interface PriceCheckRequest { destinationCountry: string shippingMethod: string cargoWeight: string length: string width: string height: string /** @deprecated 请使用 cargoTypes */ cargoType?: string /** 货物类型(多选,字典 cargo_attribute 编码) */ cargoTypes?: string[] /** 仓库主键(多选;为空则遍历全部仓库) */ warehouseIds?: number[] destinationProvince: string postalCode: string } export interface PriceCheckFailure { productId?: number productCode?: string productName?: string step?: string reason?: string cargoTypeName?: string warehouseName?: string } function normalizePriceCheckFailure(raw: unknown): PriceCheckFailure | null { if (raw == null) return null if (typeof raw === 'string') { const text = raw.trim() return text ? { reason: text } : null } if (typeof raw !== 'object') return null const row = raw as Record const reason = String( row.reason ?? row.Reason ?? row.message ?? row.Message ?? row.msg ?? row.Msg ?? '', ).trim() if (!reason && row.step == null && row.Step == null) return null return { productId: (row.productId ?? row.ProductId) as number | undefined, productCode: (row.productCode ?? row.ProductCode) as string | undefined, productName: (row.productName ?? row.ProductName) as string | undefined, step: (row.step ?? row.Step) as string | undefined, reason: reason || undefined, cargoTypeName: (row.cargoTypeName ?? row.CargoTypeName) as string | undefined, warehouseName: (row.warehouseName ?? row.WarehouseName) as string | undefined, } } /** 解析查价响应(兼容 data 包裹 / 直出、camel/Pascal、failures 在根或 data 内) */ export function parsePriceCheckResponse(resBody: any): { items: any[] failures: PriceCheckFailure[] } { assertBusinessOk(resBody) const root = resBody ?? {} const inner = root.data ?? root.Data ?? root if (Array.isArray(inner)) { return { items: inner, failures: [] } } const payload = inner && typeof inner === 'object' && !Array.isArray(inner) ? inner : root const items = (payload as any).items ?? (payload as any).Items ?? (payload as any).list ?? (payload as any).List ?? [] const rawFailures = (payload as any).failures ?? (payload as any).Failures ?? root.failures ?? root.Failures ?? [] const failureRows = Array.isArray(rawFailures) ? rawFailures : rawFailures && typeof rawFailures === 'object' ? [rawFailures] : [] const failures = failureRows .map(normalizePriceCheckFailure) .filter((f): f is PriceCheckFailure => f != null && Boolean(f.reason?.trim())) const itemList = Array.isArray(items) ? items : [] if (!itemList.length && !failures.length) { const msg = String( root.msg ?? root.Msg ?? root.message ?? root.Message ?? (payload as any).msg ?? (payload as any).Msg ?? (payload as any).message ?? (payload as any).Message ?? '', ).trim() if (msg) { failures.push({ reason: msg }) } } return { items: itemList, failures } } export async function postOrderPriceCheck(params: PriceCheckRequest): Promise<{ items: any[] failures: PriceCheckFailure[] }> { const res = await axiosInstance.post(orderUrl('/order/price-check'), params) return parsePriceCheckResponse(res.data) } /** GET /order/template/list */ export async function getOrderTemplateList(params: { page?: number pageSize?: number type?: 'standard' | 'custom' | string keyword?: string }): Promise { const res = await axiosInstance.get(orderUrl('/order/template/list'), { params: { page: params.page ?? 1, pageSize: params.pageSize ?? 50, type: params.type ?? '', keyword: params.keyword ?? '', }, }) return parseOrderListPayload(res.data) } /** 从 axios 响应中解析二进制下载内容 */ function resolveDownloadBlob(response: { data?: unknown headers?: Record }): { blob: Blob; contentType: string } { const headers = response?.headers ?? {} const contentType = String(headers['content-type'] ?? headers['Content-Type'] ?? '') const raw = response?.data if (raw instanceof Blob) { return { blob: raw, contentType: contentType || raw.type } } if (raw instanceof ArrayBuffer) { return { blob: new Blob([raw], { type: contentType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }), contentType, } } if (raw instanceof Uint8Array) { return { blob: new Blob([raw], { type: contentType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }), contentType, } } if (raw && typeof raw === 'object') { const body = raw as Record throw new Error( String(body.msg ?? body.Msg ?? body.message ?? body.Message ?? '下载失败'), ) } throw new Error('下载响应无效') } async function parseDownloadErrorBlob(blob: Blob): Promise { const text = await blob.text() try { const json = JSON.parse(text) throw new Error(String(json.msg ?? json.message ?? json.Message ?? '下载失败')) } catch (error: unknown) { if (error instanceof Error && error.message !== '下载失败') { throw error } throw new Error(text || '下载失败') } } function triggerBrowserFileDownload(blob: Blob, fileName: string) { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = fileName.endsWith('.xlsx') ? fileName : `${fileName}.xlsx` a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } /** GET /order/template/download?templateId= → Excel 文件流 */ export async function downloadOrderTemplate(templateId: number, fileName: string) { const response = await axiosInstance.get(orderUrl('/order/template/download'), { params: { templateId }, responseType: 'arraybuffer', }) const { blob, contentType } = resolveDownloadBlob(response) const ct = contentType || blob.type || '' if (ct.includes('application/json') || ct.includes('text/json')) { await parseDownloadErrorBlob(blob) } if (!blob.size) { throw new Error('下载文件为空') } triggerBrowserFileDownload(blob, fileName) } export interface OrderTemplateFieldSchemaItem { fieldKey: string label: string required: boolean dataType: string dictType?: string group: string example: string } export interface OrderTemplateCustomDetail { id: number name: string type: string standardTemplateKey?: string mappingStatus?: string sourceFileName?: string sourceHeaders: string[] mapping?: { standardTemplateKey?: string headerRowIndex?: number dataStartRowIndex?: number mappings?: Array<{ standardFieldKey: string; sourceColumn: string; required?: boolean }> unmappedSourceColumns?: string[] } standardFields: OrderTemplateFieldSchemaItem[] } /** GET /order/template/field-schema */ export async function getOrderTemplateFieldSchema(templateKey?: string) { const res = await axiosInstance.get(orderUrl('/order/template/field-schema'), { params: { templateKey: templateKey ?? '' }, }) assertBusinessOk(res.data) return (res.data?.data ?? res.data?.Data ?? res.data) as { templateKey: string templateName: string fields: OrderTemplateFieldSchemaItem[] } } /** POST /order/template/custom/upload */ export async function postOrderTemplateCustomUpload(name: string, file: File) { const form = new FormData() form.append('name', name) form.append('file', file) const res = await axiosInstance.post(orderUrl('/order/template/custom/upload'), form) assertBusinessOk(res.data) const d = unwrapApiData(res.data) const templateId = Number(d.templateId ?? d.TemplateId ?? d.id ?? d.Id) if (!Number.isFinite(templateId) || templateId <= 0) { throw new Error('上传成功但未返回模板 ID') } return { templateId, name: String(d.name ?? d.Name ?? name), mappingStatus: String(d.mappingStatus ?? d.MappingStatus ?? 'draft'), sourceHeaders: (d.sourceHeaders ?? d.SourceHeaders ?? []) as string[], } } /** GET /order/template/custom/detail */ export async function getOrderTemplateCustomDetail(templateId: number): Promise { const res = await axiosInstance.get(orderUrl('/order/template/custom/detail'), { params: { templateId }, }) assertBusinessOk(res.data) const d = unwrapApiData(res.data) return { id: Number(d.id ?? d.Id ?? templateId), name: String(d.name ?? d.Name ?? ''), type: String(d.type ?? d.Type ?? 'custom'), standardTemplateKey: d.standardTemplateKey ?? d.StandardTemplateKey, mappingStatus: d.mappingStatus ?? d.MappingStatus, sourceFileName: d.sourceFileName ?? d.SourceFileName, sourceHeaders: (d.sourceHeaders ?? d.SourceHeaders ?? []) as string[], mapping: d.mapping ?? d.Mapping, standardFields: (d.standardFields ?? d.StandardFields ?? []) as OrderTemplateFieldSchemaItem[], } } /** POST /order/template/custom/save-mapping */ export async function postOrderTemplateSaveMapping(body: { templateId: number standardTemplateKey?: string headerRowIndex?: number dataStartRowIndex?: number mappings: Array<{ standardFieldKey: string; sourceColumn: string }> }) { const templateId = Number(body.templateId) if (!Number.isFinite(templateId) || templateId <= 0) { throw new Error('模板 ID 无效') } const payload = { TemplateId: templateId, StandardTemplateKey: body.standardTemplateKey, HeaderRowIndex: body.headerRowIndex ?? 1, DataStartRowIndex: body.dataStartRowIndex ?? 2, Mappings: body.mappings.map((item) => ({ StandardFieldKey: item.standardFieldKey, SourceColumn: item.sourceColumn, })), } const res = await axiosInstance.post(orderUrl('/order/template/custom/save-mapping'), payload) assertBusinessOk(res.data) const d = unwrapApiData(res.data) return { templateId: Number(d.templateId ?? d.TemplateId ?? templateId), mappingStatus: String(d.mappingStatus ?? d.MappingStatus ?? 'draft'), missingRequiredFields: (d.missingRequiredFields ?? d.MissingRequiredFields ?? []) as string[], } } /** DELETE /order/template/custom/{id} */ export async function deleteOrderTemplateCustom(templateId: number) { const res = await axiosInstance.delete(orderUrl(`/order/template/custom/${templateId}`)) const body = res?.data if (body == null || body === '') { return } assertBusinessOk(body) } /** POST /order/upload multipart: file + templateId */ export async function postOrderUpload( file: File, templateId: number ): Promise<{ taskId?: number; jobId?: number; async?: boolean }> { const form = new FormData() form.append('file', file) form.append('templateId', String(templateId)) const res = await axiosInstance.post(orderUrl('/order/upload'), form) assertBusinessOk(res.data) const d = unwrapApiData(res.data) const taskId = d.taskId ?? d.TaskId const jobId = d.jobId ?? d.JobId const asyncFlag = d.async ?? d.Async return { taskId: taskId != null ? Number(taskId) : undefined, jobId: jobId != null ? Number(jobId) : undefined, async: asyncFlag === true || asyncFlag === 'true', } } /** GET /order/task/list */ export async function getOrderTaskList(params: { page?: number pageSize?: number status?: string keyword?: string }): Promise { const res = await axiosInstance.get(orderUrl('/order/task/list'), { params: { page: params.page ?? 1, pageSize: params.pageSize ?? 20, status: params.status ?? '', keyword: params.keyword ?? '', }, }) return parseOrderListPayload(res.data) } /** GET /order/task/detail?taskId= */ export async function getOrderTaskDetail(taskId: number | string): Promise { const res = await axiosInstance.get(orderUrl('/order/task/detail'), { params: { taskId }, }) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? {} } /** POST /order/parcel/create */ export async function postOrderParcelCreate(body: Record): Promise { const res = await axiosInstance.post(orderUrl('/order/parcel/create'), body) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? {} } export interface OrderParcelBatchCreateResult { batchNo?: string BatchNo?: string jobId?: number JobId?: number async?: boolean Async?: boolean total?: number Total?: number successCount?: number SuccessCount?: number failCount?: number FailCount?: number items?: Array<{ index?: number Index?: number requestId?: string RequestId?: string clientNo?: string ClientNo?: string serviceNo?: string ServiceNo?: string trackNo?: string TrackNo?: string labelUrl?: string LabelUrl?: string success?: boolean Success?: boolean message?: string Message?: string }> Items?: OrderParcelBatchCreateResult['items'] } function parseOrderParcelBatchCreateResult(raw: any): { batchNo: string jobId?: number async: boolean total: number successCount: number failCount: number items: Array<{ index: number clientNo?: string serviceNo?: string success: boolean message?: string }> } { const d = raw?.data ?? raw?.Data ?? raw ?? {} const itemsRaw = d.items ?? d.Items ?? [] const items = (Array.isArray(itemsRaw) ? itemsRaw : []).map((item: any, idx: number) => ({ index: Number(item.index ?? item.Index ?? idx + 1), clientNo: item.clientNo ?? item.ClientNo, serviceNo: item.serviceNo ?? item.ServiceNo, success: item.success ?? item.Success ?? false, message: item.message ?? item.Message, })) return { batchNo: String(d.batchNo ?? d.BatchNo ?? ''), jobId: d.jobId != null || d.JobId != null ? Number(d.jobId ?? d.JobId) : undefined, async: d.async === true || d.Async === true, total: Number(d.total ?? d.Total ?? items.length), successCount: Number(d.successCount ?? d.SuccessCount ?? 0), failCount: Number(d.failCount ?? d.FailCount ?? 0), items, } } /** POST /order/parcel/batch-create */ export async function postOrderParcelBatchCreate( bodies: Array> ): Promise> { const res = await axiosInstance.post(orderUrl('/order/parcel/batch-create'), bodies) assertBusinessOk(res.data) return parseOrderParcelBatchCreateResult(res.data) } /** POST /order/update */ export async function postOrderUpdate(body: Record): Promise { const res = await axiosInstance.post(orderUrl('/order/update'), body) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data } /** POST /order/weight/update */ export async function postOrderWeightUpdate(body: { id: number | string weights: Array<{ type: string length?: number width?: number height?: number actualWeight?: number }> }): Promise { const res = await axiosInstance.post(orderUrl('/order/weight/update'), body) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data } /** POST /order/fee/recalculate-receivable */ export async function postOrderRecalculateReceivable(body: OrderIdRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/fee/recalculate-receivable'), { id: body.id }) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data } /** POST /order/fee/recalculate-payable */ export async function postOrderRecalculatePayable(body: OrderIdRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/fee/recalculate-payable'), { id: body.id }) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data } export interface OrderFeeAddRequest { id: number | string type: 'receivable' | 'payable' costItem: string amount: number currency: string chargingStage: string downstreamProvider?: string priceNo?: string priceEffectiveTime?: string } /** POST /order/fee/add */ export async function postOrderFeeAdd(body: OrderFeeAddRequest): Promise { const res = await axiosInstance.post(orderUrl('/order/fee/add'), body) assertBusinessOk(res.data) return res.data?.data ?? res.data?.Data ?? res.data }