/** * 特殊类型批量上传 Hook * 封装音乐、视频、3D场景、品牌VI、买家秀、小红书的批量上传逻辑 */ import { useState, useCallback } from 'react' import { BATCH_LIMITS } from '@/components/upload/upload-constants' import { isValidMainFileType } from '@/components/upload/upload-utils' import { isBatchVideoMaterialType } from '@/lib/video-material-types' import { uploadBatchMusic, uploadBatchVideo, uploadBatch3D, uploadBatchBuyerShow, uploadBatchXiaohongshu, uploadBatchBrandVI, } from '@/components/upload/special-batch-uploaders' // 类型定义 export interface BatchFileWithSpu { file: File productModel: string } /** * 视频批量上传条目。 * 视频预览图是选填项,不能混入其它特殊类型的条目类型中。 */ export interface BatchVideoFile extends BatchFileWithSpu { productCategoryName?: string previewImage?: File previewImageUrl?: string } export interface Batch3DFile { file: File productModel: string previewImage?: File } export interface BatchImageGroup { productModel: string images: File[] } export interface UseSpecialBatchUploadOptions { onSuccess: () => void onClose: () => void } /** * 特殊类型批量上传 Hook */ export function useSpecialBatchUpload(options: UseSpecialBatchUploadOptions) { const { onSuccess, onClose } = options // 批量文件状态 const [batchMusicFiles, setBatchMusicFiles] = useState([]) const [batchVideoFiles, setBatchVideoFiles] = useState([]) const [batch3DFiles, setBatch3DFiles] = useState([]) const [batchBrandVIFiles, setBatchBrandVIFiles] = useState([]) const [batchBuyerShowFiles, setBatchBuyerShowFiles] = useState([]) const [batchXiaohongshuFiles, setBatchXiaohongshuFiles] = useState([]) // 上传状态 const [uploading, setUploading] = useState(false) const [uploadProgress, setUploadProgress] = useState(0) const [error, setError] = useState('') /** * 处理批量文件选择 */ const handleBatchFileSelect = useCallback(( e: React.ChangeEvent, materialType: string ) => { const files = Array.from(e.target.files || []) if (files.length === 0) return const validFiles = files.filter(f => isValidMainFileType(f, materialType)) if (validFiles.length === 0) { setError('请选择有效的素材文件') return } if (materialType === '音乐') { if (batchMusicFiles.length + validFiles.length > BATCH_LIMITS.music) { setError(`最多只能上传${BATCH_LIMITS.music}个音乐文件`) return } const newFiles = validFiles.map(file => ({ file, productModel: file.name.substring(0, file.name.lastIndexOf('.')) || '', })) setBatchMusicFiles(prev => [...prev, ...newFiles]) } else if (isBatchVideoMaterialType(materialType)) { if (batchVideoFiles.length + validFiles.length > BATCH_LIMITS.video) { setError(`最多只能上传${BATCH_LIMITS.video}个视频文件`) return } const newFiles = validFiles.map(file => ({ file, productModel: file.name.substring(0, file.name.lastIndexOf('.')) || '', })) setBatchVideoFiles(prev => [...prev, ...newFiles]) } else if (materialType === '3D场景') { if (batch3DFiles.length + validFiles.length > BATCH_LIMITS['3d']) { setError(`最多只能上传${BATCH_LIMITS['3d']}个3D场景文件`) return } const newFiles = validFiles.map(file => ({ file, productModel: file.name.substring(0, file.name.lastIndexOf('.')) || '', })) setBatch3DFiles(prev => [...prev, ...newFiles]) } else if (materialType === '品牌VI灵感') { if (batchBrandVIFiles.length + validFiles.length > BATCH_LIMITS.brandVI) { setError(`最多只能上传${BATCH_LIMITS.brandVI}个PDF文件`) return } const newFiles = validFiles.map(file => ({ file, productModel: file.name.substring(0, file.name.lastIndexOf('.')) || '', })) setBatchBrandVIFiles(prev => [...prev, ...newFiles]) } setError('') if (e.target) { e.target.value = '' } }, [batchMusicFiles.length, batchVideoFiles.length, batch3DFiles.length, batchBrandVIFiles.length]) /** * 买家秀批量图片选择 */ const handleBatchBuyerShowImageSelect = useCallback(( e: React.ChangeEvent, batchIndex: number ) => { const files = Array.from(e.target.files || []) if (files.length === 0) return const imageFiles = files.filter(f => f.type.startsWith('image/')) if (imageFiles.length === 0) { setError('请选择图片文件') return } setBatchBuyerShowFiles(prev => { const newFiles = [...prev] if (!newFiles[batchIndex]) { newFiles[batchIndex] = { productModel: '', images: [] } } newFiles[batchIndex].images = [...newFiles[batchIndex].images, ...imageFiles] return newFiles }) setError('') if (e.target) { e.target.value = '' } }, []) /** * 小红书批量图片选择 */ const handleBatchXiaohongshuImageSelect = useCallback(( e: React.ChangeEvent, batchIndex: number ) => { const files = Array.from(e.target.files || []) if (files.length === 0) return const imageFiles = files.filter(f => f.type.startsWith('image/')) if (imageFiles.length === 0) { setError('请选择图片文件') return } setBatchXiaohongshuFiles(prev => { const newFiles = [...prev] if (!newFiles[batchIndex]) { newFiles[batchIndex] = { productModel: '', images: [] } } newFiles[batchIndex].images = [...newFiles[batchIndex].images, ...imageFiles] return newFiles }) setError('') if (e.target) { e.target.value = '' } }, []) /** * 添加买家秀批次 */ const addBuyerShowBatch = useCallback(() => { setBatchBuyerShowFiles(prev => [...prev, { productModel: '', images: [] }]) }, []) /** * 添加小红书批次 */ const addXiaohongshuBatch = useCallback(() => { setBatchXiaohongshuFiles(prev => [...prev, { productModel: '', images: [] }]) }, []) /** * 批量上传音乐 */ const handleBatchMusicUpload = useCallback(async (platformName: string, renderer: string) => { if (batchMusicFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { await uploadBatchMusic(batchMusicFiles, platformName, renderer, (progress) => { setUploadProgress(progress) }) setUploadProgress(100) setUploading(false) setBatchMusicFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batchMusicFiles, onSuccess, onClose]) /** * 批量上传视频 */ const handleBatchVideoUpload = useCallback(async (platformName: string, renderer: string) => { if (batchVideoFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { const results = await uploadBatchVideo(batchVideoFiles, platformName, renderer, '', (progress) => { setUploadProgress(progress) }) const failedCount = results.filter((result) => result.status === 'failed').length if (failedCount > 0) { setError(`${failedCount} 个视频文件上传失败,请检查后重试`) setUploading(false) return } setUploadProgress(100) setUploading(false) batchVideoFiles.forEach((item) => { if (item.previewImageUrl) URL.revokeObjectURL(item.previewImageUrl) }) setBatchVideoFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batchVideoFiles, onSuccess, onClose]) /** * 批量上传3D场景 */ const handleBatch3DUpload = useCallback(async ( categoryName: string, sceneName: string, platformName: string, renderer: string ) => { if (batch3DFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { await uploadBatch3D(batch3DFiles, categoryName, sceneName, platformName, renderer, (progress) => { setUploadProgress(progress) }) setUploadProgress(100) setUploading(false) setBatch3DFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batch3DFiles, onSuccess, onClose]) /** * 批量上传品牌VI */ const handleBatchBrandVIUpload = useCallback(async (productCategoryName: string) => { if (batchBrandVIFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { await uploadBatchBrandVI(batchBrandVIFiles, productCategoryName, (progress) => { setUploadProgress(progress) }) setUploadProgress(100) setUploading(false) setBatchBrandVIFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batchBrandVIFiles, onSuccess, onClose]) /** * 批量上传买家秀 */ const handleBatchBuyerShowUpload = useCallback(async ( categoryName: string, sceneName: string, platformName: string, renderer: string ) => { if (batchBuyerShowFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { await uploadBatchBuyerShow(batchBuyerShowFiles, categoryName, sceneName, platformName, renderer, (progress) => { setUploadProgress(progress) }) setUploadProgress(100) setUploading(false) setBatchBuyerShowFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batchBuyerShowFiles, onSuccess, onClose]) /** * 批量上传小红书 */ const handleBatchXiaohongshuUpload = useCallback(async ( categoryName: string, sceneName: string, platformName: string, renderer: string ) => { if (batchXiaohongshuFiles.length === 0) return setUploading(true) setUploadProgress(0) setError('') try { await uploadBatchXiaohongshu(batchXiaohongshuFiles, categoryName, sceneName, platformName, renderer, (progress) => { setUploadProgress(progress) }) setUploadProgress(100) setUploading(false) setBatchXiaohongshuFiles([]) onSuccess() onClose() } catch (err) { console.error('[Upload] 批量上传失败:', err) setError('批量上传失败,请重试') setUploading(false) } }, [batchXiaohongshuFiles, onSuccess, onClose]) /** * 清空所有批量文件 */ const clearAllBatchFiles = useCallback(() => { setBatchMusicFiles([]) setBatchVideoFiles([]) setBatch3DFiles([]) setBatchBrandVIFiles([]) setBatchBuyerShowFiles([]) setBatchXiaohongshuFiles([]) }, []) return { // 批量文件状态 batchMusicFiles, batchVideoFiles, batch3DFiles, batchBrandVIFiles, batchBuyerShowFiles, batchXiaohongshuFiles, // 状态设置函数 setBatchMusicFiles, setBatchVideoFiles, setBatch3DFiles, setBatchBrandVIFiles, setBatchBuyerShowFiles, setBatchXiaohongshuFiles, // 上传状态 uploading, uploadProgress, error, setError, setUploading, setUploadProgress, // 操作函数 handleBatchFileSelect, handleBatchBuyerShowImageSelect, handleBatchXiaohongshuImageSelect, addBuyerShowBatch, addXiaohongshuBatch, handleBatchMusicUpload, handleBatchVideoUpload, handleBatch3DUpload, handleBatchBrandVIUpload, handleBatchBuyerShowUpload, handleBatchXiaohongshuUpload, clearAllBatchFiles, } }