gongxiangp2/components/upload/ModelImagesUpload.tsx

110 lines
4.2 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.

'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>
)
}