118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
'use client'
|
||
|
||
import Image from 'next/image'
|
||
import { PREVIEW_OPTIONAL_TYPES } from './upload-constants'
|
||
|
||
interface PreviewImageUploadProps {
|
||
materialType: string
|
||
previewImages: File[]
|
||
previewUrls: string[]
|
||
isPreviewDragging: boolean
|
||
previewInputRef: React.RefObject<HTMLInputElement>
|
||
previewDropZoneRef: React.RefObject<HTMLDivElement>
|
||
disabled?: boolean
|
||
onPreviewChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||
onPreviewDragEnter: (e: React.DragEvent) => void
|
||
onPreviewDragLeave: (e: React.DragEvent) => void
|
||
onPreviewDragOver: (e: React.DragEvent) => void
|
||
onPreviewDrop: (e: React.DragEvent) => void
|
||
onRemovePreviewImage: (index: number) => void
|
||
}
|
||
|
||
export default function PreviewImageUpload({
|
||
materialType,
|
||
previewImages,
|
||
previewUrls,
|
||
isPreviewDragging,
|
||
previewInputRef,
|
||
previewDropZoneRef,
|
||
disabled = false,
|
||
onPreviewChange,
|
||
onPreviewDragEnter,
|
||
onPreviewDragLeave,
|
||
onPreviewDragOver,
|
||
onPreviewDrop,
|
||
onRemovePreviewImage,
|
||
}: PreviewImageUploadProps) {
|
||
return (
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
预览图片
|
||
{PREVIEW_OPTIONAL_TYPES.includes(materialType) ? '(可选)' : ' *'}
|
||
</label>
|
||
<div
|
||
ref={previewDropZoneRef}
|
||
aria-disabled={disabled}
|
||
className={`border-2 border-dashed rounded-apple p-4 transition-colors ${
|
||
disabled ? 'cursor-not-allowed opacity-60' : ''
|
||
} ${
|
||
isPreviewDragging
|
||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||
: 'border-gray-300 dark:border-gray-600'
|
||
}`}
|
||
onDragEnter={onPreviewDragEnter}
|
||
onDragLeave={onPreviewDragLeave}
|
||
onDragOver={onPreviewDragOver}
|
||
onDrop={onPreviewDrop}
|
||
>
|
||
<input
|
||
ref={previewInputRef}
|
||
type="file"
|
||
onChange={onPreviewChange}
|
||
accept="image/*"
|
||
multiple
|
||
className="hidden"
|
||
disabled={disabled}
|
||
/>
|
||
<div className="grid grid-cols-4 gap-4">
|
||
{previewUrls.map((url, index) => (
|
||
<div key={index} className="relative group">
|
||
<div className="relative w-full h-32 rounded-apple overflow-hidden border-2 border-gray-200 dark:border-gray-600">
|
||
<Image
|
||
src={url}
|
||
alt={`预览图 ${index + 1}`}
|
||
fill
|
||
unoptimized
|
||
className="object-cover"
|
||
/>
|
||
{index === 0 && (
|
||
<div className="absolute top-1 left-1 bg-blue-500 text-white text-xs px-2 py-1 rounded">
|
||
主预览图
|
||
</div>
|
||
)}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => onRemovePreviewImage(index)}
|
||
disabled={disabled}
|
||
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button
|
||
type="button"
|
||
onClick={() => previewInputRef.current?.click()}
|
||
disabled={disabled}
|
||
className="group w-full h-32 rounded-apple bg-gray-100 dark:bg-gray-700 border-2 border-dashed border-gray-300 dark:border-gray-600 flex items-center justify-center hover:border-gray-400 dark:hover:border-gray-500 transition-colors"
|
||
>
|
||
<svg className="w-12 h-12 text-gray-400 group-hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div className="mt-3 space-y-1">
|
||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||
支持 JPG、PNG、WebP 等格式,可一次选择多张或拖拽上传
|
||
</p>
|
||
<p className="text-xs text-blue-600 dark:text-blue-400">
|
||
💡 第一张图会自动作为主预览图
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|