This commit is contained in:
freedakgmail
2025-11-30 23:09:54 +08:00
parent 96aa886adc
commit debcce71bf
150 changed files with 431 additions and 321 deletions
+50 -7
View File
@@ -2,10 +2,10 @@
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
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"
@@ -27,17 +27,25 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
const [isUploading, setIsUploading] = useState(false)
const [editingPhoto, setEditingPhoto] = useState<FamilyPhoto | null>(null)
const [editCaption, setEditCaption] = useState("")
const [pendingVisibilityUrl, setPendingVisibilityUrl] = useState<string | null>(null)
// 加载照片 - 兼容新旧格式
useEffect(() => {
// 优先使用新格式 photos
if (propPhotos && propPhotos.length > 0) {
setPhotos(propPhotos)
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() // 旧数据没有时间,用当前时间
uploadedAt: new Date().toISOString(), // 旧数据没有时间,用当前时间
visibleInOverview: false,
adminVisibleOverride: true,
}))
setPhotos(convertedPhotos)
} else {
@@ -82,7 +90,9 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
const { url } = await response.json()
newPhotos.push({
url,
uploadedAt: new Date().toISOString()
uploadedAt: new Date().toISOString(),
visibleInOverview: false,
adminVisibleOverride: true,
})
}
@@ -104,6 +114,25 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
}
}
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 || "")
@@ -177,6 +206,14 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
{photo.caption && (
<div className="text-foreground mt-1 line-clamp-2">{photo.caption}</div>
)}
<div className="mt-2 flex items-center justify-between gap-2 text-[11px] text-muted-foreground">
<span></span>
<Switch
checked={photo.visibleInOverview ?? false}
onCheckedChange={(checked) => handleToggleOverview(photo, checked)}
disabled={readonly || pendingVisibilityUrl === photo.url}
/>
</div>
</div>
{/* 操作按钮 */}
{!readonly && (
@@ -222,11 +259,14 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
{/* 照片预览对话框 */}
<Dialog open={!!selectedPhoto} onOpenChange={() => setSelectedPhoto(null)}>
<DialogContent className="max-w-4xl" aria-describedby={undefined}>
<DialogContent className="max-w-4xl" aria-describedby="photo-preview-desc">
<DialogHeader>
<DialogTitle>
{selectedPhoto?.caption || "照片预览"}
</DialogTitle>
<DialogDescription id="photo-preview-desc" className="sr-only">
</DialogDescription>
</DialogHeader>
{selectedPhoto && (
<div className="space-y-2">
@@ -245,9 +285,12 @@ export function PhotoGallery({ photos: propPhotos = [], photoIds = [], onChange,
{/* 编辑说明对话框 */}
<Dialog open={!!editingPhoto} onOpenChange={() => setEditingPhoto(null)}>
<DialogContent aria-describedby={undefined}>
<DialogContent aria-describedby="photo-edit-desc">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription id="photo-edit-desc" className="sr-only">
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">