119 lines
2.6 KiB
Vue
119 lines
2.6 KiB
Vue
<template>
|
|
<div ref="chartRef" class="echarts-container" :style="{ width, height }"></div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount, watch, nextTick, shallowRef } from 'vue';
|
|
import * as echarts from 'echarts';
|
|
import type { EChartsOption, ECharts } from 'echarts';
|
|
|
|
interface Props {
|
|
width?: string;
|
|
height?: string;
|
|
options: EChartsOption;
|
|
theme?: string;
|
|
autoResize?: boolean;
|
|
immediate?: boolean;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
width: '100%',
|
|
height: '100%',
|
|
theme: '',
|
|
autoResize: true,
|
|
immediate: true
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
ready: [chart: ECharts];
|
|
click: [params: echarts.ECElementEvent];
|
|
rendered: [];
|
|
}>();
|
|
|
|
const chartRef = ref<HTMLDivElement>();
|
|
// 3. 使用 shallowRef 优化性能,因为 ECharts 实例不需要深层响应式
|
|
const chartInstance = shallowRef<ECharts | null>(null);
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
const initChart = () => {
|
|
if (!chartRef.value) return;
|
|
|
|
if (chartInstance.value) {
|
|
chartInstance.value.dispose();
|
|
chartInstance.value = null;
|
|
}
|
|
|
|
const instance = echarts.init(chartRef.value, props.theme);
|
|
chartInstance.value = instance;
|
|
chartInstance.value.setOption(props.options);
|
|
|
|
emit('ready', instance);
|
|
emit('rendered');
|
|
|
|
instance.on('click', (params: any) => {
|
|
emit('click', params);
|
|
});
|
|
};
|
|
|
|
const updateChart = () => {
|
|
if (chartInstance.value) {
|
|
chartInstance.value.setOption(props.options, {
|
|
notMerge: false,
|
|
lazyUpdate: false
|
|
});
|
|
emit('rendered');
|
|
}
|
|
};
|
|
|
|
// ✅ 核心修复:支持 scale 缩放后重绘
|
|
const resizeChart = () => {
|
|
if (!chartInstance.value) return;
|
|
chartInstance.value.resize();
|
|
};
|
|
|
|
const getInstance = () => chartInstance.value;
|
|
|
|
const exportAsImage = (type: 'png' | 'svg' | 'jpeg' = 'png') => {
|
|
return (
|
|
chartInstance.value?.getDataURL({
|
|
type,
|
|
pixelRatio: 2,
|
|
backgroundColor: '#fff'
|
|
}) || ''
|
|
);
|
|
};
|
|
|
|
watch(() => props.options, updateChart, { deep: true });
|
|
|
|
onMounted(() => {
|
|
nextTick(() => initChart());
|
|
if (props.autoResize && chartRef.value) {
|
|
resizeObserver = new ResizeObserver(resizeChart);
|
|
resizeObserver.observe(chartRef.value);
|
|
}
|
|
window.addEventListener('resize', resizeChart);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', resizeChart);
|
|
resizeObserver?.disconnect();
|
|
chartInstance.value?.dispose();
|
|
chartInstance.value = null;
|
|
});
|
|
|
|
defineExpose({
|
|
resizeChart,
|
|
updateChart,
|
|
getInstance,
|
|
exportAsImage
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.echarts-container {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
</style>
|