import React, { useState, useCallback } from 'react'; import { Eye, Trash2, Heart, Plus, Grid3X3, List, Search } from 'lucide-react'; import { ModelPhoto, PhotoType } from '../types/model'; import { convertFileSrc } from '@tauri-apps/api/core'; import { ModelImageUploader } from './ModelImageUploader'; import { ModelImagePreviewModal } from './ModelImagePreviewModal'; import { DeleteConfirmDialog } from './DeleteConfirmDialog'; interface ModelImageGalleryProps { photos: ModelPhoto[]; onUpload: (imagePaths: string[], photoType: PhotoType) => Promise; onDelete: (photo: ModelPhoto) => Promise; onToggleFavorite?: (photo: ModelPhoto) => Promise; isUploading?: boolean; className?: string; } type ViewMode = 'grid' | 'list'; type FilterType = 'all' | PhotoType; /** * 模特图片画廊组件 * 集成上传、预览、删除等功能 */ export const ModelImageGallery: React.FC = ({ photos, onUpload, onDelete, onToggleFavorite, isUploading = false, className = '' }) => { const [viewMode, setViewMode] = useState('grid'); const [filter, setFilter] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const [showUploader, setShowUploader] = useState(false); // 预览模态框状态 const [previewModal, setPreviewModal] = useState<{ isOpen: boolean; photos: ModelPhoto[]; initialIndex: number; }>({ isOpen: false, photos: [], initialIndex: 0 }); // 删除确认对话框状态 const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; photo: ModelPhoto | null; deleting: boolean; }>({ show: false, photo: null, deleting: false }); // 过滤照片 const filteredPhotos = photos.filter(photo => { const matchesFilter = filter === 'all' || photo.photo_type === filter; const matchesSearch = searchQuery === '' || photo.file_name.toLowerCase().includes(searchQuery.toLowerCase()) || (photo.description && photo.description.toLowerCase().includes(searchQuery.toLowerCase())) || photo.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); return matchesFilter && matchesSearch; }); // 处理图片上传 const handleUpload = useCallback(async (imagePaths: string[], photoType: PhotoType) => { try { await onUpload(imagePaths, photoType); setShowUploader(false); } catch (error) { console.error('上传失败:', error); } }, [onUpload]); // 打开预览 const openPreview = useCallback((photo: ModelPhoto) => { const photoIndex = filteredPhotos.findIndex(p => p.id === photo.id); setPreviewModal({ isOpen: true, photos: filteredPhotos, initialIndex: photoIndex }); }, [filteredPhotos]); // 关闭预览 const closePreview = useCallback(() => { setPreviewModal(prev => ({ ...prev, isOpen: false })); }, []); // 处理删除确认 const handleDeleteClick = useCallback((photo: ModelPhoto) => { setDeleteConfirm({ show: true, photo, deleting: false }); }, []); // 确认删除 const confirmDelete = useCallback(async () => { if (!deleteConfirm.photo) return; try { setDeleteConfirm(prev => ({ ...prev, deleting: true })); await onDelete(deleteConfirm.photo); setDeleteConfirm({ show: false, photo: null, deleting: false }); } catch (error) { console.error('删除失败:', error); setDeleteConfirm(prev => ({ ...prev, deleting: false })); } }, [deleteConfirm.photo, onDelete]); // 取消删除 const cancelDelete = useCallback(() => { setDeleteConfirm({ show: false, photo: null, deleting: false }); }, []); const getPhotoTypeLabel = (photoType: PhotoType): string => { switch (photoType) { case PhotoType.Portrait: return '个人形象照'; case PhotoType.Commercial: return '商业照'; case PhotoType.Casual: return '生活照'; case PhotoType.FullBody: return '全身照'; case PhotoType.Headshot: return '头像照'; case PhotoType.Artistic: return '艺术照'; default: return '其他'; } }; const getPhotoTypeColor = (photoType: PhotoType): string => { switch (photoType) { case PhotoType.Portrait: return 'bg-blue-100 text-blue-800'; case PhotoType.Commercial: return 'bg-green-100 text-green-800'; case PhotoType.Casual: return 'bg-purple-100 text-purple-800'; case PhotoType.FullBody: return 'bg-indigo-100 text-indigo-800'; case PhotoType.Headshot: return 'bg-pink-100 text-pink-800'; case PhotoType.Artistic: return 'bg-yellow-100 text-yellow-800'; default: return 'bg-gray-100 text-gray-800'; } }; const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; return (
{/* 头部工具栏 */}

个人形象图片

共 {photos.length} 张照片 {filteredPhotos.length !== photos.length && ` (显示 ${filteredPhotos.length} 张)`}
{/* 搜索框 */}
setSearchQuery(e.target.value)} className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* 过滤器 */} {/* 视图模式切换 */}
{/* 上传按钮 */}
{/* 上传组件 */} {showUploader && (
)} {/* 照片展示 */} {filteredPhotos.length === 0 ? (
📷

{photos.length === 0 ? '暂无照片' : '没有找到匹配的照片'}

{photos.length === 0 ? '点击上传按钮添加第一张照片' : '尝试调整搜索条件或过滤器'}

) : viewMode === 'grid' ? ( /* 网格视图 */
{filteredPhotos.map((photo) => (
{/* 图片 */}
{photo.file_name}
{/* 悬停操作按钮 */}
{onToggleFavorite && ( )}
{/* 照片信息 */}
{getPhotoTypeLabel(photo.photo_type)} {photo.is_cover && ( )}

{photo.file_name}

{formatFileSize(photo.file_size)}

))}
) : ( /* 列表视图 */
{filteredPhotos.map((photo) => (
{/* 缩略图 */}
{photo.file_name}
{/* 信息 */}

{photo.file_name}

{getPhotoTypeLabel(photo.photo_type)} {photo.is_cover && ( )}

{formatFileSize(photo.file_size)} • {new Date(photo.created_at).toLocaleDateString()}

{photo.description && (

{photo.description}

)}
{/* 操作按钮 */}
{onToggleFavorite && ( )}
))}
)} {/* 图片预览模态框 */} {/* 删除确认对话框 */}
); }; export default ModelImageGallery;