处理监听图表的函数,确保及时移除&生产环境不打印log

This commit is contained in:
2026-04-22 11:07:10 +08:00
parent 845e5a8af3
commit cfcb4f5068
117 changed files with 6781 additions and 5767 deletions

View File

@@ -1,286 +1,294 @@
<template>
<div style="position: relative;">
<div class="legend">
<span class="legend-item-line">
<span class="line target"></span>
预算
</span>
<span class="legend-item-line">
<span class="line real"></span>
实际
</span>
</div>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'Container',
props: ["chartData",'dateData','unit'],
components: {},
data() {
return {
myChart: null, // 存储 echarts 实例
};
},
// 关键:监听 chartData 变化
watch: {
chartData: {
handler(newData) {
this.updateChart(newData);
},
immediate: true, // 组件初始化时立即执行一次
deep: true, // 深度监听对象内部变化
}
},
mounted() {
this.$nextTick(() => {
this.initChart();
});
},
methods: {
// 初始化图表实例
initChart() {
const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) {
console.error('图表容器未找到!');
return;
}
this.myChart = echarts.init(chartDom);
// 初始化时调用一次更新
this.updateChart(this.chartData);
// 监听窗口缩放
window.addEventListener('resize', () => {
this.myChart?.resize();
});
},
// 核心:根据数据更新图表
updateChart(data) {
if (!this.myChart) {
// 如果实例还未初始化,则等待 initChart 完成后再更新
setTimeout(() => this.updateChart(data), 0);
return;
}
// 1. 处理数据,如果 data 无效则清空图表
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
this.myChart.setOption({
xAxis: { data: [] },
series: [{ data: [] }, { data: [] }]
});
return;
}
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
console.log('xAxisData', xAxisData);
// 3. 提取 "实际" 和 "目标" 系列的数据
const realData = data.real ? Object.values(data.real) : [];
const targetData = data.target ? Object.values(data.target) : [];
// 4. 准备 echarts 的 option 配置
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
grid: {
top: 35,
bottom: 20,
right: 13,
},
xAxis: [
{
type: 'category',
boundaryGap: false,
axisTick: { show: false },
axisLine: {
show: true,
onZero: false,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
},
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
interval: 0,
// width: 38,
overflow: 'break',
formatter: (value) => {
const dateParts = value.split('-'); // ["2025", "07", "01"]
if (dateParts.length < 2) return value;
// 去掉月份前面的0然后加上"月"
const month = dateParts[1].replace(/^0+/, '');
return `${month}`;
}
},
data: xAxisData
}
],
yAxis: {
type: 'value',
name: this.unit,
// nameLocation:'center',
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
min: 0,
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
axisTick: { show: false },
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12
},
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.15)',
}
},
axisLine: {
show: true,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
}
},
series: [
{
name: '实际',
type: 'line',
// stack: 'Total', // 趋势图通常不需要堆叠
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(255, 132, 0, 1)', // 加深颜色
width: 2,
},
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: realData // 使用提取出的 "实际" 数据
},
{
name: '预算',
type: 'line',
// stack: 'Total',
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(98, 213, 180, 1)', // 加深颜色
width: 2,
// type: 'dashed' // 目标线使用虚线
},
itemStyle: {
color: 'rgba(98, 213, 180, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
]),
},
data: targetData // 使用提取出的 "目标" 数据
},
]
};
// 5. 应用配置项更新图表
this.myChart.setOption(option, true);
}
},
beforeDestroy() {
// 组件销毁时清理
window.removeEventListener('resize', () => {
this.myChart?.resize();
});
this.myChart?.dispose();
}
};
</script>
<style lang="scss" scoped>
/* (你的样式代码保持不变) */
.legend {
position: absolute;
right: 12px;
top: 0px;
display: flex;
/* 使用 flex 布局让两个图例项并排且对齐 */
gap: 5px;
}
.legend-item-line {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.8);
text-align: left;
font-style: normal;
position: relative;
padding-left: 24px;
/* 为圆点和线条留出空间 */
display: flex;
align-items: center;
.line {
position: absolute;
left: 6px;
/* 线条从圆点右侧开始 */
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 16px;
/* 线条长度 */
height: 2px;
margin-right: 4px;
}
.target {
background: rgba(98, 213, 180, 1);
}
.real {
background: rgba(255, 132, 0, 1);
}
&::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
background-color: rgba(255, 132, 0, 1);
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
}
.legend-item-line:nth-child(1) {
&::before {
background-color: rgba(98, 213, 180, 1);
}
}
</style>
<template>
<div style="position: relative;">
<div class="legend">
<span class="legend-item-line">
<span class="line target"></span>
预算
</span>
<span class="legend-item-line">
<span class="line real"></span>
实际
</span>
</div>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'Container',
props: ["chartData",'dateData','unit'],
components: {},
data() {
return {
myChart: null, // 存储 echarts 实例
resizeHandler: null // 窗口resize事件处理器
};
},
// 关键:监听 chartData 变化
watch: {
chartData: {
handler(newData) {
this.updateChart(newData);
},
immediate: true, // 组件初始化时立即执行一次
deep: true, // 深度监听对象内部变化
}
},
mounted() {
this.$nextTick(() => {
this.initChart();
});
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
},
methods: {
// 初始化图表实例
initChart() {
const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) {
console.error('图表容器未找到!');
return;
}
this.myChart = echarts.init(chartDom);
// 初始化时调用一次更新
this.updateChart(this.chartData);
},
// 核心:根据数据更新图表
updateChart(data) {
if (!this.myChart) {
// 如果实例还未初始化,则等待 initChart 完成后再更新
setTimeout(() => this.updateChart(data), 0);
return;
}
// 1. 处理数据,如果 data 无效则清空图表
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
this.myChart.setOption({
xAxis: { data: [] },
series: [{ data: [] }, { data: [] }]
});
return;
}
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
console.log('xAxisData', xAxisData);
// 3. 提取 "实际" 和 "目标" 系列的数据
const realData = data.real ? Object.values(data.real) : [];
const targetData = data.target ? Object.values(data.target) : [];
// 4. 准备 echarts 的 option 配置
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
grid: {
top: 35,
bottom: 20,
right: 13,
},
xAxis: [
{
type: 'category',
boundaryGap: false,
axisTick: { show: false },
axisLine: {
show: true,
onZero: false,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
},
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
interval: 0,
// width: 38,
overflow: 'break',
formatter: (value) => {
const dateParts = value.split('-'); // ["2025", "07", "01"]
if (dateParts.length < 2) return value;
// 去掉月份前面的0然后加上"月"
const month = dateParts[1].replace(/^0+/, '');
return `${month}`;
}
},
data: xAxisData
}
],
yAxis: {
type: 'value',
name: this.unit,
// nameLocation:'center',
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
min: 0,
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
axisTick: { show: false },
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12
},
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.15)',
}
},
axisLine: {
show: true,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
}
},
series: [
{
name: '实际',
type: 'line',
// stack: 'Total', // 趋势图通常不需要堆叠
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(255, 132, 0, 1)', // 加深颜色
width: 2,
},
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: realData // 使用提取出的 "实际" 数据
},
{
name: '预算',
type: 'line',
// stack: 'Total',
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(98, 213, 180, 1)', // 加深颜色
width: 2,
// type: 'dashed' // 目标线使用虚线
},
itemStyle: {
color: 'rgba(98, 213, 180, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
]),
},
data: targetData // 使用提取出的 "目标" 数据
},
]
};
// 5. 应用配置项更新图表
this.myChart.setOption(option, true);
}
},
beforeDestroy() {
// 移除窗口resize事件监听器
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
}
// 销毁图表,避免内存泄漏
if (this.myChart) {
this.myChart.dispose();
this.myChart = null;
}
}
};
</script>
<style lang="scss" scoped>
/* (你的样式代码保持不变) */
.legend {
position: absolute;
right: 12px;
top: 0px;
display: flex;
/* 使用 flex 布局让两个图例项并排且对齐 */
gap: 5px;
}
.legend-item-line {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.8);
text-align: left;
font-style: normal;
position: relative;
padding-left: 24px;
/* 为圆点和线条留出空间 */
display: flex;
align-items: center;
.line {
position: absolute;
left: 6px;
/* 线条从圆点右侧开始 */
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 16px;
/* 线条长度 */
height: 2px;
margin-right: 4px;
}
.target {
background: rgba(98, 213, 180, 1);
}
.real {
background: rgba(255, 132, 0, 1);
}
&::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
background-color: rgba(255, 132, 0, 1);
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
}
.legend-item-line:nth-child(1) {
&::before {
background-color: rgba(98, 213, 180, 1);
}
}
</style>