168 lines
5.2 KiB
TypeScript
168 lines
5.2 KiB
TypeScript
import axios from 'axios'
|
|
import { ApiResponseError, http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
|
|
export type ExportTaskStatus = 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'EXPIRED'
|
|
export type KnownExportTaskType =
|
|
| 'LAST_MILE_ORDER_LIST'
|
|
| 'OVERSEAS_INVENTORY_BALANCE'
|
|
| 'OVERSEAS_INVENTORY_LEDGER'
|
|
| 'OVERSEAS_QUOTE_LIST'
|
|
| 'MONTHLY_EXCHANGE_RATE_LIST'
|
|
| 'VEHICLE_LIST'
|
|
| 'FLEET_QUOTE_LIST'
|
|
| 'TRIP_OPERATIONS_LIST'
|
|
| 'SELFOP_INVENTORY_TURNOVER'
|
|
| 'CUSTOMER_LIST'
|
|
| 'DOMESTIC_WAREHOUSE_ORDER_LIST'
|
|
| 'FINANCE_CUSTOMER_ACCOUNT_LIST'
|
|
| 'FINANCE_CUSTOMER_ACCOUNT_LEDGER'
|
|
| 'FINANCE_FEE_SOURCE_LIST'
|
|
| 'FINANCE_RECONCILIATION_STATEMENT_LIST'
|
|
| 'FINANCE_RECEIVABLE_SETTLEMENT_LIST'
|
|
| 'FINANCE_RECEIVABLE_SETTLEMENT_DETAIL'
|
|
| 'FINANCE_PAYABLE_SETTLEMENT_LIST'
|
|
| 'FINANCE_PAYABLE_SETTLEMENT_DETAIL'
|
|
export type ExportTaskType = KnownExportTaskType | (string & {})
|
|
|
|
export interface PlatformExportTask {
|
|
id: string
|
|
taskNo: string
|
|
exportType: ExportTaskType
|
|
format: 'XLSX' | string
|
|
taskName: string
|
|
status: ExportTaskStatus
|
|
totalRows: number | null
|
|
processedRows: number
|
|
fileName: string | null
|
|
contentType: string | null
|
|
sizeBytes: number | null
|
|
failureMessage: string | null
|
|
attemptCount: number
|
|
maxAttempts: number
|
|
nextRetryAt: string | null
|
|
requestedBy: string
|
|
requestedUsername: string | null
|
|
requestedAt: string
|
|
startedAt: string | null
|
|
completedAt: string | null
|
|
expiresAt: string | null
|
|
}
|
|
|
|
export interface PlatformExportTaskPage {
|
|
records: PlatformExportTask[]
|
|
page: number
|
|
size: number
|
|
total: number
|
|
pages: number
|
|
}
|
|
|
|
export interface PlatformExportTaskQuery {
|
|
page: number
|
|
size: number
|
|
exportType?: string
|
|
status?: ExportTaskStatus
|
|
requestedFrom?: string
|
|
requestedTo?: string
|
|
}
|
|
|
|
export interface CreatePlatformExportTaskRequest {
|
|
exportType: ExportTaskType
|
|
format: 'XLSX'
|
|
taskName: string
|
|
parameters: Record<string, string | number | boolean | null | undefined | string[]>
|
|
}
|
|
|
|
const basePath = '/platform/v1/files/export-tasks'
|
|
const downloadTimeoutMs = 10 * 60 * 1000
|
|
const maxErrorEnvelopeBytes = 1024 * 1024
|
|
|
|
async function parseDownloadErrorBlob(blob: Blob, status?: number): Promise<ApiResponseError | null> {
|
|
if (!blob.type.toLowerCase().includes('json')) return null
|
|
|
|
if (blob.size > maxErrorEnvelopeBytes) {
|
|
return new ApiResponseError(
|
|
'下载接口返回了超出限制的 JSON 响应',
|
|
'INVALID_DOWNLOAD_RESPONSE',
|
|
status,
|
|
)
|
|
}
|
|
|
|
try {
|
|
const payload = JSON.parse(await blob.text()) as Partial<ApiEnvelope<unknown>>
|
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
return new ApiResponseError(
|
|
'下载接口返回了无法识别的 JSON 响应',
|
|
'INVALID_DOWNLOAD_RESPONSE',
|
|
status,
|
|
)
|
|
}
|
|
return new ApiResponseError(
|
|
payload.code === '200' ? '下载接口未返回文件内容' : payload.message || '文件下载失败',
|
|
payload.code === '200' ? 'INVALID_DOWNLOAD_RESPONSE' : payload.code || (status ? `HTTP_${status}` : 'DOWNLOAD_FAILED'),
|
|
status,
|
|
payload.data,
|
|
payload.requestId,
|
|
payload.traceId,
|
|
)
|
|
} catch {
|
|
return new ApiResponseError(
|
|
'下载接口返回了无法识别的 JSON 响应',
|
|
'INVALID_DOWNLOAD_RESPONSE',
|
|
status,
|
|
)
|
|
}
|
|
}
|
|
|
|
export async function createPlatformExportTask(payload: CreatePlatformExportTaskRequest) {
|
|
const response = await http.post<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(basePath, payload)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function listPlatformExportTasks(query: PlatformExportTaskQuery) {
|
|
const response = await http.get<ApiEnvelope<PlatformExportTaskPage>, ApiEnvelope<PlatformExportTaskPage>>(basePath, {
|
|
params: query,
|
|
})
|
|
const page = unwrapApiResponse(response)
|
|
return {
|
|
...page,
|
|
page: Number(page.page),
|
|
size: Number(page.size),
|
|
total: Number(page.total),
|
|
pages: Number(page.pages),
|
|
}
|
|
}
|
|
|
|
export async function getPlatformExportTask(taskId: string) {
|
|
const response = await http.get<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(
|
|
`${basePath}/${encodeURIComponent(taskId)}`,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function downloadPlatformExportTask(taskId: string) {
|
|
try {
|
|
const blob = await http.get<Blob, Blob>(`${basePath}/${encodeURIComponent(taskId)}/download`, {
|
|
responseType: 'blob',
|
|
// 导出文件上限 50 MB,不能继承普通接口的 15 秒超时,也不能无限等待半断开的连接。
|
|
timeout: downloadTimeoutMs,
|
|
})
|
|
const responseError = await parseDownloadErrorBlob(blob, 200)
|
|
if (responseError) throw responseError
|
|
return blob
|
|
} catch (error) {
|
|
if (axios.isAxiosError(error) && error.response?.data instanceof Blob) {
|
|
const responseError = await parseDownloadErrorBlob(error.response.data, error.response.status)
|
|
if (responseError) throw responseError
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function retryPlatformExportTask(taskId: string) {
|
|
const response = await http.post<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(
|
|
`${basePath}/${encodeURIComponent(taskId)}/retry`,
|
|
{},
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|