gongxiangp2/components/upload/PreviewImageUpload.tsx

141 lines
4.7 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 { Plus } from 'lucide-react'
import { PREVIEW_OPTIONAL_TYPES } from './upload-constants'
import {
dropZoneActiveClass,
dropZoneIdleClass,
fieldHintClass,
fieldLabelClass,
uploadAreaAddTileClass,
uploadAreaEmptyButtonClass,
uploadAreaTileClass,
} 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-3 ${
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={previewUrls.length > 0 ? 'flex flex-wrap gap-2' : ''}>
{previewUrls.map((url, index) => (
<div key={index} className="group relative">
<div className={uploadAreaTileClass}>
<Image
src={url}
alt={`预览图 ${index + 1}`}
fill
unoptimized
className="object-cover"
/>
{index === 0 && (
<div className="absolute bottom-0 inset-x-0 bg-[#1a1c1d]/80 py-0.5 text-center text-xs text-white">
主图
</div>
)}
</div>
<button
type="button"
onClick={() => onRemovePreviewImage(index)}
disabled={disabled}
aria-label={`移除预览图 ${index + 1}`}
className="absolute right-1 top-1 flex h-7 w-7 items-center justify-center rounded-[6px] bg-[#1a1c1d]/80 text-white opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff]"
>
×
</button>
</div>
))}
<button
type="button"
onClick={() => previewInputRef.current?.click()}
disabled={disabled}
className={previewUrls.length > 0 ? uploadAreaAddTileClass : uploadAreaEmptyButtonClass}
aria-label="添加预览图片"
>
<Plus className="h-5 w-5" aria-hidden="true" />
{previewUrls.length === 0 && <span>添加预览图片</span>}
</button>
</div>
</div>
<p className={`mt-1.5 ${fieldHintClass}`}>JPG、PNG、WebP · 可多选或拖入 · 首张为主预览图</p>
</div>
)
}