import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/client/components/ui/button'; import { Input } from '@/client/components/ui/input'; import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table'; import { Badge } from '@/client/components/ui/badge'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/client/components/ui/alert-dialog'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner'; import { Eye, Download, Edit, Trash2, Search, FileText, Upload } from 'lucide-react'; import { fileClient } from '@/client/api'; import type { InferResponseType, InferRequestType } from 'hono/client'; import dayjs from 'dayjs'; import MinioUploader from '@/client/admin/components/MinioUploader'; import { UpdateFileDto } from '@d8d/file-module/schemas/file.schema'; import * as z from 'zod'; // 定义类型 type FileItem = InferResponseType['data'][0]; type FileListResponse = InferResponseType; type UpdateFileRequest = InferRequestType['json']; type FileFormData = z.infer; export const FilesPage: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); const [editingFile, setEditingFile] = useState(null); const [searchText, setSearchText] = useState(''); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0, }); const [deleteFileId, setDeleteFileId] = useState(null); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const queryClient = useQueryClient(); // 表单初始化 const form = useForm({ resolver: zodResolver(UpdateFileDto), defaultValues: { name: '', description: '', }, }); // 获取文件列表数据 const fetchFiles = async ({ page, pageSize }: { page: number; pageSize: number }): Promise => { const response = await fileClient.$get({ query: { page, pageSize, keyword: searchText } }); if (!response.ok) throw new Error('Failed to fetch files'); return await response.json() as FileListResponse; }; const { data, isLoading, error } = useQuery({ queryKey: ['files', pagination.current, pagination.pageSize, searchText], queryFn: () => fetchFiles({ page: pagination.current, pageSize: pagination.pageSize }), }); // 更新文件记录 const updateFile = useMutation({ mutationFn: ({ id, data }: { id: number; data: UpdateFileRequest }) => fileClient[':id'].$put({ param: { id: Number(id) }, json: data }), onSuccess: () => { toast.success('文件记录更新成功'); queryClient.invalidateQueries({ queryKey: ['files'] }); setIsModalOpen(false); setEditingFile(null); }, onError: (error: Error) => { toast.error(`操作失败: ${error.message}`); } }); // 删除文件记录 const deleteFile = useMutation({ mutationFn: (id: number) => fileClient[':id'].$delete({ param: { id: Number(id) } }), onSuccess: () => { toast.success('文件记录删除成功'); queryClient.invalidateQueries({ queryKey: ['files'] }); }, onError: (error: Error) => { toast.error(`删除失败: ${error.message}`); } }); // 处理文件下载 const handleDownload = (record: FileItem) => { const a = document.createElement('a'); a.href = record.fullUrl; a.download = record.name; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; // 处理文件预览 const handlePreview = (record: FileItem) => { if (isPreviewable(record.type)) { window.open(record.fullUrl, '_blank'); } else { toast.warning('该文件类型不支持预览'); } }; // 检查是否为可预览的文件类型 const isPreviewable = (fileType: string | null) => { if (!fileType) return false; return fileType.startsWith('image/') || fileType.startsWith('video/'); }; // 处理上传成功回调 const handleUploadSuccess = () => { toast.success('文件上传成功'); queryClient.invalidateQueries({ queryKey: ['files'] }); }; // 处理上传失败回调 const handleUploadError = (error: Error) => { toast.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`); }; // 显示编辑弹窗 const showEditModal = (record: FileItem) => { setEditingFile(record); setIsModalOpen(true); form.reset({ name: record.name, description: record.description || '', }); }; // 处理表单提交 const handleFormSubmit = async (data: FileFormData) => { if (editingFile) { await updateFile.mutateAsync({ id: editingFile.id, data: { name: data.name, description: data.description, } }); } }; // 处理删除确认 const handleDeleteConfirm = () => { if (deleteFileId) { deleteFile.mutate(deleteFileId); setIsDeleteDialogOpen(false); setDeleteFileId(null); } }; const handleSearch = () => { setPagination({ ...pagination, current: 1 }); }; // 格式化文件大小 const formatFileSize = (bytes: number | null) => { if (!bytes || 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 tablePagination = data?.pagination || pagination; if (error) { return (

获取文件列表失败

); } return (

文件管理

文件列表
setSearchText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="max-w-sm" />
ID 预览 文件名称 文件类型 文件大小 上传时间 上传用户 操作 {isLoading ? (
) : data?.data?.length === 0 ? (

暂无文件

) : ( data?.data?.map((file) => ( {file.id} {isPreviewable(file.type) ? ( {file.name} handlePreview(file)} title="点击查看大图" /> ) : (
)}
{file.name}
{file.type} {formatFileSize(file.size)} {file.uploadTime ? dayjs(file.uploadTime).format('YYYY-MM-DD HH:mm:ss') : '-'} {file.uploadUser ? (file.uploadUser.nickname || file.uploadUser.username) : '-'}
)) )}
{/* 分页 */} {tablePagination.total > 0 && (
显示 {((tablePagination.current - 1) * tablePagination.pageSize + 1)}- {Math.min(tablePagination.current * tablePagination.pageSize, tablePagination.total)} 条, 共 {tablePagination.total} 条
第 {tablePagination.current} 页
)}
{/* 上传文件对话框 */} 上传文件 选择要上传的文件,支持拖拽上传
{ handleUploadSuccess(); setIsUploadModalOpen(false); }} onUploadError={handleUploadError} buttonText="点击或拖拽上传文件" tipText="支持单文件上传,单个文件大小不超过500MB" size="default" />
{/* 编辑对话框 */} 编辑文件信息 修改文件的基本信息
( 文件名称 )} /> ( 文件描述 )} />
{/* 删除确认对话框 */} 确认删除 确定要删除这个文件记录吗?此操作不可恢复。 取消 确认删除
); };