gongxiangp2/hooks/useSpecialBatchUpload.ts
2026-08-07 23:28:55 +08:00

446 lines
12 KiB
TypeScript

/**
* 特殊类型批量上传 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<BatchFileWithSpu[]>([])
const [batchVideoFiles, setBatchVideoFiles] = useState<BatchVideoFile[]>([])
const [batch3DFiles, setBatch3DFiles] = useState<Batch3DFile[]>([])
const [batchBrandVIFiles, setBatchBrandVIFiles] = useState<BatchFileWithSpu[]>([])
const [batchBuyerShowFiles, setBatchBuyerShowFiles] = useState<BatchImageGroup[]>([])
const [batchXiaohongshuFiles, setBatchXiaohongshuFiles] = useState<BatchImageGroup[]>([])
// 上传状态
const [uploading, setUploading] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const [error, setError] = useState('')
/**
* 处理批量文件选择
*/
const handleBatchFileSelect = useCallback((
e: React.ChangeEvent<HTMLInputElement>,
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<HTMLInputElement>,
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<HTMLInputElement>,
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,
}
}