import React, { useState, useEffect } from 'react'; import { Button, Table, Space, Form, Input, Select, message, Modal, Card, Typography, Tag, Popconfirm, Tabs, Image, Upload, Descriptions } from 'antd'; import { UploadOutlined, FileImageOutlined, FileExcelOutlined, FileWordOutlined, FilePdfOutlined, FileOutlined, } from '@ant-design/icons'; import { useQuery } from '@tanstack/react-query'; import dayjs from 'dayjs'; import { uploadMinIOWithPolicy, uploadOSSWithPolicy } from '@d8d-appcontainer/api'; import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types'; import { FileAPI } from './api/index.ts'; import type { FileLibrary, FileCategory } from '../share/types.ts'; import { OssType } from '../share/types.ts'; const { Title } = Typography; // 文件库管理页面 export const FileLibraryPage = () => { const [loading, setLoading] = useState(false); const [fileList, setFileList] = useState([]); const [categories, setCategories] = useState([]); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); const [searchParams, setSearchParams] = useState({ fileType: '', keyword: '' }); const [uploadModalVisible, setUploadModalVisible] = useState(false); const [fileDetailModalVisible, setFileDetailModalVisible] = useState(false); const [currentFile, setCurrentFile] = useState(null); const [uploadLoading, setUploadLoading] = useState(false); const [form] = Form.useForm(); const [categoryForm] = Form.useForm(); const [categoryModalVisible, setCategoryModalVisible] = useState(false); const [currentCategory, setCurrentCategory] = useState(null); // 获取文件图标 const getFileIcon = (fileType: string) => { if (fileType.includes('image')) { return ; } else if (fileType.includes('pdf')) { return ; } else if (fileType.includes('excel') || fileType.includes('sheet')) { return ; } else if (fileType.includes('word') || fileType.includes('document')) { return ; } else { return ; } }; // 加载文件列表 const fetchFileList = async () => { setLoading(true); try { const response = await FileAPI.getFileList({ page: pagination.current, pageSize: pagination.pageSize, ...searchParams }); if (response && response.data) { setFileList(response.data.list); setPagination({ ...pagination, total: response.data.pagination.total }); } } catch (error) { console.error('获取文件列表失败:', error); message.error('获取文件列表失败'); } finally { setLoading(false); } }; // 加载文件分类 const fetchCategories = async () => { try { const response = await FileAPI.getCategories(); if (response && response.data) { setCategories(response.data); } } catch (error) { console.error('获取文件分类失败:', error); message.error('获取文件分类失败'); } }; // 组件挂载时加载数据 useEffect(() => { fetchFileList(); fetchCategories(); }, [pagination.current, pagination.pageSize, searchParams]); // 上传文件 const handleUpload = async (file: File) => { try { setUploadLoading(true); // 1. 获取上传策略 const policyResponse = await FileAPI.getUploadPolicy(file.name); if (!policyResponse || !policyResponse.data) { throw new Error('获取上传策略失败'); } const policy = policyResponse.data; // 2. 上传文件至 MinIO const uploadProgress = { progress: 0, completed: false, error: null as Error | null }; const callbacks = { onProgress: (event: { progress: number }) => { uploadProgress.progress = event.progress; }, onComplete: () => { uploadProgress.completed = true; }, onError: (err: Error) => { uploadProgress.error = err; } }; const uploadUrl = window.CONFIG?.OSS_TYPE === OssType.MINIO ? await uploadMinIOWithPolicy( policy as MinioUploadPolicy, file, file.name, callbacks ) : await uploadOSSWithPolicy( policy as OSSUploadPolicy, file, file.name, callbacks ); if (!uploadUrl || uploadProgress.error) { throw uploadProgress.error || new Error('上传文件失败'); } // 3. 保存文件信息到文件库 const fileValues = form.getFieldsValue(); const fileData = { file_name: file.name, file_path: uploadUrl, file_type: file.type, file_size: file.size, category_id: fileValues.category_id ? Number(fileValues.category_id) : undefined, tags: fileValues.tags, description: fileValues.description }; const saveResponse = await FileAPI.saveFileInfo(fileData); if (saveResponse && saveResponse.data) { message.success('文件上传成功'); setUploadModalVisible(false); form.resetFields(); fetchFileList(); } } catch (error) { console.error('上传文件失败:', error); message.error('上传文件失败: ' + (error instanceof Error ? error.message : '未知错误')); } finally { setUploadLoading(false); } }; // 处理文件上传 const uploadProps = { name: 'file', multiple: false, showUploadList: false, beforeUpload: (file: File) => { const isLt10M = file.size / 1024 / 1024 < 10; if (!isLt10M) { message.error('文件大小不能超过10MB!'); return false; } handleUpload(file); return false; } }; // 查看文件详情 const viewFileDetail = async (id: number) => { try { const response = await FileAPI.getFileInfo(id); if (response && response.data) { setCurrentFile(response.data); setFileDetailModalVisible(true); } } catch (error) { console.error('获取文件详情失败:', error); message.error('获取文件详情失败'); } }; // 下载文件 const downloadFile = async (file: FileLibrary) => { try { // 更新下载计数 await FileAPI.updateDownloadCount(file.id); // 创建一个暂时的a标签用于下载 const link = document.createElement('a'); link.href = file.file_path; link.target = '_blank'; link.download = file.file_name; document.body.appendChild(link); link.click(); document.body.removeChild(link); message.success('下载已开始'); } catch (error) { console.error('下载文件失败:', error); message.error('下载文件失败'); } }; // 删除文件 const handleDeleteFile = async (id: number) => { try { await FileAPI.deleteFile(id); message.success('文件删除成功'); fetchFileList(); } catch (error) { console.error('删除文件失败:', error); message.error('删除文件失败'); } }; // 处理搜索 const handleSearch = (values: any) => { setSearchParams(values); setPagination({ ...pagination, current: 1 }); }; // 处理表格分页变化 const handleTableChange = (newPagination: any) => { setPagination({ ...pagination, current: newPagination.current, pageSize: newPagination.pageSize }); }; // 添加或更新分类 const handleCategorySave = async () => { try { const values = await categoryForm.validateFields(); if (currentCategory) { // 更新分类 await FileAPI.updateCategory(currentCategory.id, values); message.success('分类更新成功'); } else { // 创建分类 await FileAPI.createCategory(values); message.success('分类创建成功'); } setCategoryModalVisible(false); categoryForm.resetFields(); setCurrentCategory(null); fetchCategories(); } catch (error) { console.error('保存分类失败:', error); message.error('保存分类失败'); } }; // 编辑分类 const handleEditCategory = (category: FileCategory) => { setCurrentCategory(category); categoryForm.setFieldsValue(category); setCategoryModalVisible(true); }; // 删除分类 const handleDeleteCategory = async (id: number) => { try { await FileAPI.deleteCategory(id); message.success('分类删除成功'); fetchCategories(); } catch (error) { console.error('删除分类失败:', error); message.error('删除分类失败'); } }; // 文件表格列配置 const columns = [ { title: '文件名', key: 'file_name', render: (text: string, record: FileLibrary) => ( {getFileIcon(record.file_type)} viewFileDetail(record.id)}> {record.original_filename || record.file_name} ) }, { title: '文件类型', dataIndex: 'file_type', key: 'file_type', width: 120, render: (text: string) => text.split('/').pop() }, { title: '大小', dataIndex: 'file_size', key: 'file_size', width: 100, render: (size: number) => { if (size < 1024) { return `${size} B`; } else if (size < 1024 * 1024) { return `${(size / 1024).toFixed(2)} KB`; } else { return `${(size / 1024 / 1024).toFixed(2)} MB`; } } }, { title: '分类', dataIndex: 'category_id', key: 'category_id', width: 120 }, { title: '上传者', dataIndex: 'uploader_name', key: 'uploader_name', width: 120 }, { title: '下载次数', dataIndex: 'download_count', key: 'download_count', width: 120 }, { title: '上传时间', dataIndex: 'created_at', key: 'created_at', width: 180, render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss') }, { title: '操作', key: 'action', width: 180, render: (_: any, record: FileLibrary) => ( handleDeleteFile(record.id)} okText="确定" cancelText="取消" > ) } ]; // 分类表格列配置 const categoryColumns = [ { title: '分类名称', dataIndex: 'name', key: 'name' }, { title: '分类编码', dataIndex: 'code', key: 'code' }, { title: '描述', dataIndex: 'description', key: 'description' }, { title: '操作', key: 'action', render: (_: any, record: FileCategory) => ( handleDeleteCategory(record.id)} okText="确定" cancelText="取消" > ) } ]; return (
文件库管理 {/* 搜索表单 */}
{/* 文件列表 */} `共 ${total} 条记录` }} onChange={handleTableChange} />
{/* 上传文件弹窗 */} setUploadModalVisible(false)} footer={null} >
支持任意类型文件,单个文件不超过10MB
{/* 文件详情弹窗 */} setFileDetailModalVisible(false)} footer={[ , ]} width={700} > {currentFile && ( {currentFile.file_name} {currentFile.original_filename && ( {currentFile.original_filename} )} {currentFile.file_type} {currentFile.file_size < 1024 * 1024 ? `${(currentFile.file_size / 1024).toFixed(2)} KB` : `${(currentFile.file_size / 1024 / 1024).toFixed(2)} MB`} {currentFile.uploader_name} {dayjs(currentFile.created_at).format('YYYY-MM-DD HH:mm:ss')} {currentFile.category_id} {currentFile.download_count} {currentFile.tags?.split(',').map(tag => ( {tag} ))} {currentFile.description} {currentFile.file_type.startsWith('image/') && ( )} )} {/* 分类管理弹窗 */} { setCategoryModalVisible(false); categoryForm.resetFields(); setCurrentCategory(null); }} >
); };