import { useEffect, useRef } from 'react' import * as echarts from 'echarts' import './ChartPanel.css' /** * 图表面板组件 * @param {Object} props * @param {string} props.type - 图表类型: 'line' | 'bar' | 'pie' | 'ring' * @param {string} props.title - 图表标题 * @param {Object} props.data - 图表数据 * @param {number} props.height - 图表高度,默认 200px * @param {Object} props.option - 自定义 ECharts 配置 * @param {string} props.className - 自定义类名 */ function ChartPanel({ type = 'line', title, data, height = 200, option = {}, className = '' }) { const chartRef = useRef(null) const chartInstance = useRef(null) useEffect(() => { if (!chartRef.current || !data) return // 使用 setTimeout 确保 DOM 完全渲染 const timer = setTimeout(() => { // 初始化图表 if (!chartInstance.current) { chartInstance.current = echarts.init(chartRef.current) } // 根据类型生成配置 const chartOption = getChartOption(type, data, option) chartInstance.current.setOption(chartOption, true) }, 0) // 窗口大小改变时重绘(使用 passive 选项) const handleResize = () => { if (chartInstance.current) { chartInstance.current.resize() } } // 添加被动事件监听器 window.addEventListener('resize', handleResize, { passive: true }) return () => { clearTimeout(timer) window.removeEventListener('resize', handleResize) } }, [type, data, option]) // 组件卸载时销毁图表 useEffect(() => { return () => { chartInstance.current?.dispose() } }, []) return (
{title &&
{title}
}
) } /** * 根据图表类型生成 ECharts 配置 */ function getChartOption(type, data, customOption) { const baseOption = { grid: { left: '10%', right: '5%', top: '15%', bottom: '15%', }, tooltip: { trigger: type === 'pie' || type === 'ring' ? 'item' : 'axis', backgroundColor: 'rgba(255, 255, 255, 0.95)', borderColor: '#e8e8e8', borderWidth: 1, textStyle: { color: '#333', }, }, } switch (type) { case 'line': return { ...baseOption, xAxis: { type: 'category', data: data.xAxis || [], boundaryGap: false, axisLine: { lineStyle: { color: '#e8e8e8' } }, axisLabel: { color: '#8c8c8c', fontSize: 11 }, }, yAxis: { type: 'value', axisLine: { lineStyle: { color: '#e8e8e8' } }, axisLabel: { color: '#8c8c8c', fontSize: 11 }, splitLine: { lineStyle: { color: '#f0f0f0' } }, }, series: [ { type: 'line', data: data.series || [], smooth: true, lineStyle: { width: 2, color: '#1677ff' }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(22, 119, 255, 0.3)' }, { offset: 1, color: 'rgba(22, 119, 255, 0.05)' }, ]), }, symbol: 'circle', symbolSize: 6, itemStyle: { color: '#1677ff' }, }, ], ...customOption, } case 'bar': return { ...baseOption, xAxis: { type: 'category', data: data.xAxis || [], axisLine: { lineStyle: { color: '#e8e8e8' } }, axisLabel: { color: '#8c8c8c', fontSize: 11 }, }, yAxis: { type: 'value', axisLine: { lineStyle: { color: '#e8e8e8' } }, axisLabel: { color: '#8c8c8c', fontSize: 11 }, splitLine: { lineStyle: { color: '#f0f0f0' } }, }, series: [ { type: 'bar', data: data.series || [], itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#4096ff' }, { offset: 1, color: '#1677ff' }, ]), borderRadius: [4, 4, 0, 0], }, barWidth: '50%', }, ], ...customOption, } case 'pie': case 'ring': return { ...baseOption, grid: undefined, legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#8c8c8c', fontSize: 12 }, }, series: [ { type: 'pie', radius: type === 'ring' ? ['40%', '65%'] : '65%', center: ['40%', '50%'], data: data.series || [], label: { fontSize: 11, color: '#8c8c8c', }, labelLine: { lineStyle: { color: '#d9d9d9' }, }, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2, }, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.3)', }, }, }, ], ...customOption, } default: return { ...baseOption, ...customOption } } } export default ChartPanel