import React, { useState, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@/client/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog'; import { Card, CardContent } from '@/client/components/ui/card'; import { toast } from 'sonner'; import { fileClient } from '@/client/api'; import MinioUploader from '@/client/admin/components/MinioUploader'; import { Check, Upload, Eye, X } from 'lucide-react'; import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar'; import { cn } from '@/client/lib/utils'; import type { InferResponseType } from 'hono/client'; type FileType = InferResponseType['data'][0] interface AvatarSelectorProps { value?: number | null; onChange: (fileId: number | null) => void; accept?: string; maxSize?: number; uploadPath?: string; uploadButtonText?: string; previewSize?: 'small' | 'medium' | 'large'; showPreview?: boolean; placeholder?: string; } const AvatarSelector: React.FC = ({ value, onChange, accept = 'image/*', maxSize = 2, uploadPath = '/avatars', uploadButtonText = '上传', previewSize = 'medium', showPreview = true, placeholder = '选择头像', }) => { const [isOpen, setIsOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); // 获取当前选中的文件详情 const { data: currentFile } = useQuery({ queryKey: ['file-detail', value], queryFn: async () => { if (!value) return null; const response = await fileClient[':id']['$get']({ param: { id: Number(value) } }); if (response.status !== 200) throw new Error('获取文件详情失败'); return response.json(); }, enabled: !!value, }); // 当对话框打开时,设置当前选中的头像 useEffect(() => { if (isOpen && value && currentFile) { setSelectedFile(currentFile); } }, [isOpen, value, currentFile]); // 获取头像列表 const { data: filesData, isLoading, refetch } = useQuery({ queryKey: ['avatars-for-selection'] as const, queryFn: async () => { const response = await fileClient.$get({ query: { page: 1, pageSize: 50, keyword: 'image' } }); if (response.status !== 200) throw new Error('获取头像列表失败'); return response.json(); }, enabled: isOpen, }); const avatars = filesData?.data?.filter((f: any) => f?.type?.startsWith('image/')) || []; const handleSelectAvatar = (file: FileType) => { setSelectedFile(prevSelected => { // 如果点击的是已选中的头像,则取消选择 if (prevSelected?.id === file.id) { return null; } // 否则选择新的头像 return file; }); }; const handleConfirm = () => { if (!selectedFile) { toast.warning('请选择一个头像'); return; } onChange(selectedFile.id); setIsOpen(false); setSelectedFile(null); }; const handleCancel = () => { setIsOpen(false); setSelectedFile(null); }; const handleUploadSuccess = () => { toast.success('头像上传成功!请从列表中选择新上传的头像'); // 重新获取头像列表以显示新上传的头像 refetch(); }; const getPreviewSize = () => { switch (previewSize) { case 'small': return 'h-16 w-16'; case 'medium': return 'h-24 w-24'; case 'large': return 'h-32 w-32'; default: return 'h-24 w-24'; } }; const handleRemoveAvatar = (e: React.MouseEvent) => { e.stopPropagation(); onChange(null as any); }; return ( <>
{showPreview && (
setIsOpen(true)} > {currentFile ? ( ) : ( {placeholder.charAt(0).toUpperCase()} )} {currentFile && ( )}
{currentFile && (

当前: {currentFile.name}

)}
)} {!showPreview && ( )}
选择头像 上传新头像或从已有头像中选择
{/* 头像列表 */}
{isLoading ? (

加载中...

) : (
{/* 上传区域 - 作为第一项 */}

上传新头像

{/* 现有头像列表 */} {avatars.map((file) => (
handleSelectAvatar(file)} >
{file.name} {selectedFile?.id === file.id && (
)}
{ e.stopPropagation(); window.open(file.fullUrl, '_blank'); }} />

{file.name}

))} {/* 空状态 - 当没有头像时显示 */} {avatars.length === 0 && (

暂无头像

请上传头像文件

)}
)}
); }; export default AvatarSelector;