/** 店铺数据结果批量下载任务(任务 113):打包请求体与下载 job 状态;纯逻辑。 */ export type ShopDataDownloadStatus = 'idle' | 'running' | 'done' | 'failed' export interface ShopDataDownloadJob { status: ShopDataDownloadStatus message?: string fileCount?: number errorCount?: number } /** 从 Content-Disposition 解析单文件下载名(rfc5987 优先),失败回默认名。 */ export function shopDataFilenameFromDisposition(disposition: string | undefined, fallback: string): string { const text = typeof disposition === 'string' ? disposition : '' const encoded = /filename\*=UTF-8''([^;]+)/i.exec(text) if (encoded) { try { const decoded = decodeURIComponent(encoded[1]) if (decoded.trim()) return decoded.trim() } catch { // 回退普通 filename } } const plain = /filename="?([^";]+)"?/i.exec(text) return plain && plain[1] ? plain[1].trim() : fallback } 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() 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 } }