From 38c19cbf06db40a44495902707c5e08c242a93e5 Mon Sep 17 00:00:00 2001 From: wangzenghua <1048523306@qq.com> Date: Tue, 21 Jan 2025 03:53:24 +0000 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9B=E5=85=A8=E5=B1=80=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/package.json | 2 + .../components/custom-echart-bar/index.vue | 86 +++++++ main/src/components/index.js | 4 +- main/src/hooks/useBreakpoint.js | 84 +++++++ main/src/hooks/useEcharts.js | 107 +++++++++ main/src/hooks/useEventListener.js | 38 ++++ main/src/hooks/useTimeout.js | 44 ++++ main/src/plugins/globalComponents.js | 8 - main/src/utils/echarts.js | 51 +++++ main/src/views/index.vue | 2 + main/yarn.lock | 49 +++- sub-admin/package.json | 3 +- sub-admin/src/config/index.js | 215 +----------------- sub-admin/src/plugins/globalComponents.js | 11 + sub-admin/src/views/demo/chartdemo.data.js | 52 +++++ sub-admin/src/views/demo/echart.vue | 23 +- 16 files changed, 551 insertions(+), 228 deletions(-) create mode 100644 main/src/components/custom-echart-bar/index.vue create mode 100644 main/src/hooks/useBreakpoint.js create mode 100644 main/src/hooks/useEcharts.js create mode 100644 main/src/hooks/useEventListener.js create mode 100644 main/src/hooks/useTimeout.js delete mode 100644 main/src/plugins/globalComponents.js create mode 100644 main/src/utils/echarts.js create mode 100644 sub-admin/src/views/demo/chartdemo.data.js diff --git a/main/package.json b/main/package.json index 6fca603..73cb015 100644 --- a/main/package.json +++ b/main/package.json @@ -17,8 +17,10 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", + "@vueuse/core": "^12.4.0", "axios": "^1.6.5", "dayjs": "^1.11.11", + "echarts": "^5.6.0", "element-plus": "^2.7.3", "file-saver": "^2.0.5", "js-base64": "^3.7.7", diff --git a/main/src/components/custom-echart-bar/index.vue b/main/src/components/custom-echart-bar/index.vue new file mode 100644 index 0000000..8945969 --- /dev/null +++ b/main/src/components/custom-echart-bar/index.vue @@ -0,0 +1,86 @@ + + diff --git a/main/src/components/index.js b/main/src/components/index.js index 590713a..019eefa 100644 --- a/main/src/components/index.js +++ b/main/src/components/index.js @@ -1,5 +1,5 @@ import SvgIcon from './svg-icon'; - import CustomRichEditor from './custom-rich-editor'; +import CustomEchartBar from './custom-echart-bar'; -export { SvgIcon, CustomRichEditor }; +export { SvgIcon, CustomEchartBar, CustomRichEditor }; diff --git a/main/src/hooks/useBreakpoint.js b/main/src/hooks/useBreakpoint.js new file mode 100644 index 0000000..cf0e081 --- /dev/null +++ b/main/src/hooks/useBreakpoint.js @@ -0,0 +1,84 @@ +import { ref, computed, unref } from 'vue'; +import { useEventListener } from './useEventListener'; + +let globalScreenRef; +let globalWidthRef; +let globalRealWidthRef; + +const screenMap = new Map(); +screenMap.set('XS', 480); +screenMap.set('SM', 576); +screenMap.set('MD', 768); +screenMap.set('LG', 992); +screenMap.set('XL', 1200); +screenMap.set('XXL', 1600); + +export function useBreakpoint() { + return { + screenRef: computed(() => unref(globalScreenRef)), + widthRef: globalWidthRef, + realWidthRef: globalRealWidthRef, + }; +} + +// Just call it once +export function createBreakpointListen(fn) { + const screenRef = ref('XL'); + const realWidthRef = ref(window.innerWidth); + + function getWindowWidth() { + const width = document.body.clientWidth; + const xs = screenMap.get('XS'); + const sm = screenMap.get('SM'); + const md = screenMap.get('MD'); + const lg = screenMap.get('LG'); + const xl = screenMap.get('XL'); + const xxl = screenMap.set('XXL', 1600); + if (width < xs) { + screenRef.value = xs; + } else if (width < sm) { + screenRef.value = sm; + } else if (width < md) { + screenRef.value = md; + } else if (width < lg) { + screenRef.value = lg; + } else if (width < xl) { + screenRef.value = xl; + } else { + screenRef.value = xxl; + } + realWidthRef.value = width; + } + + useEventListener({ + el: window, + name: 'resize', + + listener: () => { + getWindowWidth(); + resizeFn(); + }, + // wait: 100, + }); + + getWindowWidth(); + globalScreenRef = computed(() => unref(screenRef)); + globalWidthRef = computed(() => screenMap.get(unref(screenRef))); + globalRealWidthRef = computed(() => unref(realWidthRef)); + + function resizeFn() { + fn?.({ + screen: globalScreenRef, + width: globalWidthRef, + realWidth: globalRealWidthRef, + screenMap, + }); + } + + resizeFn(); + return { + screenRef: globalScreenRef, + widthRef: globalWidthRef, + realWidthRef: globalRealWidthRef, + }; +} diff --git a/main/src/hooks/useEcharts.js b/main/src/hooks/useEcharts.js new file mode 100644 index 0000000..8d14d5d --- /dev/null +++ b/main/src/hooks/useEcharts.js @@ -0,0 +1,107 @@ +import { unref, nextTick, watch, computed, ref } from 'vue'; +import { useDebounceFn, tryOnUnmounted } from '@vueuse/core'; +import { useTimeoutFn } from './useTimeout'; +import { useEventListener } from './useEventListener'; +import { useBreakpoint } from './useBreakpoint'; +import echarts from '../utils/echarts'; + +export const useEcharts = (elRef, theme = 'default') => { + const getDarkMode = computed(() => { + return theme === 'default' ? 'dark' : theme; + }); + let chartInstance = null; + let resizeFn = resize; + const cacheOptions = ref({}); + let removeResizeFn = () => {}; + + resizeFn = useDebounceFn(resize, 200); + + const getOptions = computed(() => { + if (getDarkMode.value !== 'dark') { + return cacheOptions.value; + } + return { + backgroundColor: 'transparent', + ...cacheOptions.value, + }; + }); + + function initCharts(t = theme) { + const el = unref(elRef); + if (!el || !unref(el)) { + return; + } + + chartInstance = echarts.init(el, t); + const { removeEvent } = useEventListener({ + el: window, + name: 'resize', + listener: resizeFn, + }); + removeResizeFn = removeEvent; + const { widthRef } = useBreakpoint(); + if (unref(widthRef) <= 768 || el.offsetHeight === 0) { + useTimeoutFn(() => { + resizeFn(); + }, 30); + } + } + + function setOptions(options = {}, clear = true) { + cacheOptions.value = options; + if (unref(elRef)?.offsetHeight === 0) { + useTimeoutFn(() => { + setOptions(unref(getOptions)); + }, 30); + return; + } + nextTick(() => { + useTimeoutFn(() => { + if (!chartInstance) { + initCharts(getDarkMode.value ?? 'default'); + + if (!chartInstance) return; + } + clear && chartInstance?.clear(); + + chartInstance?.setOption(unref(getOptions)); + }, 30); + }); + } + + function resize() { + chartInstance?.resize(); + } + + watch( + () => getDarkMode.value, + (theme) => { + if (chartInstance) { + chartInstance.dispose(); + initCharts(theme); + setOptions(cacheOptions.value); + } + } + ); + + tryOnUnmounted(() => { + if (!chartInstance) return; + removeResizeFn(); + chartInstance.dispose(); + chartInstance = null; + }); + + function getInstance() { + if (!chartInstance) { + initCharts(getDarkMode.value ?? 'default'); + } + return chartInstance; + } + + return { + setOptions, + resize, + echarts, + getInstance, + }; +}; diff --git a/main/src/hooks/useEventListener.js b/main/src/hooks/useEventListener.js new file mode 100644 index 0000000..5ebdaf7 --- /dev/null +++ b/main/src/hooks/useEventListener.js @@ -0,0 +1,38 @@ +import { ref, watch, unref } from 'vue'; +import { useThrottleFn, useDebounceFn } from '@vueuse/core'; + +export function useEventListener({ el = window, name, listener, options, autoRemove = true, isDebounce = true, wait = 80 }) { + let remove; + const isAddRef = ref(false); + + if (el) { + const element = ref(el); + + const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait); + const realHandler = wait ? handler : listener; + const removeEventListener = (e) => { + isAddRef.value = true; + e.removeEventListener(name, realHandler, options); + }; + const addEventListener = (e) => e.addEventListener(name, realHandler, options); + + const removeWatch = watch( + element, + (v, _ov, cleanUp) => { + if (v) { + !unref(isAddRef) && addEventListener(v); + cleanUp(() => { + autoRemove && removeEventListener(v); + }); + } + }, + { immediate: true } + ); + + remove = () => { + removeEventListener(element.value); + removeWatch(); + }; + } + return { removeEvent: remove }; +} diff --git a/main/src/hooks/useTimeout.js b/main/src/hooks/useTimeout.js new file mode 100644 index 0000000..ccef4b6 --- /dev/null +++ b/main/src/hooks/useTimeout.js @@ -0,0 +1,44 @@ +import { ref, watch } from 'vue'; +import { tryOnUnmounted } from '@vueuse/core'; + +export function useTimeoutFn(handle, wait, native = false) { + if (typeof handle !== 'function') { + throw new Error('handle is not Function!'); + } + + const { readyRef, stop, start } = useTimeoutRef(wait); + if (native) { + handle(); + } else { + watch( + readyRef, + (maturity) => { + maturity && handle(); + }, + { immediate: false } + ); + } + return { readyRef, stop, start }; +} + +export function useTimeoutRef(wait) { + const readyRef = ref(false); + + let timer; + function stop() { + readyRef.value = false; + timer && window.clearTimeout(timer); + } + function start() { + stop(); + timer = setTimeout(() => { + readyRef.value = true; + }, wait); + } + + start(); + + tryOnUnmounted(stop); + + return { readyRef, stop, start }; +} diff --git a/main/src/plugins/globalComponents.js b/main/src/plugins/globalComponents.js deleted file mode 100644 index 846a429..0000000 --- a/main/src/plugins/globalComponents.js +++ /dev/null @@ -1,8 +0,0 @@ -// import * as components from '../../../global/components'; - -// // 全局注册组件 -// export const registerGlobalComponents = (app) => { -// Object.keys(components).forEach((key) => { -// app.component(key, components[key]); -// }); -// }; diff --git a/main/src/utils/echarts.js b/main/src/utils/echarts.js new file mode 100644 index 0000000..9b40020 --- /dev/null +++ b/main/src/utils/echarts.js @@ -0,0 +1,51 @@ +import * as echarts from 'echarts/core'; + +import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart } from 'echarts/charts'; + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, +} from 'echarts/components'; + +// TODO 如果想换成SVG渲染,就导出SVGRenderer, +// 并且放到 echarts.use 里,注释掉 CanvasRenderer +import { /*SVGRenderer*/ CanvasRenderer } from 'echarts/renderers'; + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + RadarChart, + // TODO 因为要兼容Online图表自适应打印,所以改成 CanvasRenderer,可能会模糊 + CanvasRenderer, + PictorialBarChart, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, +]); + +export default echarts; diff --git a/main/src/views/index.vue b/main/src/views/index.vue index 290dcbc..338069d 100644 --- a/main/src/views/index.vue +++ b/main/src/views/index.vue @@ -1,6 +1,8 @@ + +