94 lines
3.7 KiB
React
94 lines
3.7 KiB
React
import React, { useCallback, useEffect, useState } from 'react';
|
|
import Paper from '@mui/material/Paper';
|
|
import Typography from '@mui/material/Typography';
|
|
import Box from '@mui/material/Box';
|
|
import Table from '@mui/material/Table';
|
|
import TableBody from '@mui/material/TableBody';
|
|
import TableCell from '@mui/material/TableCell';
|
|
import TableContainer from '@mui/material/TableContainer';
|
|
import TableHead from '@mui/material/TableHead';
|
|
import TableRow from '@mui/material/TableRow';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
import { listAttachments, deleteAttachment } from '../../api/upload.js';
|
|
import { showSnack } from '../snack.jsx';
|
|
import ConfirmDialog from '../ConfirmDialog.jsx';
|
|
|
|
/** 附件管理:列表/删除(迁移自 v1 附件卡片) */
|
|
export default function Uploads() {
|
|
const [list, setList] = useState(null);
|
|
const [confirm, setConfirm] = useState(null);
|
|
|
|
const load = useCallback(() => {
|
|
listAttachments()
|
|
.then((rows) => setList(rows || []))
|
|
.catch((e) => showSnack(e.message, 'error'));
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
const doDelete = async () => {
|
|
if (!confirm) return;
|
|
try {
|
|
await deleteAttachment(confirm.id);
|
|
showSnack('已删除');
|
|
setConfirm(null);
|
|
load();
|
|
} catch (e) {
|
|
showSnack(e.message, 'error');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box>
|
|
<Typography variant="h5" sx={{ mb: 2 }}>附件管理</Typography>
|
|
<TableContainer component={Paper}>
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>ID</TableCell>
|
|
<TableCell>文件名</TableCell>
|
|
<TableCell>原始名</TableCell>
|
|
<TableCell>大小</TableCell>
|
|
<TableCell>类型</TableCell>
|
|
<TableCell>上传者</TableCell>
|
|
<TableCell>时间</TableCell>
|
|
<TableCell align="right">操作</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{list === null ? (
|
|
<TableRow><TableCell colSpan={8}>加载中...</TableCell></TableRow>
|
|
) : list.length === 0 ? (
|
|
<TableRow><TableCell colSpan={8}>暂无附件</TableCell></TableRow>
|
|
) : list.map((f) => (
|
|
<TableRow key={f.id}>
|
|
<TableCell>{f.id}</TableCell>
|
|
<TableCell sx={{ maxWidth: 160 }}>
|
|
<Box component="a" href={`/uploads/${f.filename}`} target="_blank" rel="noopener" sx={{ color: 'primary.main', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>{f.filename}</Box>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</TableCell>
|
|
<TableCell>{((f.size || 0) / 1024).toFixed(0)} KB</TableCell>
|
|
<TableCell sx={{ color: 'text.secondary', fontSize: 13 }}>{f.mime_type || '-'}</TableCell>
|
|
<TableCell sx={{ color: 'text.secondary' }}>UID {f.user_id}</TableCell>
|
|
<TableCell sx={{ color: 'text.secondary', fontSize: 13 }}>{f.created_at}</TableCell>
|
|
<TableCell align="right">
|
|
<IconButton size="small" color="error" onClick={() => setConfirm({ id: f.id, label: f.filename })}><DeleteIcon fontSize="small" /></IconButton>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
<ConfirmDialog
|
|
open={!!confirm}
|
|
message={`确定要删除 "${confirm ? confirm.label : ''}" 吗?`}
|
|
onClose={() => setConfirm(null)}
|
|
onConfirm={doDelete}
|
|
confirmText="确认删除"
|
|
/>
|
|
</Box>
|
|
);
|
|
}
|