gongxiangp2/components/upload/SpecialBatchUploadUI.tsx
chenpei 0ce59be34e 备份:组织架构设计方案讨论前的代码快照
- 包含所有当前修改和新功能
- 准备开始组织架构优化实施方案
2025-12-30 13:20:26 +08:00

412 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 特殊批量上传UI组件
* 包含音乐、视频、3D场景、品牌VI、买家秀、小红书的批量上传界面
*/
'use client'
import Image from 'next/image'
import { formatBytes } from '@/components/upload/upload-utils'
/**
* 音乐/视频/品牌VI 批量文件项
*/
export interface BatchFileWithSpu {
file: File
productModel: string
}
/**
* 3D场景批量文件项(带预览图)
*/
export interface Batch3DFile {
file: File
productModel: string
previewImage?: File
}
/**
* 买家秀/小红书批量文件项(多图)
*/
export interface BatchImageGroup {
productModel: string
images: File[]
}
// ========== 音乐批量上传列表 ==========
interface BatchMusicListProps {
files: BatchFileWithSpu[]
onUpdateProductModel: (index: number, value: string) => void
onDelete: (index: number) => void
}
export function BatchMusicList({ files, onUpdateProductModel, onDelete }: BatchMusicListProps) {
if (files.length === 0) return null
return (
<div className="mb-4 space-y-3 max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-apple p-4">
{files.map((item, index) => (
<div key={index} className="flex items-center space-x-3 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
{item.file.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{formatBytes(item.file.size)}
</p>
</div>
<input
type="text"
value={item.productModel}
onChange={(e) => onUpdateProductModel(index, e.target.value)}
className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号"
/>
<button
type="button"
onClick={() => onDelete(index)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除
</button>
</div>
))}
</div>
)
}
// ========== 视频批量上传列表 ==========
interface BatchVideoListProps {
files: BatchFileWithSpu[]
onUpdateProductModel: (index: number, value: string) => void
onDelete: (index: number) => void
}
export function BatchVideoList({ files, onUpdateProductModel, onDelete }: BatchVideoListProps) {
if (files.length === 0) return null
return (
<div className="mb-4 space-y-3 max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-apple p-4">
{files.map((item, index) => (
<div key={index} className="flex items-center space-x-3 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
{item.file.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{formatBytes(item.file.size)}
</p>
</div>
<input
type="text"
value={item.productModel}
onChange={(e) => onUpdateProductModel(index, e.target.value)}
className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号"
/>
<button
type="button"
onClick={() => onDelete(index)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除
</button>
</div>
))}
</div>
)
}
// ========== 3D场景批量上传列表 ==========
interface Batch3DListProps {
files: Batch3DFile[]
onUpdateProductModel: (index: number, value: string) => void
onUpdatePreviewImage: (index: number, file: File) => void
onDelete: (index: number) => void
}
export function Batch3DList({ files, onUpdateProductModel, onUpdatePreviewImage, onDelete }: Batch3DListProps) {
if (files.length === 0) return null
return (
<div className="mb-4 space-y-3 max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-apple p-4">
{files.map((item, index) => (
<div key={index} className="flex items-center space-x-3 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
{item.file.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{formatBytes(item.file.size)}
</p>
</div>
<input
type="text"
value={item.productModel}
onChange={(e) => onUpdateProductModel(index, e.target.value)}
className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号"
/>
<input
type="file"
accept="image/*"
onChange={(e) => {
const file = e.target.files?.[0]
if (file) {
onUpdatePreviewImage(index, file)
}
}}
className="hidden"
id={`3d-preview-${index}`}
/>
<label
htmlFor={`3d-preview-${index}`}
className="text-xs text-blue-600 dark:text-blue-400 cursor-pointer"
>
{item.previewImage ? '已选预览图' : '选择预览图'}
</label>
<button
type="button"
onClick={() => onDelete(index)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除
</button>
</div>
))}
</div>
)
}
// ========== 品牌VI批量上传列表 ==========
interface BatchBrandVIListProps {
files: BatchFileWithSpu[]
onUpdateProductModel: (index: number, value: string) => void
onDelete: (index: number) => void
}
export function BatchBrandVIList({ files, onUpdateProductModel, onDelete }: BatchBrandVIListProps) {
if (files.length === 0) return null
return (
<div className="mb-4 space-y-3 max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-apple p-4">
{files.map((item, index) => (
<div key={index} className="flex items-center space-x-3 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
{item.file.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{formatBytes(item.file.size)}
</p>
</div>
<input
type="text"
value={item.productModel}
onChange={(e) => onUpdateProductModel(index, e.target.value)}
className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号"
/>
<button
type="button"
onClick={() => onDelete(index)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除
</button>
</div>
))}
</div>
)
}
// ========== 买家秀批量上传UI ==========
interface BatchBuyerShowUIProps {
batches: BatchImageGroup[]
onUpdateProductModel: (batchIndex: number, value: string) => void
onImageSelect: (e: React.ChangeEvent<HTMLInputElement>, batchIndex: number) => void
onRemoveImage: (batchIndex: number, imageIndex: number) => void
onDeleteBatch: (batchIndex: number) => void
onAddBatch: () => void
}
export function BatchBuyerShowUI({
batches,
onUpdateProductModel,
onImageSelect,
onRemoveImage,
onDeleteBatch,
onAddBatch,
}: BatchBuyerShowUIProps) {
return (
<div className="mb-4 space-y-4">
{batches.map((batch, batchIndex) => (
<div key={batchIndex} className="border border-gray-200 dark:border-gray-700 rounded-apple p-4 bg-gray-50 dark:bg-gray-800">
<div className="flex items-center space-x-3 mb-3">
<input
type="text"
value={batch.productModel}
onChange={(e) => onUpdateProductModel(batchIndex, e.target.value)}
className="flex-1 px-3 py-2 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号 *"
/>
<input
type="file"
accept="image/*"
multiple
onChange={(e) => onImageSelect(e, batchIndex)}
className="hidden"
id={`buyer-show-images-${batchIndex}`}
/>
<label
htmlFor={`buyer-show-images-${batchIndex}`}
className="btn-secondary text-sm"
>
选择图片(至少8张)
</label>
<button
type="button"
onClick={() => onDeleteBatch(batchIndex)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除批次
</button>
</div>
{batch.images.length > 0 && (
<div className="grid grid-cols-4 gap-2 mt-3">
{batch.images.map((img, imgIndex) => (
<div key={imgIndex} className="relative group">
<div className="relative w-full h-24 rounded-apple overflow-hidden border-2 border-gray-200 dark:border-gray-600">
<Image
src={URL.createObjectURL(img)}
alt={`图片 ${imgIndex + 1}`}
fill
unoptimized
className="object-cover"
/>
</div>
<button
type="button"
onClick={() => onRemoveImage(batchIndex, imgIndex)}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity text-xs"
>
×
</button>
</div>
))}
</div>
)}
{batch.images.length > 0 && batch.images.length < 8 && (
<p className="text-xs text-red-600 dark:text-red-400 mt-2">
每批次至少需要8张图片(当前:{batch.images.length}张)
</p>
)}
</div>
))}
<button
type="button"
onClick={onAddBatch}
className="btn-secondary text-sm"
>
+ 添加新批次
</button>
</div>
)
}
// ========== 小红书批量上传UI ==========
interface BatchXiaohongshuUIProps {
batches: BatchImageGroup[]
onUpdateProductModel: (batchIndex: number, value: string) => void
onImageSelect: (e: React.ChangeEvent<HTMLInputElement>, batchIndex: number) => void
onRemoveImage: (batchIndex: number, imageIndex: number) => void
onDeleteBatch: (batchIndex: number) => void
onAddBatch: () => void
}
export function BatchXiaohongshuUI({
batches,
onUpdateProductModel,
onImageSelect,
onRemoveImage,
onDeleteBatch,
onAddBatch,
}: BatchXiaohongshuUIProps) {
return (
<div className="mb-4 space-y-4">
{batches.map((batch, batchIndex) => (
<div key={batchIndex} className="border border-gray-200 dark:border-gray-700 rounded-apple p-4 bg-gray-50 dark:bg-gray-800">
<div className="flex items-center space-x-3 mb-3">
<input
type="text"
value={batch.productModel}
onChange={(e) => onUpdateProductModel(batchIndex, e.target.value)}
className="flex-1 px-3 py-2 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="SPU型号 *"
/>
<input
type="file"
accept="image/*"
multiple
onChange={(e) => onImageSelect(e, batchIndex)}
className="hidden"
id={`xiaohongshu-images-${batchIndex}`}
/>
<label
htmlFor={`xiaohongshu-images-${batchIndex}`}
className="btn-secondary text-sm"
>
选择图片
</label>
<button
type="button"
onClick={() => onDeleteBatch(batchIndex)}
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm"
>
删除批次
</button>
</div>
{batch.images.length > 0 && (
<div className="grid grid-cols-4 gap-2 mt-3">
{batch.images.map((img, imgIndex) => (
<div key={imgIndex} className="relative group">
<div className="relative w-full h-24 rounded-apple overflow-hidden border-2 border-gray-200 dark:border-gray-600">
<Image
src={URL.createObjectURL(img)}
alt={`图片 ${imgIndex + 1}`}
fill
unoptimized
className="object-cover"
/>
</div>
<button
type="button"
onClick={() => onRemoveImage(batchIndex, imgIndex)}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity text-xs"
>
×
</button>
</div>
))}
</div>
)}
</div>
))}
<button
type="button"
onClick={onAddBatch}
className="btn-secondary text-sm"
>
+ 添加新批次
</button>
</div>
)
}