110 lines
4.2 KiB
TypeScript
110 lines
4.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import Image from 'next/image'
|
||
import { handleDropZoneKeyDown, hasDragLeftDropZone, prepareFileDrag } from './drag-drop-utils'
|
||
|
||
interface ModelImagesUploadProps {
|
||
modelImageUrls: string[]
|
||
onModelImagesChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||
onModelImagesDrop: (files: File[]) => void
|
||
onRemoveModelImage: (index: number) => void
|
||
}
|
||
|
||
export default function ModelImagesUpload({
|
||
modelImageUrls,
|
||
onModelImagesChange,
|
||
onModelImagesDrop,
|
||
onRemoveModelImage,
|
||
}: ModelImagesUploadProps) {
|
||
const [isDragging, setIsDragging] = useState(false)
|
||
|
||
return (
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
模特展示图片 * (可上传多张)
|
||
</label>
|
||
<div
|
||
role="group"
|
||
tabIndex={0}
|
||
aria-label="上传模特展示图片"
|
||
className={`border-2 border-dashed rounded-apple p-4 transition-colors border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500/40 ${
|
||
isDragging ? 'border-blue-500 bg-blue-50/70 dark:border-blue-400 dark:bg-blue-900/20' : ''
|
||
}`}
|
||
onDragEnter={(event) => {
|
||
prepareFileDrag(event)
|
||
setIsDragging(true)
|
||
}}
|
||
onDragLeave={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
if (hasDragLeftDropZone(event)) setIsDragging(false)
|
||
}}
|
||
onDragOver={prepareFileDrag}
|
||
onDrop={(event) => {
|
||
prepareFileDrag(event)
|
||
setIsDragging(false)
|
||
onModelImagesDrop(Array.from(event.dataTransfer.files || []))
|
||
}}
|
||
onKeyDown={(event) =>
|
||
handleDropZoneKeyDown(event, () => document.getElementById('modelImagesInput')?.click())
|
||
}
|
||
>
|
||
<input
|
||
type="file"
|
||
onChange={onModelImagesChange}
|
||
accept="image/*"
|
||
multiple
|
||
className="hidden"
|
||
id="modelImagesInput"
|
||
/>
|
||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
|
||
{modelImageUrls.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="bg-gray-50 object-contain dark:bg-gray-800"
|
||
/>
|
||
{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={() => onRemoveModelImage(index)}
|
||
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100 focus:opacity-100"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button
|
||
type="button"
|
||
onClick={() => document.getElementById('modelImagesInput')?.click()}
|
||
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">
|
||
可一次选择多张图片,也可以直接拖拽到这里
|
||
</p>
|
||
<p className="text-xs text-blue-600 dark:text-blue-400">
|
||
💡 第一张图会自动作为预览图,高度超过1500px会自动切割
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|