287 lines
7.4 KiB
Vue
287 lines
7.4 KiB
Vue
<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'],
|
||
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: '元/㎡',
|
||
// 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>
|