| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div style="position: relative;"> |
| | | <div ref="chartRef" :style="chartStyle"></div> |
| | | <slot></slot> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | const emit = defineEmits(['finished', 'click']) |
| | | |
| | | // Props |
| | | const props = defineProps({ |
| | | options: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | chartStyle: { |
| | | type: Object, |
| | | default: () => ({ |
| | | height: '80%', |
| | | width: '100%' |
| | | }) |
| | | }, |
| | | dataset: { |
| | | type: Object, |
| | | default: () => { } |
| | | }, |
| | | xAxis: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | yAxis: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | series: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | grid: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | legend: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | tooltip: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | lineColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | barColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | pieColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | loadingOption: { |
| | | type: Object, |
| | | default: () => ({ |
| | | text: 'æ°æ®å è½½ä¸...', |
| | | color: '#00BAFF', |
| | | textColor: '#000', |
| | | maskColor: 'rgba(255, 255, 255, 0.8)', |
| | | zlevel: 0 |
| | | }) |
| | | }, |
| | | color: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | visualMap: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | option: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | }) |
| | | |
| | | import { watch } from 'vue' |
| | | |
| | | // 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) |
| | | chartInstance.on('click', (params) => { |
| | | emit('click', params) |
| | | }) |
| | | renderChart() |
| | | // setOption åè¡¥ä¸æ¬¡ resizeï¼ç¡®ä¿é¦å±å°ºå¯¸æ£ç¡® |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | } |
| | | |
| | | // Methods |
| | | function generateChart(option) { |
| | | const copiedOption = option |
| | | |
| | | if (copiedOption.series && copiedOption.series.length > 0) { |
| | | copiedOption.series.forEach((s, index) => { |
| | | if (s.type === 'line' && props.lineColors.length) { |
| | | s.itemStyle = s.itemStyle || {} |
| | | s.lineStyle = s.lineStyle || {} |
| | | s.itemStyle.color = props.lineColors[index] || props.lineColors[0] |
| | | s.lineStyle.color = props.lineColors[index] || props.lineColors[0] |
| | | } else if (s.type === 'bar' && props.barColors.length) { |
| | | s.itemStyle = s.itemStyle || {} |
| | | s.itemStyle.color = props.barColors[index] || props.barColors[0] |
| | | } |
| | | }) |
| | | } |
| | | |
| | | chartInstance.setOption(copiedOption) |
| | | } |
| | | |
| | | function renderChart() { |
| | | const option = { |
| | | color: props.color.length ? props.color : undefined, |
| | | backgroundColor: props.options.backgroundColor || '#fff', |
| | | textStyle: props.options.textStyle || { color: '#333' }, |
| | | xAxis: props.xAxis, |
| | | yAxis: props.yAxis, |
| | | dataset: props.dataset, |
| | | series: props.series, |
| | | grid: props.grid, |
| | | legend: props.legend, |
| | | tooltip: props.tooltip, |
| | | visualMap: Object.keys(props.visualMap).length ? props.visualMap : undefined, |
| | | } |
| | | |
| | | chartInstance.clear() |
| | | generateChart(option) |
| | | } |
| | | |
| | | function windowResizeListener() { |
| | | if (!chartInstance) return |
| | | chartInstance.resize() |
| | | } |
| | | |
| | | // Lifecycle hooks |
| | | onMounted(() => { |
| | | 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], |
| | | () => { |
| | | // 妿é¦å±è¿æ²¡åå§åæåï¼çå¾
å®¹å¨ ready å忏²æ |
| | | if (!chartInstance) { |
| | | initChartWhenReady() |
| | | return |
| | | } |
| | | renderChart() |
| | | // æ°æ®åååè¡¥ä¸æ¬¡ resizeï¼é¿å
å¸å±åå导è´çåç§» |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |
| | | </script> |