import React, { useEffect, useState, useRef } from 'react'; import * as echarts from 'echarts'; import styled from 'styled-components'; import { apiGet } from '../../utils/request'; import { useDefaultDate, getSelectedMonthRange } from '../../App'; import useGlobalRefresh from '../../hooks/useGlobalRefresh'; // 组件容器样式 - 与第一个模块保持一致 const ChartWrapper = styled.div` width: 100%; height: 100%; position: relative; overflow: hidden; border: 1px solid #2980B9; `; // 组件标题 - 使用与其他模块相同的样式 const PanelHeader = styled.div` position: relative; width: 95%; height: 1.6vw; padding-left: 5.5%; text-align: left; line-height: 1.6vw; font-size: 0.8vw; color: #E6F2FF; font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif; background: url('/component_header_bg.png') no-repeat; background-size: 100% 100%; `; // 图表容器 - 统一与其他模块一致的布局 const ChartContainer = styled.div` width: 100%; height: calc(100% - 1.6vw); padding: 0.5vw; box-sizing: border-box; overflow: hidden; `; // 数据接口类型 - 根据新接口字段定义 interface WeeklyDrugData { weekDay: number; // 星期(数字) weekDayLabel: string; // 星期(文本) totalCount: number; // 日均发药盒数 machineCount: number; // 日均机发盒数 directSendRate: number; // 机发率 } // 这个函数已不再需要,将在fetchData中直接使用getSelectedMonthRange const DailyMedicineQuantityChart: React.FC = () => { const chartRef = useRef(null); const chartInstance = useRef(null); const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const { defaultDateTime, selectedMonth } = useDefaultDate(); // 获取数据 const fetchData = async () => { try { setLoading(true); const { startTime, endTime } = getSelectedMonthRange(defaultDateTime, selectedMonth); const response = await apiGet('/stageStats/weeklyDrugOverview', { params: { startTime, endTime } }); if (response.data && response.data.data && Array.isArray(response.data.data)) { setData(response.data.data); } else { console.warn('每周药品概况 - 接口返回格式不符合预期:', response.data); // 设置为空数组,不使用模拟数据 setData([]); } } catch (error) { console.error('获取每周药品概况数据失败:', error); // 设置为空数组,不使用模拟数据 setData([]); } finally { setLoading(false); } }; // 初始化图表 - 双柱状图+折线图 const initChart = () => { if (!chartRef.current || data.length === 0) return; // 销毁已存在的图表实例 if (chartInstance.current) { chartInstance.current.dispose(); } // 创建新的图表实例 chartInstance.current = echarts.init(chartRef.current); const option = { legend: { data: ['日均发药盒数', '日均机发盒数', '机发率'], top: '2%', left: 'center', textStyle: { color: '#CCE6FF', fontSize: '0.6vw' }, itemWidth: 10, itemHeight: 8, itemGap: 20 }, grid: { left: '0%', right: '0%', top: '15%', bottom: '5%', containLabel: true }, xAxis: { type: 'category', data: data.map(item => item.weekDayLabel), axisLine: { lineStyle: { color: '#1F324D' } }, axisLabel: { color: '#CCE6FF', fontSize: '0.6vw', margin: 5 }, axisTick: { show: false } }, yAxis: [ { type: 'value', position: 'left', max: function() { // 获取两个柱状图数据的最大值 const maxValue = Math.max( ...data.map(item => Math.max(item.totalCount, item.machineCount)) ); // 如果最大值很小(小于100),设置max为最大值的1.5倍 if (maxValue < 100) { return Math.ceil(maxValue * 1.5); } // 如果最大值较大,设置为1.2倍并向上取整到合适的数值 const scaledMax = maxValue * 1.2; // 根据数值大小选择合适的取整方式 if (scaledMax < 1000) { return Math.ceil(scaledMax / 100) * 100; } else if (scaledMax < 10000) { return Math.ceil(scaledMax / 500) * 500; } else { return Math.ceil(scaledMax / 1000) * 1000; } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#CCE6FF', fontSize: '0.55vw', }, splitLine: { lineStyle: { color: '#1F324D', type: 'solid' } } }, { type: 'value', position: 'right', min: function() { // 接口返回的是小数(0.65表示65%),动态计算显示范围 const values = data.map(item => item.directSendRate); const minValue = Math.min(...values); const maxValue = Math.max(...values); const range = maxValue - minValue; // 如果数据范围很小,扩大显示范围 if (range < 0.2) { return Math.max(0, minValue - 0.1); } else { return Math.max(0, minValue - range * 0.2); } }, max: function() { const values = data.map(item => item.directSendRate); const minValue = Math.min(...values); const maxValue = Math.max(...values); const range = maxValue - minValue; // 如果数据范围很小,扩大显示范围 if (range < 0.2) { return Math.min(1, maxValue + 0.1); } else { return Math.min(1, maxValue + range * 0.2); } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#CCE6FF', fontSize: '0.55vw', formatter: function(value: number) { return (value * 100).toFixed(0) + '%'; } }, splitLine: { show: false } } ], series: [ { name: '日均发药盒数', type: 'bar', yAxisIndex: 0, data: data.map(item => item.totalCount), barWidth: '20%', barGap: '10%', itemStyle: { borderRadius: [3, 3, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#66B3FF' }, { offset: 1, color: 'rgba(102,179,255,0)' } ]), }, emphasis: { itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#66B3FF' }, { offset: 1, color: 'rgba(102,179,255,0.2)' } ]) } }, label: { show: false } }, { name: '日均机发盒数', type: 'bar', yAxisIndex: 0, data: data.map(item => item.machineCount), barWidth: '20%', itemStyle: { borderRadius: [3, 3, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#F3F9FF' }, { offset: 1, color: 'rgba(243,249,255,0)' } ]), }, emphasis: { itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#F3F9FF' }, { offset: 1, color: 'rgba(243,249,255,0.2)' } ]) } }, label: { show: false } }, { name: '机发率', type: 'line', yAxisIndex: 1, data: data.map(item => item.directSendRate), lineStyle: { color: '#99FFAA', width: 2 }, itemStyle: { color: '#99FFAA' }, symbol: 'none', smooth: true } ], tooltip: { trigger: 'axis', backgroundColor: 'rgba(15, 35, 85, 0.95)', borderColor: 'rgba(0, 212, 255, 0.6)', borderWidth: 1, textStyle: { color: '#FFFFFF', fontFamily: 'DingTalk JinBuTi', fontSize: '0.6vw' }, formatter: (params: any) => { const totalData = params.find((p: any) => p.seriesName === '日均发药盒数'); const machineData = params.find((p: any) => p.seriesName === '日均机发盒数'); const lineData = params.find((p: any) => p.seriesType === 'line'); const dataItem = data[totalData.dataIndex]; return `
${totalData.name}
日均发药盒数: ${dataItem.totalCount}
日均机发盒数: ${dataItem.machineCount}
机发率: ${(dataItem.directSendRate * 100).toFixed(1)}%
`; } } }; chartInstance.current.setOption(option); }; // 监听数据变化,重新渲染图表 useEffect(() => { if (data.length > 0) { initChart(); } }, [data]); // 组件挂载时获取数据 useEffect(() => { fetchData(); }, [defaultDateTime, selectedMonth]); // 监听窗口大小变化,确保图表自适应 useEffect(() => { const handleResize = () => { if (chartInstance.current) { chartInstance.current.resize(); } }; window.addEventListener('resize', handleResize); // 使用ResizeObserver监听容器大小变化 const resizeObserver = new ResizeObserver(() => { if (chartInstance.current) { chartInstance.current.resize(); } }); if (chartRef.current) { resizeObserver.observe(chartRef.current); } return () => { window.removeEventListener('resize', handleResize); resizeObserver.disconnect(); if (chartInstance.current) { chartInstance.current.dispose(); } }; }, []); // 使用刷新Hook,配置对应的模块代码 useGlobalRefresh(fetchData, '34'); return ( 每周药品概况
); }; export default DailyMedicineQuantityChart;