P5: 后台独立应用(MUI)- 13 模块/布局/守卫/复用前台基建
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user