| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- import React, { useState, useEffect, useRef, useCallback } from 'react';
- import styled from 'styled-components';
- import ReactECharts from 'echarts-for-react';
- import * as echarts from 'echarts';
- import { apiGet } from '../../utils/request';
- import { getDefaultDate } from '../../App';
- import { PanelContainer } from '../styled/DashboardStyles';
- import useGlobalRefresh from '../../hooks/useGlobalRefresh';
- // 组件容器样式
- const Container = styled(PanelContainer)`
- width: 100%;
- height:42vh;
- padding: 0;
- position: relative;
- border-radius: 0.2vw;
- `;
- // 组件标题
- const PanelHeader = styled.div`
- position: relative;
- width: 65%;
- height: 1.6vw;
- padding-left: 4%;
- text-align: left;
- line-height: 1.6vw;
- font-size: 0.8vw;
- color: #CCE6FF;
- font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
- background: url('/component_header_bg.png') no-repeat;
- background-size: 100% 100%;
- `;
- // 内容容器
- const ContentBox = styled.div`
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 0.5vw;
- padding-top: 1.2vw;
- height: calc(100% - 1.6vw);
-
- `;
- // 设备标题
- const DeviceTitle = styled.div`
- font-size: 1.2vw;
- color: #CCE6FF;
- margin-bottom: 1vw;
- text-align: left;
- font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
- `;
- // 图表容器
- const ChartContainer = styled.div`
- flex: 1;
- display: flex;
- overflow: hidden;
- background: url('/kucunbeijing.png') no-repeat;
- background-size: 100% 100%;
- background-position: center;
- `;
- // 仪表盘容器
- const GaugeContainer = styled.div`
- width: 40%;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- position: relative;
- padding-right:4%;
- `;
- // 数据项列表
- const DataList = styled.div`
- width: 60%;
- display: flex;
- flex-wrap: wrap;
- `;
- // 数据项
- const DataItem = styled.div`
- width: 50%;
- height: 33.33%;
- display: flex;
- align-items: center;
- padding: 1vw 1vw;
- `;
- // 数据项图标
- const DataIcon = styled.div`
- width: 2vw;
- height: 2vw;
- border-radius: 0.3vw;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-right: 1vw;
- `;
- // 数据项内容
- const DataContent = styled.div`
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: flex-start;
- `;
- // 数据项标题
- const DataTitle = styled.div`
- font-size: 0.8vw;
- color: #CCE6FF;
- margin-bottom: 0.2vw;
- `;
- // 数据项值
- const DataValue = styled.div`
- font-size: 2.3vw;
- color: #FBFDFF;
- font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
- `;
- interface InventoryData {
- totalStock: number;
- equipTotalStock: number;
- equipInputStock: number | null;
- equipOutputStock: number | null;
- equipDrugVarietyNum: number;
- expectedAvailableDays: number;
- equipOutStockDrugVariety: number;
- }
- // 新增类型定义
- type EquipTotalStockItem = {
- name: string;
- value: number;
- color?: string;
- };
- interface InventoryManagementProps {
- selectedEquipCode: string;
- selectedEquipName: string;
- }
- const InventoryManagement: React.FC<InventoryManagementProps> = ({ selectedEquipCode, selectedEquipName }) => {
- const chartRef = useRef<ReactECharts>(null);
- const [data, setData] = useState<InventoryData>({
- totalStock: 0,
- equipTotalStock: 0,
- equipInputStock: null,
- equipOutputStock: null,
- equipDrugVarietyNum: 0,
- expectedAvailableDays: 0,
- equipOutStockDrugVariety: 0
- });
- const [chartSize, setChartSize] = useState({ width: 300, height: 300 });
- const [equipTotalStockList, setEquipTotalStockList] = useState<EquipTotalStockItem[]>([]);
-
- // 监听容器大小变化
- useEffect(() => {
- const updateSize = () => {
- if (chartRef.current) {
- const container = chartRef.current.ele;
- if (container) {
- const newWidth = container.clientWidth;
- const newHeight = container.clientHeight;
-
- // 只有当尺寸真正变化且不为0时才更新状态
- if (newWidth > 0 && newHeight > 0 &&
- (Math.abs(newWidth - chartSize.width) > 5 ||
- Math.abs(newHeight - chartSize.height) > 5)) {
- setChartSize({
- width: newWidth,
- height: newHeight
- });
- }
- }
- }
- };
-
- // 初始更新
- setTimeout(updateSize, 300); // 给图表一些渲染时间
-
- // 监听窗口大小变化
- window.addEventListener('resize', updateSize);
-
- return () => {
- window.removeEventListener('resize', updateSize);
- };
- }, [chartSize.width, chartSize.height]);
-
- const fetchData = useCallback(async () => {
- try {
- // 获取默认日期
- const defaultDate = getDefaultDate();
-
- // 直接使用选中的设备代码
- const equipCode = selectedEquipCode;
-
- // 调用新的库存管理接口
- const response = await apiGet('/equipMana/equipInventoryInfo', {
- params: {
- dateTime: defaultDate,
- equipCode: equipCode
- }
- });
-
- if (response.data && response.data.data) {
- setData({
- totalStock: response.data.data.totalStock || 0,
- equipTotalStock: response.data.data.equipTotalStock || 0,
- equipInputStock: response.data.data.equipInputStock,
- equipOutputStock: response.data.data.equipOutputStock,
- equipDrugVarietyNum: response.data.data.equipDrugVarietyNum || 0,
- expectedAvailableDays: response.data.data.expectedAvailableDays || 0,
- equipOutStockDrugVariety: response.data.data.equipOutStockDrugVariety || 0
- });
- // 修正:适配equipTotalStockList字段
- if (Array.isArray(response.data.data.equipTotalStockList)) {
- const colorArr = ['#4D88FF', '#FFE980', '#4DE1FF', '#8FADCC', '#FFB980', '#B6A2DE', '#5AB1EF', '#FF6666'];
- setEquipTotalStockList(
- response.data.data.equipTotalStockList.map((item: any, idx: number) => ({
- name: item.equipCode ? `设备${item.equipCode}` : `设备${idx+1}`,
- value: item.quantity,
- color: colorArr[idx % colorArr.length]
- }))
- );
- } else {
- setEquipTotalStockList([]);
- }
- }
- } catch (error) {
- console.error('获取库存数据失败:', error);
- // 使用模拟数据(仅用于开发)
- setData({
- totalStock: 19352,
- equipTotalStock: 16856,
- equipInputStock: null,
- equipOutputStock: null,
- equipDrugVarietyNum: 686,
- expectedAvailableDays: 0.0,
- equipOutStockDrugVariety: 0
- });
- setEquipTotalStockList([
- { name: '蓝色部分', value: 25, color: '#4D88FF' },
- { name: '黄色部分', value: 25, color: '#FFE980' },
- { name: '青色部分', value: 25, color: '#4DE1FF' },
- { name: '淡蓝色部分', value: 25, color: '#8FADCC' }
- ]);
- }
- }, [selectedEquipCode]);
- // 组件挂载时获取数据
- useEffect(() => {
- fetchData();
- }, [fetchData]);
- // 当选中的设备代码变化时重新获取数据
- useEffect(() => {
- fetchData();
- }, [selectedEquipCode, fetchData]);
- // 使用全局刷新钩子,指定模块代码'25'
- useGlobalRefresh(fetchData, '25');
- // 获取饼图配置
- const getPieOption = () => {
- // 计算自适应的边框宽度,基于容器大小,但不小于1
- const adaptiveBorderWidth = Math.max(1, Math.min(4, chartSize.width * 0.01));
-
- // 计算字体大小,但设置最小值避免过小
- const titleFontSize = Math.max(12, chartSize.width * 0.05);
- const valueFontSize = Math.max(20, chartSize.width * 0.1);
- const paddingBottom = Math.max(5, chartSize.width * 0.015);
-
- // 饼图缩放比例 - 可以调整这个值来改变饼图大小
- const pieScale = 0.8; // 1.0为标准大小,可增大或减小
-
- // 环宽度控制参数
- const ringWidth = 0.3; // 环宽度,值越大环越宽,值越小环越窄
- const outerRadius = 90 * pieScale; // 外圆半径
- const innerRadius = outerRadius - (outerRadius * ringWidth); // 内圆半径
-
- // 内圆大小控制参数
- const innerCircleScale = 0.45 * pieScale; // 内部黑色圆的大小
-
- // 饼图数据动态生成
- const pieData = (equipTotalStockList && equipTotalStockList.length > 0)
- ? (equipTotalStockList as EquipTotalStockItem[]).map(function(item: EquipTotalStockItem, idx: number) {
- return {
- value: item.value,
- name: item.name,
- itemStyle: { color: item.color || ['#4D88FF', '#FFE980', '#4DE1FF', '#8FADCC'][idx % 4] }
- };
- })
- : [];
- // 如果没有数据,只显示中心label,不渲染pie片
- if (!pieData.length) {
- return {
- backgroundColor: 'transparent',
- series: [
- {
- type: 'pie',
- radius: ['0%', '100%'],
- center: ['50%', '50%'],
- label: {
- position: 'center',
- formatter: function() {
- return '{title|总库存}\n{value|' + data.totalStock + '}';
- },
- rich: {
- title: {
- color: '#CCE6FF',
- fontSize: titleFontSize,
- padding: [0, 0, paddingBottom, 0]
- },
- value: {
- color: '#fff',
- fontSize: valueFontSize,
- fontWeight: 'bold',
- fontFamily: 'DingTalk JinBuTi, PingFang SC, Microsoft YaHei, Arial, sans-serif'
- }
- }
- },
- data: [{ value: 100, name: '背景', itemStyle: { color: '#041529' } }],
- itemStyle: { color: '#041529' },
- labelLine: { show: false },
- emphasis: { scale: false }
- }
- ]
- };
- }
- return {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'item',
- formatter: '{b}: {c} ({d}%)',
- textStyle: { fontSize: Math.max(12, chartSize.width * 0.04) }
- },
- series: [
- {
- type: 'pie',
- radius: [`${innerRadius}%`, `${outerRadius}%`],
- center: ['50%', '50%'],
- startAngle: 0,
- avoidLabelOverlap: false,
- label: { show: false },
- emphasis: { scale: false },
- itemStyle: {
- borderWidth: adaptiveBorderWidth,
- borderColor: '#041529'
- },
- data: pieData
- },
- {
- type: 'pie',
- radius: ['0', `${innerCircleScale * 100}%`],
- center: ['50%', '50%'],
- label: {
- position: 'center',
- formatter: function() {
- return '{title|总库存}\n{value|' + data.totalStock + '}';
- },
- rich: {
- title: {
- color: '#CCE6FF',
- fontSize: titleFontSize,
- padding: [0, 0, paddingBottom, 0]
- },
- value: {
- color: '#fff',
- fontSize: valueFontSize,
- fontWeight: 'bold',
- fontFamily: 'DingTalk JinBuTi, PingFang SC, Microsoft YaHei, Arial, sans-serif'
- }
- }
- },
- itemStyle: {
- color: '#041529'
- },
- data: [
- { value: 100, name: '背景' }
- ]
- }
- ]
- };
- };
- return (
- <Container>
- <PanelHeader>库存管理</PanelHeader>
- <ContentBox>
- <DeviceTitle>设备: {selectedEquipName}</DeviceTitle>
- <ChartContainer>
- <GaugeContainer>
- <ReactECharts
- ref={chartRef}
- option={getPieOption()}
- style={{ height: '100%', width: '100%' }}
- opts={{ renderer: 'canvas' }}
- notMerge={true}
- />
- </GaugeContainer>
-
- <DataList>
- <DataItem>
- <DataIcon>
- <img src="/kucunicon.png" alt="库存图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>库存</DataTitle>
- <DataValue>{data.equipTotalStock}</DataValue>
- </DataContent>
- </DataItem>
-
- <DataItem>
- <DataIcon>
- <img src="/zaikupinzgingshuicon.png" alt="在库品种数图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>设备在库品种数</DataTitle>
- <DataValue>{data.equipDrugVarietyNum}</DataValue>
- </DataContent>
- </DataItem>
-
- <DataItem>
- <DataIcon>
- <img src="/shebeirukuheshuicon.png" alt="设备入库盒数图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>设备入库盒数</DataTitle>
- <DataValue>{data.equipInputStock || 0}</DataValue>
- </DataContent>
- </DataItem>
-
- <DataItem>
- <DataIcon>
- <img src="/shebeichukuheshuicon.png" alt="设备出库盒数图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>设备出库盒数</DataTitle>
- <DataValue>{data.equipOutputStock || 0}</DataValue>
- </DataContent>
- </DataItem>
-
- <DataItem>
- <DataIcon>
- <img src="/yugexiaohaotianshuicon.png" alt="预估可消耗天数图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>预估可消耗天数</DataTitle>
- <DataValue>{data.expectedAvailableDays}</DataValue>
- </DataContent>
- </DataItem>
-
- <DataItem>
- <DataIcon>
- <img src="/queyaolvicon.png" alt="缺药率图标" style={{ width: '2.5vw', height: '2.5vw' }} />
- </DataIcon>
- <DataContent>
- <DataTitle>缺药率>90%品种数</DataTitle>
- <DataValue>{data.equipOutStockDrugVariety}</DataValue>
- </DataContent>
- </DataItem>
- </DataList>
- </ChartContainer>
- </ContentBox>
- </Container>
- );
- };
- export default InventoryManagement;
|