import React, { useState, useEffect } from 'react'; import { Layout, Menu, Button, Table, Space, Form, Input, Select, message, Modal, Card, Spin, Row, Col, Breadcrumb, Avatar, Dropdown, ConfigProvider, theme, Typography, Switch, Badge, Image, Upload, Divider, Descriptions, Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer } from 'antd'; import { UploadOutlined, FileImageOutlined, FileExcelOutlined, FileWordOutlined, FilePdfOutlined, FileOutlined, } from '@ant-design/icons'; import { useQuery, } from '@tanstack/react-query'; import dayjs from 'dayjs'; import weekday from 'dayjs/plugin/weekday'; import localeData from 'dayjs/plugin/localeData'; import { uploadMinIOWithPolicy,uploadOSSWithPolicy } from '@d8d-appcontainer/api'; import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types'; import 'dayjs/locale/zh-cn'; import type { FileLibrary, FileCategory } from '../share/types.ts'; import { OssType, } from '../share/types.ts'; import { FileAPI , UserAPI } from './api/index.ts'; // 配置 dayjs 插件 dayjs.extend(weekday); dayjs.extend(localeData); // 设置 dayjs 语言 dayjs.locale('zh-cn'); const { Title } = Typography; // 仪表盘页面 export const DashboardPage = () => { return (
仪表盘
); }; // 用户管理页面 export const UsersPage = () => { const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' }); const [modalVisible, setModalVisible] = useState(false); const [modalTitle, setModalTitle] = useState(''); const [editingUser, setEditingUser] = useState(null); const [form] = Form.useForm(); const { data: usersData, isLoading, refetch } = useQuery({ queryKey: ['users', searchParams], queryFn: async () => { return await UserAPI.getUsers(searchParams); } }); const users = usersData?.data || []; const pagination = { current: searchParams.page, pageSize: searchParams.limit, total: usersData?.pagination?.total || 0 }; // 处理搜索 const handleSearch = (values: any) => { setSearchParams(prev => ({ ...prev, search: values.search || '', page: 1 })); }; // 处理分页变化 const handleTableChange = (newPagination: any) => { setSearchParams(prev => ({ ...prev, page: newPagination.current, limit: newPagination.pageSize })); }; // 打开创建用户模态框 const showCreateModal = () => { setModalTitle('创建用户'); setEditingUser(null); form.resetFields(); setModalVisible(true); }; // 打开编辑用户模态框 const showEditModal = (user: any) => { setModalTitle('编辑用户'); setEditingUser(user); form.setFieldsValue(user); setModalVisible(true); }; // 处理模态框确认 const handleModalOk = async () => { try { const values = await form.validateFields(); if (editingUser) { // 编辑用户 await UserAPI.updateUser(editingUser.id, values); message.success('用户更新成功'); } else { // 创建用户 await UserAPI.createUser(values); message.success('用户创建成功'); } setModalVisible(false); form.resetFields(); refetch(); // 刷新用户列表 } catch (error) { console.error('表单提交失败:', error); message.error('操作失败,请重试'); } }; // 处理删除用户 const handleDelete = async (id: number) => { try { await UserAPI.deleteUser(id); message.success('用户删除成功'); refetch(); // 刷新用户列表 } catch (error) { console.error('删除用户失败:', error); message.error('删除失败,请重试'); } }; const columns = [ { title: '用户名', dataIndex: 'username', key: 'username', }, { title: '昵称', dataIndex: 'nickname', key: 'nickname', }, { title: '邮箱', dataIndex: 'email', key: 'email', }, { title: '角色', dataIndex: 'role', key: 'role', render: (role: string) => ( {role === 'admin' ? '管理员' : '普通用户'} ), }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'), }, { title: '操作', key: 'action', render: (_: any, record: any) => ( handleDelete(record.id)} okText="确定" cancelText="取消" > ), }, ]; return (
用户管理
`共 ${total} 条记录` }} onChange={handleTableChange} /> {/* 创建/编辑用户模态框 */} { setModalVisible(false); form.resetFields(); }} width={600} >
{!editingUser && ( )}
); }; // 文件库管理页面 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); }} >
); };