// 批量上传处理函数 import { chunkUpload } from '@/lib/chunk-upload' import type { BatchFileItem, BatchUploadStatus } from './upload-types' import { PREVIEW_OPTIONAL_TYPES, CHUNK_THRESHOLD } from './upload-constants' import { completeBatchUpload, fileToBase64String } from './special-batch-uploaders' import { compressPreviewImage } from './image-processor' import { shouldPassRenderer, requiresRenderer, shouldAutoFillGeneral } from '@/lib/material-validation' import { parseApiError } from '@/lib/error-handler' // 批量上传单个文件的处理函数 export async function uploadBatchFile( item: BatchFileItem, fileIndex: number, materialType: string, categoryName: string, sceneName: string, platformName: string, productCategoryName: string, renderer: string, onProgress: (progress: number, speed?: number) => void, onStatusUpdate: (status: Partial) => void, onRequest: (xhr: XMLHttpRequest | null) => void, isCancelled: () => boolean, startTime: number ): Promise<{ success: boolean; uploadId?: string; error?: string }> { try { // 构建元数据 const metadata: any = { materialType, productModel: item.productModel, categoryName: shouldAutoFillGeneral(materialType) ? '通用' : item.categoryName, sceneName: shouldAutoFillGeneral(materialType) ? '通用' : item.sceneName, platformName: item.platformName || platformName || '', productCategoryName: item.productCategoryName || productCategoryName || '', } // 添加渲染器(3D场景) if (requiresRenderer(materialType) && item.renderer) { metadata.renderer = item.renderer } // 处理预览图(如果需要)- 先压缩再转换为base64 // 详情模板:预览图可选,如果上传了则使用 const needsPreview = !PREVIEW_OPTIONAL_TYPES.includes(materialType) let compressedPreview: File | undefined if (needsPreview && item.previewImage) { compressedPreview = await compressPreviewImage(item.previewImage) metadata.previewImage = await fileToBase64String(compressedPreview) } else if (materialType === '详情模板' && item.previewImage) { // 详情模板:如果用户上传了预览图,也处理 compressedPreview = await compressPreviewImage(item.previewImage) metadata.previewImage = await fileToBase64String(compressedPreview) } // 判断是否需要分片上传 const useChunk = item.file.size >= CHUNK_THRESHOLD if (useChunk) { // 分片上传(chunkSize使用默认配置:内网20MB,公网10MB) const result = await chunkUpload(item.file, metadata, { onProgress, onRequest, skipComplete: true, maxRetries: 5, }) onStatusUpdate({ status: 'processing', progress: 100, uploadId: result?.uploadId }) if (result?.uploadId) { // 后台异步处理 completeBatchUpload(result.uploadId, metadata).catch((err) => { console.error(`[Upload] 文件 ${item.file.name} 后台处理失败:`, err) onStatusUpdate({ status: 'failed', error: '后台处理失败' }) }) } return { success: true, uploadId: result?.uploadId } } else { // 小文件直接上传(使用XMLHttpRequest以便取消和显示进度) const formData = new FormData() formData.append('zipFile', item.file) formData.append('productModel', item.productModel) formData.append('categoryName', metadata.categoryName) formData.append('sceneName', metadata.sceneName) formData.append('platformName', metadata.platformName) formData.append('productCategoryName', metadata.productCategoryName) formData.append('materialType', materialType) // 统一处理renderer字段:根据素材类型判断是否需要传递 const rendererInfo = shouldPassRenderer(materialType, metadata.renderer) if (rendererInfo.shouldPass) { formData.append('renderer', rendererInfo.value) } // 小文件上传也使用压缩后的预览图 if (compressedPreview) { formData.append('previewImage', compressedPreview) } else if (item.previewImage) { // 如果没有压缩过(不需要预览图的类型),使用原图 formData.append('previewImage', item.previewImage) } const xhr = new XMLHttpRequest() onRequest(xhr) await new Promise((resolve, reject) => { xhr.upload.onprogress = (e) => { if (e.lengthComputable && !isCancelled()) { const progress = Math.round((e.loaded / e.total) * 100) const elapsed = (Date.now() - startTime) / 1000 const speed = e.loaded / elapsed onProgress(progress, speed) } } xhr.onload = () => { onRequest(null) if (xhr.status >= 200 && xhr.status < 300) { resolve() } else { reject(new Error(`上传失败: ${xhr.statusText}`)) } } xhr.onerror = () => { onRequest(null) reject(new Error('网络错误')) } xhr.onabort = () => { onRequest(null) reject(new Error('上传已取消')) } xhr.open('POST', '/api/materials/upload') xhr.send(formData) }) onStatusUpdate({ status: 'processing', progress: 100 }) return { success: true } } } catch (error: any) { console.error(`[Upload] 文件 ${item.file.name} 上传失败:`, error) const { message } = parseApiError(error) onStatusUpdate({ status: 'failed', error: message }) return { success: false, error: message } } }