From 9aa4a723213ab217137d94f9d81298ef6959539a Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Mon, 22 Dec 2025 17:31:35 +0800 Subject: [PATCH] u --- .../link56-frontend-new/src/hooks/echarts.ts | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 code/link56/link56-frontend-new/src/hooks/echarts.ts diff --git a/code/link56/link56-frontend-new/src/hooks/echarts.ts b/code/link56/link56-frontend-new/src/hooks/echarts.ts new file mode 100644 index 00000000..cb36252c --- /dev/null +++ b/code/link56/link56-frontend-new/src/hooks/echarts.ts @@ -0,0 +1,213 @@ +import { effectScope, nextTick, onScopeDispose, ref, watch } from "vue"; +import { useElementSize } from "@vueuse/core"; +import * as echarts from "echarts/core"; +import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from "echarts/charts"; +import type { BarSeriesOption, GaugeSeriesOption, LineSeriesOption, PictorialBarSeriesOption, PieSeriesOption, RadarSeriesOption, ScatterSeriesOption } from "echarts/charts"; +import { DatasetComponent, GridComponent, LegendComponent, TitleComponent, ToolboxComponent, TooltipComponent, TransformComponent } from "echarts/components"; +import type { DatasetComponentOption, GridComponentOption, LegendComponentOption, TitleComponentOption, ToolboxComponentOption, TooltipComponentOption } from "echarts/components"; +import { LabelLayout, UniversalTransition } from "echarts/features"; +import { CanvasRenderer } from "echarts/renderers"; + +export type ECOption = echarts.ComposeOption< + | BarSeriesOption + | LineSeriesOption + | PieSeriesOption + | ScatterSeriesOption + | PictorialBarSeriesOption + | RadarSeriesOption + | GaugeSeriesOption + | TitleComponentOption + | LegendComponentOption + | TooltipComponentOption + | GridComponentOption + | ToolboxComponentOption + | DatasetComponentOption +>; + +echarts.use([ + TitleComponent, + LegendComponent, + TooltipComponent, + GridComponent, + DatasetComponent, + TransformComponent, + ToolboxComponent, + BarChart, + LineChart, + PieChart, + ScatterChart, + PictorialBarChart, + RadarChart, + GaugeChart, + LabelLayout, + UniversalTransition, + CanvasRenderer +]); + +interface ChartHooks { + onRender?: (chart: echarts.ECharts) => void | Promise; + onUpdated?: (chart: echarts.ECharts) => void | Promise; + onDestroy?: (chart: echarts.ECharts) => void | Promise; +} + +/** + * use echarts + * + * @param optionsFactory echarts options factory function + * @param darkMode dark mode + */ +export function useEcharts(optionsFactory: () => T, hooks: ChartHooks = {}) { + const scope = effectScope(); + + const darkMode = ref(false); + + const domRef = ref(null); + const initialSize = { width: 0, height: 0 }; + const { width, height } = useElementSize(domRef, initialSize); + + let chart: echarts.ECharts | null = null; + const chartOptions: T = optionsFactory(); + + const { + onRender = (instance) => { + const textColor = darkMode.value ? "rgb(224, 224, 224)" : "rgb(31, 31, 31)"; + const maskColor = darkMode.value ? "rgba(0, 0, 0, 0.4)" : "rgba(255, 255, 255, 0.8)"; + + instance.showLoading({ + textColor, + fontSize: 14, + maskColor + }); + }, + onUpdated = (instance) => { + instance.hideLoading(); + }, + onDestroy + } = hooks; + + /** + * whether can render chart + * + * when domRef is ready and initialSize is valid + */ + function canRender() { + return domRef.value && initialSize.width > 0 && initialSize.height > 0; + } + + /** is chart rendered */ + function isRendered() { + return Boolean(domRef.value && chart); + } + + /** + * update chart options + * + * @param callback callback function + */ + async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) { + if (!isRendered()) return; + + const updatedOpts = callback(chartOptions, optionsFactory); + + Object.assign(chartOptions, updatedOpts); + + if (isRendered()) { + chart?.clear(); + } + + chart?.setOption({ ...updatedOpts, backgroundColor: "transparent" }); + + await onUpdated?.(chart!); + } + + function setOptions(options: T) { + chart?.setOption(options); + } + + /** render chart */ + async function render() { + if (!isRendered()) { + const chartTheme = darkMode.value ? "dark" : "light"; + + await nextTick(); + + chart = echarts.init(domRef.value, chartTheme); + + chart.setOption({ ...chartOptions, backgroundColor: "transparent" }); + + await onRender?.(chart); + } + } + + /** resize chart */ + function resize() { + chart?.resize(); + } + + /** destroy chart */ + async function destroy() { + if (!chart) return; + + await onDestroy?.(chart); + chart?.dispose(); + chart = null; + } + + /** change chart theme */ + async function changeTheme() { + await destroy(); + await render(); + await onUpdated?.(chart!); + } + + /** + * render chart by size + * + * @param w width + * @param h height + */ + async function renderChartBySize(w: number, h: number) { + initialSize.width = w; + initialSize.height = h; + + // size is abnormal, destroy chart + if (!canRender()) { + await destroy(); + + return; + } + + // resize chart + if (isRendered()) { + resize(); + } + + // render chart + await render(); + + if (chart) { + await onUpdated?.(chart); + } + } + + scope.run(() => { + watch([width, height], ([newWidth, newHeight]) => { + renderChartBySize(newWidth, newHeight); + }); + + watch(darkMode, () => { + changeTheme(); + }); + }); + + onScopeDispose(() => { + destroy(); + scope.stop(); + }); + + return { + domRef, + updateOptions, + setOptions + }; +}