feat(耗材管理): 添加耗材操作记录时间线功能
新增耗材操作记录时间线组件,展示耗材的入库、出库、调整等操作历史 包含操作类型、数量变化、操作人等信息,并支持分页加载更多记录
This commit is contained in:
@@ -0,0 +1,497 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Timeline,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Statistic,
|
||||||
|
Spin,
|
||||||
|
Empty,
|
||||||
|
Tooltip,
|
||||||
|
Badge,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
ClockCircleOutlined,
|
||||||
|
InboxOutlined,
|
||||||
|
ExportOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
SwapOutlined,
|
||||||
|
UploadOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
EnvironmentOutlined,
|
||||||
|
BarcodeOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import axios from 'axios';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import CloseButton from './CloseButton';
|
||||||
|
import { designTokens } from '../config/theme';
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
const getOperationConfig = type => {
|
||||||
|
const config = {
|
||||||
|
in: {
|
||||||
|
color: designTokens.colors.success.main,
|
||||||
|
bg: designTokens.colors.success.bg,
|
||||||
|
icon: <InboxOutlined />,
|
||||||
|
label: '入库',
|
||||||
|
prefix: '+',
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
color: designTokens.colors.error.main,
|
||||||
|
bg: designTokens.colors.error.bg,
|
||||||
|
icon: <ExportOutlined />,
|
||||||
|
label: '出库',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
color: designTokens.colors.primary.main,
|
||||||
|
bg: designTokens.colors.primary.bg,
|
||||||
|
icon: <PlusOutlined />,
|
||||||
|
label: '创建',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
color: designTokens.colors.warning.main,
|
||||||
|
bg: designTokens.colors.warning.bg,
|
||||||
|
icon: <EditOutlined />,
|
||||||
|
label: '更新',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
color: designTokens.colors.error.main,
|
||||||
|
bg: designTokens.colors.error.bg,
|
||||||
|
icon: <DeleteOutlined />,
|
||||||
|
label: '删除',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
adjust: {
|
||||||
|
color: designTokens.colors.purple.main,
|
||||||
|
bg: '#f5f3ff',
|
||||||
|
icon: <SwapOutlined />,
|
||||||
|
label: '调整',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
import: {
|
||||||
|
color: designTokens.colors.info.main,
|
||||||
|
bg: designTokens.colors.info.bg,
|
||||||
|
icon: <UploadOutlined />,
|
||||||
|
label: '导入',
|
||||||
|
prefix: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return config[type] || {
|
||||||
|
color: designTokens.colors.neutral[500],
|
||||||
|
bg: designTokens.colors.neutral[100],
|
||||||
|
icon: <ClockCircleOutlined />,
|
||||||
|
label: type,
|
||||||
|
prefix: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const TimelineItem = ({ log, isFirst, isLast }) => {
|
||||||
|
const config = getOperationConfig(log.operationType);
|
||||||
|
const isPositive = log.quantity > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
borderRadius: designTokens.borderRadius.md,
|
||||||
|
borderLeft: `3px solid ${config.color}`,
|
||||||
|
background: '#fff',
|
||||||
|
boxShadow: designTokens.shadows.sm,
|
||||||
|
marginBottom: isLast ? 0 : '12px',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
}}
|
||||||
|
bodyStyle={{ padding: '12px 16px' }}
|
||||||
|
>
|
||||||
|
<Row gutter={[12, 8]} align="middle">
|
||||||
|
<Col span={24}>
|
||||||
|
<Space size="small" wrap>
|
||||||
|
<Tag
|
||||||
|
color={config.color}
|
||||||
|
style={{
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '2px 8px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{config.icon} {config.label}
|
||||||
|
</Tag>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||||
|
{dayjs(log.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</Text>
|
||||||
|
<Tooltip title={dayjs(log.createdAt).fromNow()}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '11px', cursor: 'help' }}>
|
||||||
|
({dayjs(log.createdAt).fromNow()})
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={[12, 8]} style={{ marginTop: '8px' }}>
|
||||||
|
<Col xs={24} sm={12} md={8}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
padding: '6px 10px',
|
||||||
|
background: config.bg,
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||||
|
变动数量
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
color: isPositive ? designTokens.colors.success.main : designTokens.colors.error.main,
|
||||||
|
fontSize: '14px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{config.prefix}
|
||||||
|
{log.quantity}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={8}>
|
||||||
|
<div style={{ padding: '6px 10px' }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px', display: 'block' }}>
|
||||||
|
库存变化
|
||||||
|
</Text>
|
||||||
|
<Space size="small">
|
||||||
|
<Text style={{ fontSize: '13px' }}>{log.previousStock}</Text>
|
||||||
|
<Text type="secondary">→</Text>
|
||||||
|
<Text strong style={{ fontSize: '13px' }}>
|
||||||
|
{log.currentStock}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={8}>
|
||||||
|
<div style={{ padding: '6px 10px' }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px', display: 'block' }}>
|
||||||
|
操作人
|
||||||
|
</Text>
|
||||||
|
<Space size="small">
|
||||||
|
<UserOutlined style={{ color: designTokens.colors.neutral[400] }} />
|
||||||
|
<Text style={{ fontSize: '13px' }}>{log.operator || '系统'}</Text>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{(log.reason || log.notes) && (
|
||||||
|
<div style={{ marginTop: '8px', padding: '8px 10px', background: '#f8fafc', borderRadius: '6px' }}>
|
||||||
|
{log.reason && (
|
||||||
|
<div style={{ marginBottom: log.notes ? '4px' : 0 }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||||
|
原因:
|
||||||
|
</Text>
|
||||||
|
<Text style={{ fontSize: '12px', marginLeft: '4px' }}>{log.reason}</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{log.notes && (
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||||
|
备注:
|
||||||
|
</Text>
|
||||||
|
<Text style={{ fontSize: '12px', marginLeft: '4px' }}>{log.notes}</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{log.snList && log.snList.length > 0 && (
|
||||||
|
<div style={{ marginTop: '8px' }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '11px', display: 'block', marginBottom: '4px' }}>
|
||||||
|
<BarcodeOutlined style={{ marginRight: '4px' }} />
|
||||||
|
SN序列号 ({log.snList.length}个)
|
||||||
|
</Text>
|
||||||
|
<Space size="small" wrap>
|
||||||
|
{log.snList.slice(0, 5).map((sn, idx) => (
|
||||||
|
<Tag key={idx} color="purple" style={{ fontSize: '11px', borderRadius: '4px' }}>
|
||||||
|
{sn}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
{log.snList.length > 5 && (
|
||||||
|
<Tag style={{ fontSize: '11px', borderRadius: '4px' }}>...</Tag>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConsumableTimelineModal = ({ visible, consumable, onClose }) => {
|
||||||
|
const [logs, setLogs] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 });
|
||||||
|
const [stats, setStats] = useState({ totalIn: 0, totalOut: 0, netChange: 0 });
|
||||||
|
|
||||||
|
const fetchLogs = useCallback(async (page = 1) => {
|
||||||
|
if (!consumable?.consumableId) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/consumables/logs', {
|
||||||
|
params: {
|
||||||
|
consumableId: consumable.consumableId,
|
||||||
|
page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedLogs = (response.data.logs || []).filter(
|
||||||
|
log => log.operationType !== 'update'
|
||||||
|
);
|
||||||
|
setLogs(page === 1 ? fetchedLogs : [...logs, ...fetchedLogs]);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
total: response.data.total - (response.data.logs || []).filter(log => log.operationType === 'update').length,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const totalIn = fetchedLogs
|
||||||
|
.filter(log => log.operationType === 'in')
|
||||||
|
.reduce((sum, log) => sum + Math.abs(log.quantity), 0);
|
||||||
|
const totalOut = fetchedLogs
|
||||||
|
.filter(log => log.operationType === 'out')
|
||||||
|
.reduce((sum, log) => sum + Math.abs(log.quantity), 0);
|
||||||
|
setStats({
|
||||||
|
totalIn,
|
||||||
|
totalOut,
|
||||||
|
netChange: totalIn - totalOut,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取耗材操作记录失败:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [consumable, pagination.pageSize, logs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && consumable?.consumableId) {
|
||||||
|
setLogs([]);
|
||||||
|
setPagination(prev => ({ ...prev, current: 1 }));
|
||||||
|
fetchLogs(1);
|
||||||
|
}
|
||||||
|
}, [visible, consumable]);
|
||||||
|
|
||||||
|
const handleLoadMore = () => {
|
||||||
|
if (!loading && logs.length < pagination.total) {
|
||||||
|
fetchLogs(pagination.current + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!consumable) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
closeIcon={<CloseButton />}
|
||||||
|
footer={null}
|
||||||
|
width={900}
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<ClockCircleOutlined style={{ color: designTokens.colors.primary.main }} />
|
||||||
|
<span>耗材操作记录</span>
|
||||||
|
<Tag color="blue" style={{ borderRadius: '6px' }}>
|
||||||
|
{consumable.name}
|
||||||
|
</Tag>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px', fontWeight: 'normal' }}>
|
||||||
|
({consumable.consumableId})
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
style={{ top: 20 }}
|
||||||
|
bodyStyle={{ maxHeight: 'calc(100vh - 200px)', overflowY: 'auto', padding: '16px 24px' }}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
borderRadius: designTokens.borderRadius.md,
|
||||||
|
background: `linear-gradient(135deg, ${designTokens.colors.primary.bg} 0%, #fff 100%)`,
|
||||||
|
border: `1px solid ${designTokens.colors.primary.light}20`,
|
||||||
|
}}
|
||||||
|
bodyStyle={{ padding: '16px' }}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Statistic
|
||||||
|
title={
|
||||||
|
<Space size="small">
|
||||||
|
<InboxOutlined style={{ color: designTokens.colors.success.main }} />
|
||||||
|
<span>总入库</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
value={stats.totalIn}
|
||||||
|
valueStyle={{ color: designTokens.colors.success.main, fontSize: '24px' }}
|
||||||
|
suffix={consumable.unit || '个'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Statistic
|
||||||
|
title={
|
||||||
|
<Space size="small">
|
||||||
|
<ExportOutlined style={{ color: designTokens.colors.error.main }} />
|
||||||
|
<span>总出库</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
value={stats.totalOut}
|
||||||
|
valueStyle={{ color: designTokens.colors.error.main, fontSize: '24px' }}
|
||||||
|
suffix={consumable.unit || '个'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Statistic
|
||||||
|
title={
|
||||||
|
<Space size="small">
|
||||||
|
<SwapOutlined style={{ color: designTokens.colors.purple.main }} />
|
||||||
|
<span>净变化</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
value={stats.netChange}
|
||||||
|
valueStyle={{
|
||||||
|
color: stats.netChange >= 0 ? designTokens.colors.success.main : designTokens.colors.error.main,
|
||||||
|
fontSize: '24px',
|
||||||
|
}}
|
||||||
|
prefix={stats.netChange >= 0 ? '+' : ''}
|
||||||
|
suffix={consumable.unit || '个'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Statistic
|
||||||
|
title={
|
||||||
|
<Space size="small">
|
||||||
|
<ClockCircleOutlined style={{ color: designTokens.colors.info.main }} />
|
||||||
|
<span>当前库存</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
value={consumable.currentStock || 0}
|
||||||
|
valueStyle={{ color: designTokens.colors.info.main, fontSize: '24px' }}
|
||||||
|
suffix={consumable.unit || '个'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
padding: '12px 16px',
|
||||||
|
background: designTokens.colors.neutral[50],
|
||||||
|
borderRadius: designTokens.borderRadius.md,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Badge status="processing" />
|
||||||
|
<Text type="secondary">
|
||||||
|
共 {pagination.total} 条操作记录
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Tag color="green" style={{ borderRadius: '4px' }}>入库</Tag>
|
||||||
|
<Tag color="red" style={{ borderRadius: '4px' }}>出库</Tag>
|
||||||
|
<Tag color="blue" style={{ borderRadius: '4px' }}>创建</Tag>
|
||||||
|
<Tag color="purple" style={{ borderRadius: '4px' }}>调整</Tag>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading && logs.length === 0 ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||||
|
<Spin size="large" />
|
||||||
|
<Text type="secondary" style={{ display: 'block', marginTop: '16px' }}>
|
||||||
|
加载中...
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
) : logs.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
description={
|
||||||
|
<Text type="secondary">
|
||||||
|
暂无操作记录
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
style={{ padding: '40px' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Timeline
|
||||||
|
mode="left"
|
||||||
|
items={logs.map((log, index) => ({
|
||||||
|
key: log.id || index,
|
||||||
|
color: getOperationConfig(log.operationType).color,
|
||||||
|
dot: (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: getOperationConfig(log.operationType).bg,
|
||||||
|
border: `2px solid ${getOperationConfig(log.operationType).color}`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: getOperationConfig(log.operationType).color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getOperationConfig(log.operationType).icon}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<TimelineItem
|
||||||
|
log={log}
|
||||||
|
isFirst={index === 0}
|
||||||
|
isLast={index === logs.length - 1}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{logs.length < pagination.total && (
|
||||||
|
<div style={{ textAlign: 'center', marginTop: '16px' }}>
|
||||||
|
<Tag
|
||||||
|
onClick={handleLoadMore}
|
||||||
|
style={{
|
||||||
|
cursor: loading ? 'not-allowed' : 'pointer',
|
||||||
|
padding: '8px 24px',
|
||||||
|
borderRadius: '20px',
|
||||||
|
background: loading ? designTokens.colors.neutral[100] : designTokens.colors.primary.bg,
|
||||||
|
color: loading ? designTokens.colors.neutral[400] : designTokens.colors.primary.main,
|
||||||
|
border: `1px solid ${loading ? designTokens.colors.neutral[200] : designTokens.colors.primary.light}`,
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '加载中...' : `加载更多 (${logs.length}/${pagination.total})`}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConsumableTimelineModal;
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
AutoComplete,
|
AutoComplete,
|
||||||
Avatar,
|
Avatar,
|
||||||
Statistic,
|
Statistic,
|
||||||
|
Timeline,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
@@ -56,12 +57,14 @@ import {
|
|||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
DesktopOutlined,
|
DesktopOutlined,
|
||||||
|
HistoryOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { designTokens } from '../config/theme';
|
import { designTokens } from '../config/theme';
|
||||||
import CloseButton from '../components/CloseButton';
|
import CloseButton from '../components/CloseButton';
|
||||||
|
import ConsumableTimelineModal from '../components/ConsumableTimelineModal';
|
||||||
import {
|
import {
|
||||||
inputStyles,
|
inputStyles,
|
||||||
selectStyles,
|
selectStyles,
|
||||||
@@ -172,6 +175,8 @@ function ConsumableManagement() {
|
|||||||
const [showFieldMapping, setShowFieldMapping] = useState(false);
|
const [showFieldMapping, setShowFieldMapping] = useState(false);
|
||||||
const [pollingInterval, setPollingInterval] = useState(null);
|
const [pollingInterval, setPollingInterval] = useState(null);
|
||||||
const importProgressRef = React.useRef(null);
|
const importProgressRef = React.useRef(null);
|
||||||
|
const [timelineModalVisible, setTimelineModalVisible] = useState(false);
|
||||||
|
const [selectedConsumable, setSelectedConsumable] = useState(null);
|
||||||
|
|
||||||
// 获取全部耗材(用于扫码入库下拉框,不受分页限制)
|
// 获取全部耗材(用于扫码入库下拉框,不受分页限制)
|
||||||
const fetchAllConsumablesForScan = useCallback(async () => {
|
const fetchAllConsumablesForScan = useCallback(async () => {
|
||||||
@@ -1482,10 +1487,21 @@ function ConsumableManagement() {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 200,
|
width: 240,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space size="small">
|
<Space size="small">
|
||||||
|
<Tooltip title="查看记录">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<HistoryOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedConsumable(record);
|
||||||
|
setTimelineModalVisible(true);
|
||||||
|
}}
|
||||||
|
style={{ color: designTokens.colors.info.main }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip title="编辑">
|
<Tooltip title="编辑">
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
@@ -4382,6 +4398,15 @@ function ConsumableManagement() {
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<ConsumableTimelineModal
|
||||||
|
visible={timelineModalVisible}
|
||||||
|
consumable={selectedConsumable}
|
||||||
|
onClose={() => {
|
||||||
|
setTimelineModalVisible(false);
|
||||||
|
setSelectedConsumable(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user