| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, watchEffect } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | const emit = defineEmits(['finished']) |
| | | |
| | | // Props |
| | | const props = defineProps({ |
| | |
| | | // Refs |
| | | const chartRef = ref(null) |
| | | let chartInstance = null |
| | | let finishedHandler = null |
| | | let initTimer = null |
| | | let initAttempts = 0 |
| | | |
| | | function clearInitTimer() { |
| | | if (initTimer) { |
| | | clearTimeout(initTimer) |
| | | initTimer = null |
| | | } |
| | | } |
| | | |
| | | function isContainerReady() { |
| | | const el = chartRef.value |
| | | if (!el) return false |
| | | // offsetWidth/offsetHeight 更贴近真实布局(为 0 往往代表还没布局/不可见) |
| | | return el.offsetWidth > 0 && el.offsetHeight > 0 |
| | | } |
| | | |
| | | function initChartWhenReady() { |
| | | clearInitTimer() |
| | | initAttempts += 1 |
| | | |
| | | if (!isContainerReady()) { |
| | | // 等容器真正有尺寸(避免首屏初始化偏移/空白,热更新后才正常的情况) |
| | | // 最多重试约 3 秒,避免无限循环 |
| | | if (initAttempts < 60) { |
| | | initTimer = setTimeout(initChartWhenReady, 50) |
| | | } |
| | | return |
| | | } |
| | | |
| | | if (chartInstance) return |
| | | chartInstance = echarts.init(chartRef.value) |
| | | finishedHandler = () => emit('finished') |
| | | chartInstance.on('finished', finishedHandler) |
| | | renderChart() |
| | | // setOption 后补一次 resize,确保首屏尺寸正确 |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | } |
| | | |
| | | // Methods |
| | | function generateChart(option) { |
| | |
| | | |
| | | // Lifecycle hooks |
| | | onMounted(() => { |
| | | chartInstance = echarts.init(chartRef.value) |
| | | renderChart() |
| | | initAttempts = 0 |
| | | initChartWhenReady() |
| | | window.addEventListener('resize', windowResizeListener) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (chartInstance) { |
| | | window.removeEventListener('resize', windowResizeListener) |
| | | if (finishedHandler) { |
| | | chartInstance.off('finished', finishedHandler) |
| | | finishedHandler = null |
| | | } |
| | | chartInstance.dispose() |
| | | chartInstance = null |
| | | } |
| | | clearInitTimer() |
| | | }) |
| | | |
| | | // Watch all reactive props that affect the chart |
| | | watch( |
| | | () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap], |
| | | () => { |
| | | if (chartInstance) { |
| | | renderChart() |
| | | // 如果首屏还没初始化成功,等待容器 ready 后再渲染 |
| | | if (!chartInstance) { |
| | | initChartWhenReady() |
| | | return |
| | | } |
| | | renderChart() |
| | | // 数据变化后补一次 resize,避免布局变化导致的偏移 |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |