172 lines
4.0 KiB
Vue
172 lines
4.0 KiB
Vue
<script setup lang="ts">
|
||
import type { BarSeriesOption, Color, ECharts, EChartsOption } from 'echarts';
|
||
import * as echarts from 'echarts';
|
||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||
|
||
const props = defineProps({
|
||
list: {
|
||
type: Array,
|
||
default: () => [], // 增加默认值,避免undefined
|
||
},
|
||
});
|
||
|
||
// 修复1:定义符合ECharts类型的渐变颜色(推荐用官方构造函数)
|
||
const colorList: Color[] = [
|
||
// 青蓝渐变
|
||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: '#00c8e6' },
|
||
{ offset: 1, color: '#007fa3' },
|
||
]),
|
||
// 黄绿渐变
|
||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: '#a8d04d' },
|
||
{ offset: 1, color: '#6d8c20' },
|
||
]),
|
||
];
|
||
|
||
// 图表DOM引用
|
||
const chartRef = ref<HTMLDivElement | null>(null);
|
||
// 图表实例
|
||
let myChart: ECharts | null = null;
|
||
// 存储resize清理函数
|
||
let resizeCleanup: (() => void) | null = null;
|
||
|
||
// 初始化图表函数
|
||
function initChart() {
|
||
if (!chartRef.value)
|
||
return;
|
||
|
||
// 销毁原有实例,避免内存泄漏
|
||
if (myChart) {
|
||
myChart.dispose();
|
||
}
|
||
|
||
// 创建ECharts实例
|
||
myChart = echarts.init(chartRef.value);
|
||
|
||
// 修复2:提前处理数据,避免undefined和类型错误
|
||
const chartData = props.list.map((item: any, index: number) => ({
|
||
value: Number(item.value) || 0, // 兜底为数字0
|
||
itemStyle: {
|
||
// 修复3:itemStyle需嵌套color字段,且循环取色时兜底(避免颜色数组越界)
|
||
color: colorList[index % colorList.length],
|
||
},
|
||
}));
|
||
|
||
// 计算y轴最大值(向上取整到百位)
|
||
const yMax = props.list.length
|
||
? Math.ceil(Math.max(...props.list.map((item: any) => Number(item.value) || 0)) / 100) * 100
|
||
: 0;
|
||
|
||
// 图表配置项(严格匹配TS类型)
|
||
const option: EChartsOption = {
|
||
tooltip: {
|
||
trigger: 'item',
|
||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||
textStyle: { color: '#333' },
|
||
formatter: '{b}: {c}',
|
||
},
|
||
grid: {
|
||
left: '0%',
|
||
right: '0%',
|
||
bottom: '0%',
|
||
top: '30px',
|
||
containLabel: true,
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: props.list.map((item: any) => item.name || ''), // 兜底空字符串
|
||
axisLine: { lineStyle: { color: '#416273' } },
|
||
axisLabel: { color: '#ade6ee', fontSize: 14 },
|
||
axisTick: { show: false },
|
||
splitLine: { show: false },
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
max: yMax,
|
||
axisLine: {
|
||
show: true,
|
||
lineStyle: {
|
||
color: '#416273',
|
||
width: 1,
|
||
type: 'solid',
|
||
},
|
||
},
|
||
axisLabel: { color: '#ade6ee', fontSize: 12 },
|
||
splitLine: {
|
||
lineStyle: { color: '#77B5D533', type: 'solid' },
|
||
},
|
||
},
|
||
series: [
|
||
// 修复4:显式指定BarSeriesOption类型,消除类型歧义
|
||
{
|
||
type: 'bar',
|
||
barWidth: 60,
|
||
data: chartData, // 使用提前处理的无undefined数据
|
||
} as BarSeriesOption,
|
||
],
|
||
};
|
||
|
||
// 设置配置项
|
||
myChart.setOption(option);
|
||
|
||
// 窗口大小监听函数
|
||
const resizeHandler = () => {
|
||
myChart?.resize();
|
||
};
|
||
window.addEventListener('resize', resizeHandler);
|
||
|
||
// 存储清理函数
|
||
resizeCleanup = () => {
|
||
window.removeEventListener('resize', resizeHandler);
|
||
if (myChart) {
|
||
myChart.dispose();
|
||
myChart = null;
|
||
}
|
||
};
|
||
}
|
||
|
||
// 监听list变化,重新渲染图表
|
||
watch(() => props.list, () => {
|
||
initChart();
|
||
}, { deep: true });
|
||
|
||
// 生命周期:挂载时初始化
|
||
onMounted(() => {
|
||
initChart();
|
||
});
|
||
|
||
// 兜底清理:组件卸载时销毁图表
|
||
onUnmounted(() => {
|
||
if (resizeCleanup) {
|
||
resizeCleanup();
|
||
}
|
||
if (myChart) {
|
||
myChart.dispose();
|
||
myChart = null;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="history-cumulative-bar-chart">
|
||
<!-- 柱状图容器 -->
|
||
<div ref="chartRef" class="chart" />
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* 图表容器样式 */
|
||
.history-cumulative-bar-chart {
|
||
width: 100%;
|
||
height: 100%;
|
||
/* 增加最小高度,避免容器为空 */
|
||
}
|
||
|
||
.chart {
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0 auto;
|
||
}
|
||
</style>
|