import React, { useState } from 'react'; import { useQuery, useQueryClient } 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 { Badge } from '@/client/components/ui/badge'; import { toast } from 'sonner'; import { fileClient } from '@/client/api'; import type { FileType } from '@/server/modules/files/file.schema'; import MinioUploader from '@/client/admin-shadcn/components/MinioUploader'; import { Check, Upload } from 'lucide-react'; // 定义重载的 props 类型 interface SingleFileSelectorProps { visible: boolean; onCancel: () => void; onSelect: (file: FileType) => void; accept?: string; maxSize?: number; uploadPath?: string; uploadButtonText?: string; multiple?: false; } interface MultipleFileSelectorProps { visible: boolean; onCancel: () => void; onSelect: (files: FileType[]) => void; accept?: string; maxSize?: number; uploadPath?: string; uploadButtonText?: string; multiple: true; } type FileSelectorProps = SingleFileSelectorProps | MultipleFileSelectorProps; const FileSelector: React.FC = ({ visible, onCancel, onSelect, accept = 'image/*', maxSize = 5, uploadPath = '/uploads', uploadButtonText = '上传新文件', multiple = false, ...props }) => { const queryClient = useQueryClient(); const [selectedFiles, setSelectedFiles] = useState([]); // 获取文件列表 const { data: filesData, isLoading } = useQuery({ queryKey: ['files-for-selection', accept] as const, queryFn: async () => { const response = await fileClient.$get({ query: { page: 1, pageSize: 50, keyword: accept.startsWith('image/') ? 'image' : undefined } }); if (response.status !== 200) throw new Error('获取文件列表失败'); return response.json(); }, enabled: visible }); // 重置选择状态 React.useEffect(() => { if (!visible) { setSelectedFiles([]); } }, [visible]); const handleSelectFile = (file: FileType) => { if (!file) { console.error('No file provided to handleSelectFile'); return; } if (multiple) { // 多选模式 const newSelection = selectedFiles.some(f => f.id === file.id) ? selectedFiles.filter(f => f.id !== file.id) : [...selectedFiles, file]; setSelectedFiles(newSelection); } else { // 单选模式 setSelectedFiles([file]); } }; const handleConfirm = () => { if (selectedFiles.length === 0) { toast.warning('请先选择文件'); return; } if (multiple) { // 多选模式 (onSelect as MultipleFileSelectorProps['onSelect'])(selectedFiles); } else { // 单选模式 (onSelect as SingleFileSelectorProps['onSelect'])(selectedFiles[0]); } onCancel(); }; const handleUploadSuccess = (fileKey: string, fileUrl: string, file: any) => { toast.success('文件上传成功!请从列表中选择新上传的文件'); // 刷新文件列表 queryClient.invalidateQueries({ queryKey: ['files-for-selection', accept] }); }; const filteredFiles = Array.isArray(filesData?.data) ? filesData.data.filter((f: any) => !accept || f?.type?.startsWith(accept.replace('*', ''))) : []; const isFileSelected = (file: FileType) => { return selectedFiles.some(f => f.id === file.id); }; const getSelectionText = () => { if (multiple) { return selectedFiles.length > 0 ? `已选择 ${selectedFiles.length} 个文件` : '请选择文件'; } return selectedFiles.length > 0 ? `已选择: ${selectedFiles[0].name}` : '请选择文件'; }; // 格式化文件大小 const formatFileSize = (bytes: number) => { 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]; }; // 获取文件类型的显示标签 const getFileTypeBadge = (type: string) => { if (type.startsWith('image/')) { return { text: '图片', color: 'bg-blue-100 text-blue-800' }; } else if (type.startsWith('video/')) { return { text: '视频', color: 'bg-red-100 text-red-800' }; } else if (type.startsWith('audio/')) { return { text: '音频', color: 'bg-purple-100 text-purple-800' }; } else if (type.includes('pdf')) { return { text: 'PDF', color: 'bg-red-100 text-red-800' }; } else if (type.includes('word')) { return { text: '文档', color: 'bg-blue-100 text-blue-800' }; } else if (type.includes('excel') || type.includes('sheet')) { return { text: '表格', color: 'bg-green-100 text-green-800' }; } else { return { text: '文件', color: 'bg-gray-100 text-gray-800' }; } }; return ( 选择文件 从已有文件中选择,或上传新文件
{/* 上传区域 */}

上传后请从下方列表中选择文件

{/* 选择状态显示 */}

{getSelectionText()}

{/* 文件列表 */}
{isLoading ? (

加载中...

) : filteredFiles.length > 0 ? (
{filteredFiles.map((file) => { const typeBadge = getFileTypeBadge(file.type); const isSelected = isFileSelected(file); return ( handleSelectFile(file)} >
{/* 文件预览图 */}
{file.type.startsWith('image/') ? ( {file.name} ) : (
)} {isSelected && (
)}
{/* 文件信息 */}

{file.name}

{typeBadge.text} ID: {file.id} {formatFileSize(file.size || 0)}
{/* 选择按钮 */}
); })}
) : (

暂无符合条件的文件

请先上传文件或调整筛选条件

)}
); }; // 类型守卫函数 function isMultipleSelector(props: FileSelectorProps): props is MultipleFileSelectorProps { return props.multiple === true; } function isSingleSelector(props: FileSelectorProps): props is SingleFileSelectorProps { return props.multiple === false || props.multiple === undefined; } export default FileSelector;