import React, { useState, useEffect } from 'react';
import {
Card,
Row,
Col,
Statistic,
Table,
Tag,
DatePicker,
Space,
Select,
Progress,
message,
Button,
} from 'antd';
import {
InboxOutlined,
ExportOutlined,
WarningOutlined,
DollarOutlined,
ShoppingCartOutlined,
ExclamationCircleOutlined,
PlusOutlined,
BarChartOutlined,
DownloadOutlined,
} from '@ant-design/icons';
import axios from 'axios';
import dayjs from 'dayjs';
const { RangePicker } = DatePicker;
const { Option } = Select;
const designTokens = {
colors: {
primary: {
main: '#667eea',
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
light: '#8b9ff0',
},
success: {
main: '#10b981',
gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
},
warning: {
main: '#f59e0b',
gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
},
error: {
main: '#ef4444',
gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
},
text: {
primary: '#1e293b',
secondary: '#64748b',
tertiary: '#94a3b8',
inverse: '#ffffff',
},
background: {
primary: '#ffffff',
secondary: '#f8fafc',
tertiary: '#f1f5f9',
},
border: {
light: '#e2e8f0',
medium: '#cbd5e1',
dark: '#94a3b8',
},
},
shadows: {
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
},
borderRadius: {
small: '6px',
medium: '10px',
large: '16px',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
},
};
const pageContainerStyle = {
minHeight: '100vh',
background: designTokens.colors.background.secondary,
padding: designTokens.spacing.lg,
};
const headerStyle = {
marginBottom: designTokens.spacing.lg,
padding: `${designTokens.spacing.lg}px ${designTokens.spacing.xl}px`,
background: designTokens.colors.background.primary,
borderRadius: designTokens.borderRadius.large,
boxShadow: designTokens.shadows.small,
border: `1px solid ${designTokens.colors.border.light}`,
};
const titleRowStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: designTokens.spacing.md,
flexWrap: 'wrap',
gap: designTokens.spacing.md,
};
const titleStyle = {
display: 'flex',
alignItems: 'center',
gap: designTokens.spacing.sm,
fontSize: '20px',
fontWeight: '600',
color: designTokens.colors.text.primary,
};
const statsRowStyle = {
display: 'flex',
gap: designTokens.spacing.md,
flexWrap: 'wrap',
};
const statCardStyle = {
background: designTokens.colors.background.primary,
borderRadius: designTokens.borderRadius.medium,
padding: `${designTokens.spacing.md}px ${designTokens.spacing.lg}px`,
boxShadow: designTokens.shadows.small,
border: `1px solid ${designTokens.colors.border.light}`,
minWidth: '180px',
flex: 1,
};
const statCardTextStyle = {
fontSize: '13px',
color: designTokens.colors.text.secondary,
marginBottom: designTokens.spacing.xs,
};
const statCardValueStyle = {
fontSize: '28px',
fontWeight: '600',
color: designTokens.colors.text.primary,
};
const statCardIconStyle = color => ({
fontSize: '28px',
color: color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '48px',
height: '48px',
borderRadius: designTokens.borderRadius.medium,
background: `${color}12`,
});
const panelStyle = {
background: designTokens.colors.background.primary,
borderRadius: designTokens.borderRadius.large,
boxShadow: designTokens.shadows.small,
border: `1px solid ${designTokens.colors.border.light}`,
overflow: 'hidden',
};
const panelHeaderStyle = {
padding: `${designTokens.spacing.md}px ${designTokens.spacing.lg}px`,
borderBottom: `1px solid ${designTokens.colors.border.light}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
};
const panelTitleStyle = {
fontSize: '15px',
fontWeight: '600',
color: designTokens.colors.text.primary,
display: 'flex',
alignItems: 'center',
gap: designTokens.spacing.sm,
};
const panelBodyStyle = {
padding: designTokens.spacing.lg,
};
const actionButtonStyle = {
height: '36px',
padding: `0 ${designTokens.spacing.md}px`,
borderRadius: designTokens.borderRadius.small,
fontSize: '13px',
display: 'flex',
alignItems: 'center',
gap: designTokens.spacing.xs,
};
const primaryActionStyle = {
...actionButtonStyle,
background: designTokens.colors.primary.gradient,
border: 'none',
color: '#ffffff',
boxShadow: designTokens.shadows.small,
};
function ConsumableStatistics() {
const [summary, setSummary] = useState({ total: 0, lowStock: 0, totalValue: 0, byCategory: [] });
const [lowStockItems, setLowStockItems] = useState([]);
const [stats, setStats] = useState({
inCount: 0,
outCount: 0,
inQuantity: 0,
outQuantity: 0,
recentRecords: [],
});
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState([]);
const [categoryFilter, setCategoryFilter] = useState(null);
const fetchSummary = async () => {
try {
const response = await axios.get('/api/consumables/statistics/summary');
setSummary(response.data);
} catch (error) {
message.error('获取统计信息失败');
}
};
const fetchLowStock = async () => {
try {
const response = await axios.get('/api/consumables/low-stock');
setLowStockItems(response.data);
} catch (error) {
message.error('获取低库存信息失败');
}
};
const fetchInOutStats = async () => {
try {
const params = {};
if (dateRange && dateRange.length === 2) {
params.startDate = dateRange[0].toISOString();
params.endDate = dateRange[1].toISOString();
}
if (categoryFilter) {
params.category = categoryFilter;
}
const response = await axios.get('/api/consumable-records/statistics', { params });
setStats(response.data);
} catch (error) {
message.error('获取出入库统计失败');
}
};
useEffect(() => {
const loadData = async () => {
setLoading(true);
await Promise.all([fetchSummary(), fetchLowStock(), fetchInOutStats()]);
setLoading(false);
};
loadData();
}, []);
useEffect(() => {
fetchInOutStats();
}, [dateRange, categoryFilter]);
const lowStockColumns = [
{
title: '耗材名称',
dataIndex: 'name',
key: 'name',
width: 150,
render: text => (
{text}
),
},
{
title: '分类',
dataIndex: 'category',
key: 'category',
width: 120,
render: category => (