Files
track/frontend/src/components/BaseEChart.tsx
duxingchen 2b17dde42e feat(analytics): 前端 ECharts CustomChart 注册 + 数据契约类型扩展
- BaseEChart 注册 CustomChart 支持区间渲染
- FlowInterval 类型扩为 6 位(含 isMain)
- FlowDevice 增加 lead_time/started_at
2026-08-14 11:44:48 +08:00

82 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** 轻量级 ECharts 封装 — 原生 echarts/core + ResizeObserver 响应式缩放
* 支持 onEvents常规事件与 onZrClickZRender 底层点击,扩大热区)。 */
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 }} />;
}