Files
track/frontend/src/components/BaseEChart.tsx

82 lines
2.7 KiB
TypeScript
Raw Normal View History

/** 轻量级 ECharts 封装 — 原生 echarts/core + ResizeObserver 响应式缩放
* 支持 onEvents(常规事件)与 onZrClick(ZRender 底层点击,扩大热区)。 */
import { useEffect, useRef } from "react";
import * as echarts from "echarts/core";
import { LineChart, BarChart, CustomChart } from "echarts/charts";
import {
GridComponent, TooltipComponent, LegendComponent, DataZoomComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import type { EChartsCoreOption } from "echarts/core";
// 按需注册(新增图表/组件时在此追加,避免全量打包)
echarts.use([
LineChart,
BarChart,
CustomChart,
GridComponent,
TooltipComponent,
LegendComponent,
DataZoomComponent,
CanvasRenderer,
]);
type EChartsInstance = ReturnType<typeof echarts.init>;
interface BaseEChartProps {
option: EChartsCoreOption;
height?: number | string;
className?: string;
onEvents?: Record<string, (params: any) => void>;
onZrClick?: (chart: EChartsInstance, event: any) => void;
}
export default function BaseEChart({ option, height = 380, className, onEvents, onZrClick }: BaseEChartProps) {
const containerRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<EChartsInstance | null>(null);
const eventsRef = useRef(onEvents);
eventsRef.current = onEvents;
const onZrClickRef = useRef(onZrClick);
onZrClickRef.current = onZrClick;
// 初始化(仅一次):init → setOption → 注册事件 → ResizeObserver
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const chart = echarts.init(el);
chartRef.current = chart;
chart.setOption(option);
// 常规 ECharts 事件(读 eventsRef,保证始终用最新 handler)
Object.keys(eventsRef.current ?? {}).forEach((event) => {
chart.on(event, (params: any) => {
eventsRef.current?.[event]?.(params);
});
});
// ZRender 底层点击(点击列阴影/背景也能触发,热区更大)
chart.getZr().on("click", (e: any) => {
onZrClickRef.current?.(chart, e);
});
const observer = new ResizeObserver(() => chart.resize());
observer.observe(el);
return () => {
observer.disconnect();
chart.dispose();
chartRef.current = null;
};
// 仅挂载时执行一次;option 更新交给下面的 effect
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// option 更新:整表替换,避免 merge 残留旧 series
useEffect(() => {
chartRef.current?.setOption(option, { notMerge: true });
}, [option]);
return <div ref={containerRef} className={className} style={{ width: "100%", height }} />;
}