gongxiangp2/components/upload/PreviewImageUpload.tsx

144 lines
5.0 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 Image from 'next/image'
import { PREVIEW_OPTIONAL_TYPES } from './upload-constants'
import {
dropZoneActiveClass,
dropZoneIdleClass,
fieldHintClass,
fieldLabelClass,
} from './upload-field-styles'
import VideoPreviewImageUpload from './VideoPreviewImageUpload'
import { isVideoMaterialType } from '@/lib/video-material-types'
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
onVideoPreviewSelect: (file: File) => void | Promise<void>
onValidationError: (message: string) => void
onVideoPreviewProcessingChange?: (processing: boolean) => void
}
export default function PreviewImageUpload({
materialType,
previewImages,
previewUrls,
isPreviewDragging,
previewInputRef,
previewDropZoneRef,
disabled = false,
onPreviewChange,
onPreviewDragEnter,
onPreviewDragLeave,
onPreviewDragOver,
onPreviewDrop,
onRemovePreviewImage,
onVideoPreviewSelect,
onValidationError,
onVideoPreviewProcessingChange,
}: PreviewImageUploadProps) {
if (isVideoMaterialType(materialType)) {
return (
<VideoPreviewImageUpload
previewUrl={previewUrls[0]}
fileName={previewImages[0]?.name}
fileSize={previewImages[0]?.size}
disabled={disabled}
onFileSelect={onVideoPreviewSelect}
onRemove={() => onRemovePreviewImage(0)}
onValidationError={onValidationError}
onProcessingChange={onVideoPreviewProcessingChange}
/>
)
}
return (
<div>
<label className={fieldLabelClass}>
预览图片
{PREVIEW_OPTIONAL_TYPES.includes(materialType) ? '(可选)' : ' *'}
</label>
<div
ref={previewDropZoneRef}
aria-disabled={disabled}
className={`p-4 ${
disabled ? 'cursor-not-allowed opacity-60' : ''
} ${isPreviewDragging ? dropZoneActiveClass : dropZoneIdleClass}`}
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-2 gap-4 md:grid-cols-4">
{previewUrls.map((url, index) => (
<div key={index} className="group relative">
<div className="relative h-32 w-full overflow-hidden rounded-[8px] border border-[#e7e8ea]">
<Image
src={url}
alt={`预览图 ${index + 1}`}
fill
unoptimized
className="object-cover"
/>
{index === 0 && (
<div className="absolute left-1 top-1 bg-[#1a1c1d] px-2 py-0.5 text-[11px] tracking-[-0.02em] text-white">
主预览图
</div>
)}
</div>
<button
type="button"
onClick={() => onRemovePreviewImage(index)}
disabled={disabled}
aria-label={`移除预览图 ${index + 1}`}
className="absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full bg-[#1a1c1d] text-white opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70"
>
×
</button>
</div>
))}
<button
type="button"
onClick={() => previewInputRef.current?.click()}
disabled={disabled}
className="group flex h-32 w-full items-center justify-center rounded-[8px] border border-dashed border-[#e7e8ea] bg-white transition-colors hover:border-[#1a1c1d] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70 focus-visible:ring-offset-2"
>
<svg className="h-8 w-8 text-[#86898e] transition-colors group-hover:text-[#1a1c1d]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4v16m8-8H4" />
</svg>
</button>
</div>
<div className="mt-3 space-y-1">
<p className={fieldHintClass}>
支持 JPG、PNG、WebP,可一次选多张或拖进来。
</p>
<p className={fieldHintClass}>
第一张图会作为主预览图。
</p>
</div>
</div>
</div>
)
}