import { useState, useEffect, useCallback } from 'react' import { Breadcrumb, Button, Card, Table, Modal, Form, Input, InputNumber, message, Space, Popconfirm, Upload, Typography } from 'antd' import { FolderOutlined, FileOutlined, PlusOutlined, EditOutlined, DeleteOutlined, UploadOutlined, ArrowLeftOutlined } from '@ant-design/icons' import type { ColumnsType } from 'antd/es/table' import { listFolders, createFolder, updateFolder, deleteFolder, listFiles, createFile, deleteFile, type DocFolder, type DocFile } from '@/api/document' import { getUploadSignature } from '@/api/oss' const ACCEPT_MIME = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt' /** 可上传文件类型说明(展示用) */ const UPLOAD_FILE_TIP = '支持上传:PDF、Word(.doc/.docx)、Excel(.xls/.xlsx)、PPT(.ppt/.pptx)、TXT 等格式。' const DocumentList = () => { const [folders, setFolders] = useState([]) const [files, setFiles] = useState([]) const [currentFolder, setCurrentFolder] = useState(null) const [loading, setLoading] = useState(false) const [folderModalVisible, setFolderModalVisible] = useState(false) const [editingFolder, setEditingFolder] = useState(null) const [uploading, setUploading] = useState(false) const [form] = Form.useForm() const fetchFolders = useCallback(async () => { setLoading(true) try { const res = await listFolders() if (res.success && res.list) setFolders(res.list) } catch (e) { message.error('获取文件夹列表失败') } finally { setLoading(false) } }, []) const fetchFiles = useCallback(async (folderId: string) => { setLoading(true) try { const res = await listFiles(folderId) if (res.success && res.list) setFiles(res.list) } catch (e) { message.error('获取文件列表失败') } finally { setLoading(false) } }, []) useEffect(() => { fetchFolders() }, [fetchFolders]) useEffect(() => { if (currentFolder) fetchFiles(currentFolder.id) else setFiles([]) }, [currentFolder, fetchFiles]) const handleCreateFolder = () => { setEditingFolder(null) form.resetFields() setFolderModalVisible(true) } const handleEditFolder = (record: DocFolder) => { setEditingFolder(record) form.setFieldsValue({ name: record.name, sort_order: record.sort_order }) setFolderModalVisible(true) } const handleFolderSubmit = async () => { try { const values = await form.validateFields() if (editingFolder) { await updateFolder({ id: editingFolder.id, name: values.name, sort_order: values.sort_order ?? 0 }) message.success('更新成功') } else { await createFolder({ name: values.name, sort_order: values.sort_order ?? 0 }) message.success('创建成功') } setFolderModalVisible(false) fetchFolders() } catch (e: any) { if (e.errorFields) return message.error(e.message || '操作失败') } } const handleDeleteFolder = async (id: string) => { try { await deleteFolder(id) message.success('删除成功') if (currentFolder?.id === id) setCurrentFolder(null) fetchFolders() } catch (e: any) { message.error(e.response?.data?.message || e.message || '删除失败') } } const handleUploadFile = async (file: File) => { if (!currentFolder) return setUploading(true) try { const credentials = await getUploadSignature('doc') const key = `${credentials.dir}${Date.now()}_${Math.random().toString(36).slice(2, 8)}.${(file.name.split('.').pop() || '')}` const formData = new FormData() formData.append('success_action_status', '200') formData.append('policy', credentials.policy) formData.append('x-oss-signature', credentials.signature) formData.append('x-oss-signature-version', credentials.x_oss_signature_version) formData.append('x-oss-credential', credentials.x_oss_credential) formData.append('x-oss-date', credentials.x_oss_date) formData.append('key', key) formData.append('x-oss-security-token', credentials.security_token) formData.append('file', file) const resp = await fetch(credentials.host, { method: 'POST', body: formData }) if (!resp.ok) throw new Error(`上传失败: ${resp.status}`) const fileUrl = `${credentials.host}/${key}` await createFile({ folder_id: currentFolder.id, file_name: file.name, file_url: fileUrl, file_size: file.size, mime_type: file.type || '', }) message.success('上传成功') fetchFiles(currentFolder.id) } catch (e: any) { message.error(e.message || '上传失败') } finally { setUploading(false) } return false // 阻止 Upload 默认上传 } const handleDeleteFile = async (id: string) => { try { await deleteFile(id) message.success('删除成功') if (currentFolder) fetchFiles(currentFolder.id) } catch (e: any) { message.error(e.response?.data?.message || e.message || '删除失败') } } const formatSize = (n: number) => { if (n < 1024) return `${n} B` if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB` return `${(n / (1024 * 1024)).toFixed(1)} MB` } const folderColumns: ColumnsType = [ { title: '名称', dataIndex: 'name', key: 'name', render: (name, record) => setCurrentFolder(record)}> {name} }, { title: '排序', dataIndex: 'sort_order', key: 'sort_order', width: 80 }, { title: '操作', key: 'action', width: 140, render: (_, record) => ( handleDeleteFolder(record.id)}> ), }, ] const fileColumns: ColumnsType = [ { title: '名称', dataIndex: 'name', key: 'name', render: (name, row) => {name} }, { title: '原始文件名', dataIndex: 'file_name', key: 'file_name' }, { title: '大小', dataIndex: 'file_size', key: 'file_size', width: 100, render: (n: number) => formatSize(n) }, { title: '类型', dataIndex: 'mime_type', key: 'mime_type', width: 120 }, { title: '操作', key: 'action', width: 80, render: (_, record) => ( handleDeleteFile(record.id)}> ), }, ] return (
setCurrentFolder(null)}>文档管理 }, ...(currentFolder ? [{ title: currentFolder.name }] : []), ]} /> {currentFolder ? ( <> ) : ( )} } > {currentFolder ? ( {UPLOAD_FILE_TIP} ) : null} {!currentFolder ? ( ) : (
)} setFolderModalVisible(false)} destroyOnClose >
) } export default DocumentList