446 lines
12 KiB
TypeScript
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,
|
|
}
|
|
}
|