"use client" import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { X, Upload, Image as ImageIcon, Plus, ZoomIn, Edit2 } from "lucide-react" import { useDialog } from "@/components/ui/alert-dialog-custom" import imageCompression from "browser-image-compression" import type { FamilyPhoto } from "@/types/family" import { format } from "date-fns" import { zhCN } from "date-fns/locale" interface PhotoGalleryProps { photos?: FamilyPhoto[] photoIds?: string[] // 兼容旧格式 onChange: (photos: FamilyPhoto[]) => Promise | void readonly?: boolean } export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange, readonly = false }: PhotoGalleryProps) { const { showAlert, showConfirm, showPrompt } = useDialog() const [photos, setPhotos] = useState([]) const [selectedPhoto, setSelectedPhoto] = useState(null) const [isUploading, setIsUploading] = useState(false) const [editingPhoto, setEditingPhoto] = useState(null) const [editCaption, setEditCaption] = useState("") const [pendingVisibilityUrl, setPendingVisibilityUrl] = useState(null) // 加载照片 - 兼容新旧格式 useEffect(() => { // 优先使用新格式 photos if (propPhotos && propPhotos.length > 0) { const normalized = propPhotos.map(photo => ({ ...photo, visibleInOverview: photo.visibleInOverview ?? false, adminVisibleOverride: photo.adminVisibleOverride ?? true, })) setPhotos(normalized) } else if (photoIds && photoIds.length > 0) { // 兼容旧格式:将 string[] 转换为 FamilyPhoto[] const convertedPhotos: FamilyPhoto[] = photoIds.map(url => ({ url, uploadedAt: new Date().toISOString(), // 旧数据没有时间,用当前时间 visibleInOverview: false, adminVisibleOverride: true, })) setPhotos(convertedPhotos) } else { setPhotos([]) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(propPhotos), JSON.stringify(photoIds)]) const handleUpload = async (e: React.ChangeEvent) => { const files = e.target.files if (!files || files.length === 0) return setIsUploading(true) try { const newPhotos: FamilyPhoto[] = [] for (const file of Array.from(files)) { if (!file.type.startsWith('image/')) continue // 压缩图片 const options = { maxSizeMB: 2, maxWidthOrHeight: 1920, useWebWorker: true, } const compressedFile = await imageCompression(file, options) // 上传到服务器 const formData = new FormData() formData.append('file', compressedFile) const response = await fetch('/api/upload', { method: 'POST', body: formData, }) if (!response.ok) { throw new Error('上传失败') } const { url } = await response.json() newPhotos.push({ url, uploadedAt: new Date().toISOString(), visibleInOverview: false, adminVisibleOverride: true, }) } // 更新照片列表 onChange([...photos, ...newPhotos]) } catch (error) { console.error('上传照片失败:', error) await showAlert('上传照片失败', '错误') } finally { setIsUploading(false) e.target.value = '' } } const handleDelete = async (photo: FamilyPhoto) => { const confirmed = await showConfirm('确定要删除这张照片吗?', '删除照片') if (confirmed) { onChange(photos.filter(p => p.url !== photo.url)) } } const handleToggleOverview = async (photo: FamilyPhoto, value: boolean) => { if (readonly) return setPendingVisibilityUrl(photo.url) const previous = photos const updatedPhotos = photos.map(p => p.url === photo.url ? { ...p, visibleInOverview: value } : p ) setPhotos(updatedPhotos) try { await onChange(updatedPhotos) } catch (error) { console.error('更新照片展示状态失败:', error) setPhotos(previous) await showAlert('更新照片展示状态失败,请重试', '错误') } finally { setPendingVisibilityUrl(null) } } const handleEditCaption = (photo: FamilyPhoto) => { setEditingPhoto(photo) setEditCaption(photo.caption || "") } const handleSaveCaption = async () => { if (!editingPhoto) return const updatedPhotos = photos.map(p => p.url === editingPhoto.url ? { ...p, caption: editCaption.trim() || undefined } : p ) try { await onChange(updatedPhotos) setEditingPhoto(null) setEditCaption("") } catch (error) { console.error("保存说明失败:", error) await showAlert("保存说明失败,请重试", "错误") } } return (

照片墙 ({photos.length})

{!readonly && ( )}
{photos.length > 0 ? (
{photos.map((photo, index) => (
{photo.caption setSelectedPhoto(photo)} />
{/* 照片信息区域 */}
{format(new Date(photo.uploadedAt), 'yyyy-MM-dd', { locale: zhCN })}
{photo.caption && (
{photo.caption}
)}
展示到总览 handleToggleOverview(photo, checked)} disabled={readonly || pendingVisibilityUrl === photo.url} />
{/* 操作按钮 */} {!readonly && ( <> )}
))}
) : (
暂无照片
)} {/* 照片预览对话框 */} setSelectedPhoto(null)}> {selectedPhoto?.caption || "照片预览"} 家族照片预览弹窗,可查看大图与上传时间 {selectedPhoto && (
{selectedPhoto.caption
上传于 {format(new Date(selectedPhoto.uploadedAt), 'yyyy年MM月dd日', { locale: zhCN })}
)}
{/* 编辑说明对话框 */} setEditingPhoto(null)}> 编辑照片说明 修改当前照片的备注文字并保存
setEditCaption(e.target.value)} placeholder="输入照片说明..." maxLength={200} />

{editCaption.length}/200

) }