"use client" import { useState, useRef, useEffect } from "react" import { Button } from "@/components/ui/button" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" import { Upload, X, Loader2, Crop } from "lucide-react" import { db } from "@/lib/db" import { v4 as uuidv4 } from "uuid" import ReactCrop, { type Crop as CropType } from "react-image-crop" import "react-image-crop/dist/ReactCrop.css" import imageCompression from "browser-image-compression" interface ImageUploadProps { value?: string // The image ID onChange: (imageId: string | undefined) => void className?: string } export function ImageUpload({ value, onChange, className }: ImageUploadProps) { const [previewUrl, setPreviewUrl] = useState(undefined) const [isLoading, setIsLoading] = useState(false) const [showCropDialog, setShowCropDialog] = useState(false) const [imageToCrop, setImageToCrop] = useState(null) const [crop, setCrop] = useState() const [completedCrop, setCompletedCrop] = useState() const imgRef = useRef(null) const fileInputRef = useRef(null) // Load preview if value (imageId) exists useEffect(() => { let objectUrl: string | undefined const loadPreview = async () => { if (!value) { setPreviewUrl(undefined) return } try { const image = await db.images.get(value) if (image) { objectUrl = URL.createObjectURL(image.blob) setPreviewUrl(objectUrl) } } catch (error) { console.error("Failed to load image:", error) } } loadPreview() return () => { if (objectUrl) URL.revokeObjectURL(objectUrl) } }, [value]) const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return try { // Basic validation if (!file.type.startsWith("image/")) { alert("请选择图片文件") return } // 压缩图片 const options = { maxSizeMB: 1, maxWidthOrHeight: 1024, useWebWorker: true, } const compressedFile = await imageCompression(file, options) // 显示裁剪对话框 const reader = new FileReader() reader.onload = () => { setImageToCrop(reader.result as string) setShowCropDialog(true) } reader.readAsDataURL(compressedFile) } catch (error) { console.error("Failed to process image:", error) alert("图片处理失败") } finally { // Reset input if (fileInputRef.current) fileInputRef.current.value = "" } } const handleCropComplete = async () => { if (!completedCrop || !imgRef.current) { // 如果没有裁剪,直接保存原图 await saveImage(imageToCrop!) return } try { setIsLoading(true) const canvas = document.createElement('canvas') const scaleX = imgRef.current.naturalWidth / imgRef.current.width const scaleY = imgRef.current.naturalHeight / imgRef.current.height canvas.width = completedCrop.width canvas.height = completedCrop.height const ctx = canvas.getContext('2d') if (ctx) { ctx.drawImage( imgRef.current, completedCrop.x * scaleX, completedCrop.y * scaleY, completedCrop.width * scaleX, completedCrop.height * scaleY, 0, 0, completedCrop.width, completedCrop.height ) } canvas.toBlob(async (blob) => { if (blob) { await saveImage(URL.createObjectURL(blob), blob) } }, 'image/jpeg', 0.9) } catch (error) { console.error("Failed to crop image:", error) alert("图片裁剪失败") } finally { setIsLoading(false) setShowCropDialog(false) setImageToCrop(null) } } const saveImage = async (dataUrl: string, blob?: Blob) => { try { setIsLoading(true) let imageBlob = blob if (!imageBlob) { const response = await fetch(dataUrl) imageBlob = await response.blob() } const imageId = uuidv4() await db.images.add({ id: imageId, blob: imageBlob, mimeType: imageBlob.type, createdAt: new Date().toISOString(), }) onChange(imageId) setShowCropDialog(false) setImageToCrop(null) } catch (error) { console.error("Failed to save image:", error) alert("图片保存失败") } finally { setIsLoading(false) } } const handleRemove = () => { onChange(undefined) } return ( <>
{isLoading ? : }
{value && ( )}
{/* 裁剪对话框 */} 裁剪图片
{imageToCrop && ( setCrop(c)} onComplete={(c) => setCompletedCrop(c)} aspect={1} circularCrop > 待裁剪图片 )}
) }