import React, { useState } from 'react'; import { Modal, Button, Image, message } from 'antd'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { fileClient } from '@/client/api'; import type { FileType } from '@/server/modules/files/file.schema'; import MinioUploader from '@/client/admin-shadcn/components/MinioUploader'; // 定义重载的 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) { message.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) => { message.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}` : '请选择文件'; }; return (
上传后请从下方列表中选择文件
{getSelectionText()}
{isLoading ? (
加载中...
) : filteredFiles.length > 0 ? ( filteredFiles.map((file) => (
handleSelectFile(file)} onMouseEnter={(e) => { if (!isFileSelected(file)) { e.currentTarget.style.backgroundColor = '#f6ffed'; e.currentTarget.style.borderColor = '#b7eb8f'; } }} onMouseLeave={(e) => { if (!isFileSelected(file)) { e.currentTarget.style.backgroundColor = ''; e.currentTarget.style.borderColor = '#f0f0f0'; } }} > {file.name}
{file.name}
ID: {file.id} | 大小: {((file.size || 0) / 1024 / 1024).toFixed(2)}MB
)) ) : (
暂无符合条件的文件,请先上传
)}
); }; // 类型守卫函数 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;