import type { EChartsOption } from 'echarts'; import { onActivated, Ref } from 'vue'; import { useTimeoutFn } from '@jeesite/core/hooks/core/useTimeout'; import { tryOnUnmounted } from '@vueuse/core'; import { unref, nextTick, watch, computed, ref } from 'vue'; import { useDebounceFn } from '@vueuse/core'; import { useEventListener } from '@jeesite/core/hooks/event/useEventListener'; import { useBreakpoint } from '@jeesite/core/hooks/event/useBreakpoint'; import echarts from '@jeesite/core/utils/lib/echarts'; import { useRootSetting } from '@jeesite/core/hooks/setting/useRootSetting'; type EventListenerTarget = { addEventListener: ( type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions, ) => void; }; function toArray(value?: T | T[]) { if (!value) { return []; } return Array.isArray(value) ? value : [value]; } function hasChartSize(el?: HTMLDivElement | null) { return !!el?.clientWidth && !!el?.clientHeight; } function needActiveWheelListener(options: EChartsOption) { const chartOptions = options as Record; if (toArray(chartOptions.dataZoom).length > 0) { return true; } const roamTargets = [...toArray(chartOptions.geo), ...toArray(chartOptions.bmap), ...toArray(chartOptions.series)]; return roamTargets.some((item) => !!item?.roam); } function getWheelListenerTargets() { const targets = new Set(); const targetTypes = [ EventTarget, typeof Window === 'undefined' ? undefined : Window, typeof Document === 'undefined' ? undefined : Document, typeof Element === 'undefined' ? undefined : Element, ]; targetTypes.forEach((targetType) => { if (typeof targetType !== 'undefined' && targetType.prototype?.addEventListener) { targets.add(targetType.prototype as EventListenerTarget); } }); if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') { targets.add(window as unknown as EventListenerTarget); } if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') { targets.add(document as unknown as EventListenerTarget); } return [...targets]; } function normalizeWheelListenerOptions(options?: boolean | AddEventListenerOptions) { if (typeof options === 'boolean') { return { capture: options, passive: true }; } return { ...options, passive: true }; } function withPassiveWheelListeners(callback: () => T) { if (typeof EventTarget === 'undefined') { return callback(); } const targets = getWheelListenerTargets(); const originalAddEventListeners = new Map(); targets.forEach((target) => { const originalAddEventListener = target.addEventListener; originalAddEventListeners.set(target, originalAddEventListener); target.addEventListener = function ( type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions, ) { if (type !== 'wheel' && type !== 'mousewheel') { return originalAddEventListener.call(this, type, listener, options); } return originalAddEventListener.call(this, type, listener, normalizeWheelListenerOptions(options)); }; }); try { return callback(); } finally { originalAddEventListeners.forEach((originalAddEventListener, target) => { target.addEventListener = originalAddEventListener; }); } } export function useECharts(elRef: Ref, theme: 'light' | 'dark' | 'default' = 'default') { const { getDarkMode: getSysDarkMode } = useRootSetting(); const getDarkMode = computed(() => { return theme === 'default' ? getSysDarkMode.value : theme; }); let chartInstance: echarts.ECharts | null = null; let resizeFn: Fn = resize; const cacheOptions = ref({}) as Ref; let removeResizeFn: Fn = () => {}; resizeFn = useDebounceFn(resize, 200); const getOptions = computed(() => { if (getDarkMode.value !== 'dark') { return cacheOptions.value as EChartsOption; } return { backgroundColor: 'transparent', ...cacheOptions.value, } as EChartsOption; }); function initCharts(t = theme) { const el = unref(elRef); if (!el || !unref(el)) { return; } if (!hasChartSize(el)) { useTimeoutFn(() => { initCharts(t); }, 30); return; } const options = unref(getOptions); chartInstance = needActiveWheelListener(options) ? echarts.init(el, t) : withPassiveWheelListeners(() => echarts.init(el, t)); const { removeEvent } = useEventListener({ el: window, name: 'resize', listener: resizeFn, }); removeResizeFn = removeEvent; const { widthRef, screenEnum } = useBreakpoint(); if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) { useTimeoutFn(() => { resizeFn(); }, 30); } } function setOptions(options: EChartsOption, clear = true) { cacheOptions.value = options; if (!hasChartSize(unref(elRef))) { useTimeoutFn(() => { setOptions(unref(getOptions)); }, 30); return; } nextTick(() => { useTimeoutFn(() => { if (!chartInstance) { initCharts(getDarkMode.value as 'default'); if (!chartInstance) return; } clear && chartInstance?.clear(); chartInstance?.setOption(unref(getOptions)); }, 30); }); } function resize() { chartInstance?.resize(); } onActivated(() => { resize(); }); watch( () => getDarkMode.value, (theme) => { if (chartInstance) { chartInstance.dispose(); initCharts(theme as 'default'); setOptions(cacheOptions.value); } }, ); tryOnUnmounted(() => { if (!chartInstance) return; removeResizeFn(); chartInstance.dispose(); chartInstance = null; }); function getInstance(): echarts.ECharts | null { if (!chartInstance) { initCharts(getDarkMode.value as 'default'); } return chartInstance; } return { setOptions, resize, echarts, getInstance, }; }