import React, { useState } from 'react'; import { App, Button, Popconfirm, Space, Table, Upload } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { EyeOutlined, InboxOutlined, UploadOutlined } from '@ant-design/icons'; import api from '../../api'; import { message } from '../../ui/app-message'; import { useApiMutation } from '../../hooks/useApiMutation'; import { usePermission } from '../../hooks/usePermission'; import { getErrorMessage } from '../../utils/error'; import { ATTACHMENT_CATEGORY_OPTIONS, formatFileSize } from './shared'; import type { AttachmentRecord, TabProps } from './shared'; export const AttachmentsTab: React.FC = ({ data, studentId, }) => { const { modal } = App.useApp(); const { hasPermission } = usePermission(); const canPurgeArchive = hasPermission('archive:purge'); const [uploading, setUploading] = useState(false); const deleteAttachmentMutation = useApiMutation( async (attachmentId: number) => api.delete(`/archive/attachments/${attachmentId}`), { invalidate: [['archive', studentId]] }, ); const purgeAttachmentMutation = useApiMutation( async (id: number) => api.delete(`/archive/attachments/${id}/permanent`), { invalidate: [['archive', studentId]] }, ); const uploadAttachmentMutation = useApiMutation( async (formData: FormData) => api.post(`/archive/${studentId}/attachments`, formData), { invalidate: [['archive', studentId]] }, ); const handleDelete = async (attachmentId: number) => { try { await deleteAttachmentMutation.mutateAsync(attachmentId); message.success('已归档'); } catch { // 错误提示由 useApiMutation 统一处理 } }; const handlePurge = (record: AttachmentRecord) => { modal.confirm({ title: `永久删除附件「${record.fileName}」?`, content: '删除后不可恢复,磁盘上的附件文件将被清除。确定继续?', okText: '永久删除', okButtonProps: { danger: true }, cancelText: '取消', onOk: async () => { try { await purgeAttachmentMutation.mutateAsync(record.id); message.success('已永久删除(不可恢复)'); } catch { // 错误提示由 useApiMutation 统一处理 } }, }); }; const columns: ColumnsType = [ { title: '类别', dataIndex: 'category', render: (v: string) => ATTACHMENT_CATEGORY_OPTIONS.find((o) => o.value === v)?.label || v, }, { title: '文件名', dataIndex: 'fileName' }, { title: '大小', dataIndex: 'fileSize', render: formatFileSize }, { title: '操作', render: (_: unknown, record: AttachmentRecord) => ( {hasPermission('student:edit') && record.status !== 'archived' ? ( handleDelete(record.id)}> ) : null} {record.status === 'archived' && canPurgeArchive ? ( ) : null} ), }, ]; return (
{hasPermission('student:edit') ? ( { const formData = new FormData(); formData.append( 'file', options.file instanceof File ? options.file : new File([options.file as Blob], 'attachment'), ); setUploading(true); try { await uploadAttachmentMutation.mutateAsync(formData); message.success('上传成功'); options.onSuccess?.({}); } catch (e) { options.onError?.(e instanceof Error ? e : new Error('上传失败')); } finally { setUploading(false); } }} > ) : null} columns={columns} dataSource={data} rowKey="id" rowClassName={(record) => (record.status === 'archived' ? 'archived-row' : '')} pagination={{ defaultPageSize: 15, showSizeChanger: true, pageSizeOptions: [15, 30, 50], }} style={{ marginTop: 16 }} />
); };