diff --git a/apps/desktop/src/components/ImagePreviewModal.tsx b/apps/desktop/src/components/ImagePreviewModal.tsx index ada6d34..c4a03d7 100644 --- a/apps/desktop/src/components/ImagePreviewModal.tsx +++ b/apps/desktop/src/components/ImagePreviewModal.tsx @@ -11,7 +11,10 @@ import { MapPin, User, Shirt, - AlertCircle + AlertCircle, + ChevronLeft, + ChevronRight, + DownloadCloud } from 'lucide-react'; import { GroundingSource } from '../types/ragGrounding'; @@ -27,6 +30,12 @@ interface ImagePreviewModalProps { onClose: () => void; /** 下载回调 */ onDownload?: (source: GroundingSource) => void; + /** 所有图片列表 */ + images?: string[]; + /** 当前图片索引 */ + currentIndex?: number; + /** 导航回调 */ + onNavigate?: (index: number) => void; } /** @@ -37,7 +46,10 @@ export const ImagePreviewModal: React.FC = ({ isOpen, source, onClose, - onDownload + onDownload, + images = [], + currentIndex = 0, + onNavigate }) => { const [imageLoaded, setImageLoaded] = useState(false); const [imageError, setImageError] = useState(false); @@ -45,6 +57,83 @@ export const ImagePreviewModal: React.FC = ({ const [rotation, setRotation] = useState(0); const [isDownloading, setIsDownloading] = useState(false); + // 切换到上一张图片 + const handlePrevious = useCallback(() => { + if (images.length > 1 && onNavigate) { + const newIndex = currentIndex > 0 ? currentIndex - 1 : images.length - 1; + onNavigate(newIndex); + } + }, [images, currentIndex, onNavigate]); + + // 切换到下一张图片 + const handleNext = useCallback(() => { + if (images.length > 1 && onNavigate) { + const newIndex = currentIndex < images.length - 1 ? currentIndex + 1 : 0; + onNavigate(newIndex); + } + }, [images, currentIndex, onNavigate]); + + // 批量下载所有图片 + const handleBatchDownload = useCallback(async () => { + if (!images.length || !onDownload) return; + + setIsDownloading(true); + try { + for (let i = 0; i < images.length; i++) { + const imageSource: GroundingSource = { + uri: images[i], + title: `穿搭图片 ${i + 1}`, + content: { description: '穿搭生成图片' } + }; + await onDownload(imageSource); + // 添加小延迟避免过快下载 + await new Promise(resolve => setTimeout(resolve, 500)); + } + } catch (error) { + console.error('批量下载失败:', error); + } finally { + setIsDownloading(false); + } + }, [images, onDownload]); + + // 键盘事件处理 + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (!isOpen) return; + + switch (event.key) { + case 'Escape': + onClose(); + break; + case 'ArrowLeft': + event.preventDefault(); + handlePrevious(); + break; + case 'ArrowRight': + event.preventDefault(); + handleNext(); + break; + case '+': + case '=': + event.preventDefault(); + setZoom(prev => Math.min(prev + 0.25, 3)); + break; + case '-': + event.preventDefault(); + setZoom(prev => Math.max(prev - 0.25, 0.25)); + break; + case 'r': + case 'R': + event.preventDefault(); + setRotation(prev => prev + 90); + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [isOpen, onClose, handlePrevious, handleNext]); + // 重置状态当模态框打开时 useEffect(() => { if (isOpen) { @@ -165,6 +254,29 @@ export const ImagePreviewModal: React.FC = ({
+ {/* 图片导航 */} + {images.length > 1 && ( +
+ + + {currentIndex + 1}/{images.length} + + +
+ )} + {/* 缩放控制 */}
+ <> + + + {/* 批量下载按钮 */} + {images.length > 1 && ( + + )} + )} {/* 外部链接 */} @@ -236,7 +362,7 @@ export const ImagePreviewModal: React.FC = ({ {/* 图片展示区域 */}
{imageUri && !imageError ? ( -
+
{description = ({ const [previewModal, setPreviewModal] = useState<{ isOpen: boolean; source: GroundingSource | null; + images: string[]; + currentIndex: number; }>({ isOpen: false, - source: null + source: null, + images: [], + currentIndex: 0 }); // 删除确认对话框状态 @@ -102,8 +106,8 @@ export const OutfitImageGallery: React.FC = ({ }, []); // 打开图片预览 - const openImagePreview = useCallback((imageUrl: string, title: string) => { - console.log('🖼️ 打开图片预览:', { imageUrl, title }); + const openImagePreview = useCallback((imageUrl: string, title: string, allImages: string[] = [], currentIndex: number = 0) => { + console.log('🖼️ 打开图片预览:', { imageUrl, title, allImages, currentIndex }); const source: GroundingSource = { uri: imageUrl, title: title, @@ -111,16 +115,20 @@ export const OutfitImageGallery: React.FC = ({ }; setPreviewModal({ isOpen: true, - source + source, + images: allImages, + currentIndex }); - console.log('🖼️ 预览模态框状态已更新:', { isOpen: true, source }); + console.log('🖼️ 预览模态框状态已更新:', { isOpen: true, source, images: allImages, currentIndex }); }, []); // 关闭图片预览 const closeImagePreview = useCallback(() => { setPreviewModal({ isOpen: false, - source: null + source: null, + images: [], + currentIndex: 0 }); }, []); @@ -334,24 +342,28 @@ export const OutfitImageGallery: React.FC = ({ {record.status === OutfitImageStatus.Completed && record.outfit_images.length > 0 && (
{record.outfit_images.slice(0, 4).map((image, index) => ( -
+
{ + console.log('🖱️ 图片容器点击事件触发:', { + imageUrl: image.image_url, + title: `穿搭图片 ${index + 1}`, + event: e + }); + e.preventDefault(); + e.stopPropagation(); + const allImages = record.outfit_images.map(img => img.image_url); + openImagePreview(image.image_url, `穿搭图片 ${index + 1}`, allImages, index); + }} + > {`穿搭图片 { - console.log('🖱️ 图片点击事件触发:', { - imageUrl: image.image_url, - title: `穿搭图片 ${index + 1}`, - event: e - }); - e.preventDefault(); - e.stopPropagation(); - openImagePreview(image.image_url, `穿搭图片 ${index + 1}`); - }} /> {/* 预览按钮覆盖层 */} -
+
@@ -442,7 +454,8 @@ export const OutfitImageGallery: React.FC = ({ }); e.preventDefault(); e.stopPropagation(); - openImagePreview(image.image_url, `穿搭图片 ${index + 1}`); + const allImages = record.outfit_images.map(img => img.image_url); + openImagePreview(image.image_url, `穿搭图片 ${index + 1}`, allImages, index); }} > = ({ isOpen={previewModal.isOpen} source={previewModal.source} onClose={closeImagePreview} + images={previewModal.images} + currentIndex={previewModal.currentIndex} + onNavigate={(newIndex) => { + if (previewModal.images[newIndex]) { + const newImageUrl = previewModal.images[newIndex]; + const newSource: GroundingSource = { + uri: newImageUrl, + title: `穿搭图片 ${newIndex + 1}`, + content: { description: '穿搭生成图片' } + }; + setPreviewModal({ + ...previewModal, + source: newSource, + currentIndex: newIndex + }); + } + }} />
);