task-113(任务与重复分析中心): 实现店铺数据任务批量下载

新增 shop-data-download.ts(打包请求体 result_ids + 下载 job 状态),
shop-data-api.ts 增加 requestShopDataZipDownload(POST /download-zip, blob)。

TDD: task-113.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 17:20:39 +08:00
parent 40463772c5
commit 69f6965c93
3 changed files with 125 additions and 1 deletions
@@ -1,7 +1,9 @@
/** 店铺数据任务列表载适配(任务 110GET /api/admin/shop-data-crawl-tasks + 筛选归一与解析。 */
/** 店铺数据任务列表/批量下载适配(任务 110/113)。 */
import { http } from '@/api/http'
import { parseShopDataTaskPage, type ShopDataTaskPageResult } from './shop-data-model.ts'
import { toShopDataQuery, type ShopDataFilter } from './shop-data-filter.ts'
import { toShopDataZipRequest } from './shop-data-download.ts'
import { downloadZipHeaderCounts } from './image-video-download.ts'
export const SHOP_DATA_CRAWL_TASKS_ENDPOINT = '/api/admin/shop-data-crawl-tasks'
@@ -15,3 +17,18 @@ export async function fetchShopDataTaskList(
})
return parseShopDataTaskPage(data)
}
export interface ShopDataZipDownloadResult {
blob: Blob
fileCount?: number
errorCount?: number
}
/** 批量下载选中结果文件为 zipPOST /download-zip(blob);从响应头读成功/失败计数。 */
export async function requestShopDataZipDownload(resultIds: readonly (number | string)[]): Promise<ShopDataZipDownloadResult> {
const { data, headers } = await http.post<Blob>(`${SHOP_DATA_CRAWL_TASKS_ENDPOINT}/download-zip`, toShopDataZipRequest(resultIds), {
responseType: 'blob',
})
const counts = downloadZipHeaderCounts(headers as Record<string, string>)
return { blob: data, fileCount: counts.fileCount, errorCount: counts.errorCount }
}
@@ -0,0 +1,48 @@
/** 店铺数据结果批量下载任务(任务 113):打包请求体与下载 job 状态;纯逻辑。 */
export type ShopDataDownloadStatus = 'idle' | 'running' | 'done' | 'failed'
export interface ShopDataDownloadJob {
status: ShopDataDownloadStatus
message?: string
fileCount?: number
errorCount?: number
}
function finitePositive(value: unknown): number | null {
const number = typeof value === 'number' && Number.isFinite(value)
? value
: typeof value === 'string' && /^\d+$/.test(value.trim())
? Number(value.trim())
: null
return number === null || number < 1 ? null : Math.floor(number)
}
/** result_id → POST /download-zip 请求体(过滤非法 id,按序去重)。 */
export function toShopDataZipRequest(resultIds: readonly (number | string)[]): { result_ids: number[] } {
const ids: number[] = []
const seen = new Set<number>()
for (const id of resultIds) {
const parsed = finitePositive(id)
if (parsed === null || seen.has(parsed)) continue
seen.add(parsed)
ids.push(parsed)
}
return { result_ids: ids }
}
export function createShopDataDownloadJob(): ShopDataDownloadJob {
return { status: 'idle' }
}
export function startShopDataDownload(): ShopDataDownloadJob {
return { status: 'running' }
}
export function succeedShopDataDownload(_job: ShopDataDownloadJob, fileCount: number, errorCount: number): ShopDataDownloadJob {
return { status: 'done', fileCount, errorCount }
}
export function failShopDataDownload(_job: ShopDataDownloadJob, message: string): ShopDataDownloadJob {
return { status: 'failed', message }
}