feat(耗材管理): 实现耗材日志归档功能

This commit is contained in:
zhang1106
2026-02-24 13:20:26 +08:00
parent b810e2cca1
commit 8a0c85e502
5 changed files with 595 additions and 31 deletions
+125 -18
View File
@@ -16,6 +16,10 @@ import {
Form,
Tooltip,
Timeline,
Row,
Col,
Statistic,
Divider,
} from 'antd';
import {
HistoryOutlined,
@@ -57,6 +61,11 @@ function ConsumableLogs() {
const [form] = Form.useForm();
const fileInputRef = useRef(null);
// 归档详情弹窗状态
const [archiveModalVisible, setArchiveModalVisible] = useState(false);
const [currentArchive, setCurrentArchive] = useState(null);
const [archiveLoading, setArchiveLoading] = useState(false);
const fetchLogs = async (page = 1, pageSize = 10, currentFilters = filters) => {
try {
setLoading(true);
@@ -121,22 +130,13 @@ function ConsumableLogs() {
dataIndex: 'consumableId',
key: 'consumableId',
width: 150,
render: (value, record) => (
<Space>
<code>{value}</code>
{record.isConsumableDeleted && (
<Tooltip title="该耗材已被删除">
<Tag color="red">已删除</Tag>
</Tooltip>
)}
</Space>
),
render: value => <code>{value}</code>,
},
{
title: '耗材名称',
dataIndex: 'consumableName',
key: 'consumableName',
width: 150,
width: 180,
render: (value, record) => (
<Tooltip
title={
@@ -151,7 +151,12 @@ function ConsumableLogs() {
) : null
}
>
<span>{value}</span>
<Space direction="vertical" size={0}>
<span>{value}</span>
{record.isConsumableDeleted && (
<Tag color="red" size="small">已删除</Tag>
)}
</Space>
</Tooltip>
),
},
@@ -159,8 +164,23 @@ function ConsumableLogs() {
title: '操作类型',
dataIndex: 'operationType',
key: 'operationType',
width: 100,
render: type => getOperationTag(type),
width: 120,
render: (type, record) => (
<Space direction="vertical" size={0}>
{getOperationTag(type)}
{type === 'delete' && record.relatedId && (
<Tooltip title="点击查看归档详情">
<Tag
color="blue"
style={{ cursor: 'pointer', fontSize: '11px' }}
onClick={() => handleViewArchive(record.relatedId)}
>
已归档
</Tag>
</Tooltip>
)}
</Space>
),
},
{
title: '变动数量',
@@ -209,8 +229,19 @@ function ConsumableLogs() {
dataIndex: 'notes',
key: 'notes',
width: 200,
render: value => value || '-',
ellipsis: true,
render: value => (
<Tooltip title={value || '-'}>
<span style={{
display: 'inline-block',
maxWidth: '180px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
{value || '-'}
</span>
</Tooltip>
),
},
{
title: '操作',
@@ -430,6 +461,21 @@ function ConsumableLogs() {
setEditModalVisible(true);
};
// 查看归档详情
const handleViewArchive = async archiveId => {
setArchiveModalVisible(true);
setArchiveLoading(true);
try {
const response = await axios.get(`/api/consumables/archives/${archiveId}`);
setCurrentArchive(response.data);
} catch (error) {
message.error('获取归档详情失败');
console.error('获取归档详情失败:', error);
} finally {
setArchiveLoading(false);
}
};
// 提交编辑
const handleEditSubmit = async values => {
if (!currentLog) return;
@@ -693,8 +739,8 @@ function ConsumableLogs() {
)}
{item.consumableSnapshot && (
<p>
<strong>快照信息:</strong> 分类:{item.consumableSnapshot.category || '-'} |
单位:{item.consumableSnapshot.unit || '-'} |
<strong>快照信息:</strong> 分类:{item.consumableSnapshot.category || '-'} |
单位:{item.consumableSnapshot.unit || '-'} |
单价:{item.consumableSnapshot.unitPrice || '-'}
</p>
)}
@@ -720,6 +766,67 @@ function ConsumableLogs() {
</Timeline>
)}
</Modal>
{/* 归档详情弹窗 */}
<Modal
title="归档记录详情"
open={archiveModalVisible}
onCancel={() => {
setArchiveModalVisible(false);
setCurrentArchive(null);
}}
footer={null}
width={600}
>
{archiveLoading ? (
<div style={{ textAlign: 'center', padding: '40px' }}>加载中...</div>
) : !currentArchive ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#888' }}>
无法获取归档信息
</div>
) : (
<div>
<Card size="small" title="基本信息" style={{ marginBottom: 16 }}>
<p><strong>归档ID:</strong> <code>{currentArchive.archiveId}</code></p>
<p><strong>耗材ID:</strong> <code>{currentArchive.consumableId}</code></p>
<p><strong>耗材名称:</strong> {currentArchive.consumableName}</p>
<p><strong>删除人:</strong> {currentArchive.deletedBy}</p>
<p><strong>删除时间:</strong> {dayjs(currentArchive.deletedAt).format('YYYY-MM-DD HH:mm:ss')}</p>
<p><strong>删除原因:</strong> {currentArchive.deleteReason || '-'}</p>
</Card>
<Card size="small" title="操作统计" style={{ marginBottom: 16 }}>
<Row gutter={16}>
<Col span={8}>
<Statistic title="总操作数" value={currentArchive.totalOperations} />
</Col>
<Col span={8}>
<Statistic title="总入库" value={currentArchive.totalInQuantity} />
</Col>
<Col span={8}>
<Statistic title="总出库" value={currentArchive.totalOutQuantity} />
</Col>
</Row>
<Divider style={{ margin: '12px 0' }} />
<p><strong>首次操作:</strong> {currentArchive.firstOperationAt ? dayjs(currentArchive.firstOperationAt).format('YYYY-MM-DD HH:mm:ss') : '-'}</p>
<p><strong>最后操作:</strong> {currentArchive.lastOperationAt ? dayjs(currentArchive.lastOperationAt).format('YYYY-MM-DD HH:mm:ss') : '-'}</p>
<p><strong>删除时库存:</strong> {currentArchive.finalStock}</p>
</Card>
{currentArchive.consumableSnapshot && (
<Card size="small" title="耗材快照">
<p><strong>分类:</strong> {currentArchive.consumableSnapshot.category || '-'}</p>
<p><strong>单位:</strong> {currentArchive.consumableSnapshot.unit || '-'}</p>
<p><strong>单价:</strong> {currentArchive.consumableSnapshot.unitPrice || '-'}</p>
<p><strong>供应商:</strong> {currentArchive.consumableSnapshot.supplier || '-'}</p>
<p><strong>位置:</strong> {currentArchive.consumableSnapshot.location || '-'}</p>
<p><strong>最小库存:</strong> {currentArchive.consumableSnapshot.minStock || '-'}</p>
<p><strong>最大库存:</strong> {currentArchive.consumableSnapshot.maxStock || '-'}</p>
</Card>
)}
</div>
)}
</Modal>
</div>
);
}