203 lines
5.4 KiB
JavaScript
203 lines
5.4 KiB
JavaScript
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 (
|
|
<div className={`chart-panel ${className}`}>
|
|
{title && <div className="chart-panel-title">{title}</div>}
|
|
<div ref={chartRef} style={{ width: '100%', height: `${height}px` }} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 根据图表类型生成 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
|