340 lines
11 KiB
Vue
340 lines
11 KiB
Vue
<template>
|
||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||
</template>
|
||
<script>
|
||
import * as echarts from 'echarts';
|
||
|
||
export default {
|
||
components: {},
|
||
data() {
|
||
return {
|
||
myChart: null // 存储图表实例,避免重复创建
|
||
};
|
||
},
|
||
props: {
|
||
// 明确接收的props结构,增强可读性
|
||
refName: {
|
||
type: String,
|
||
default: () => 'verticalBarChart',
|
||
},
|
||
detailData: {
|
||
type: Object,
|
||
default: () => ({
|
||
}),
|
||
}
|
||
},
|
||
mounted() {
|
||
this.$nextTick(() => {
|
||
this.updateChart();
|
||
});
|
||
},
|
||
|
||
// 新增:监听 chartData 变化
|
||
watch: {
|
||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||
detailData: {
|
||
handler() {
|
||
console.log(this.chartData, 'chartData');
|
||
|
||
this.updateChart();
|
||
},
|
||
deep: true,
|
||
immediate: true // 初始化时立即执行
|
||
}
|
||
},
|
||
methods: {
|
||
getRateFlag(rate, real, target) {
|
||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||
|
||
// 1. 完成率 >= 100 => 达标
|
||
if (rate >= 100) return 1;
|
||
|
||
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||
if (rate === 0 && target === 0) return 1;
|
||
|
||
// 其他情况 => 未达标
|
||
return 0;
|
||
},
|
||
updateChart() {
|
||
const chartDom = this.$refs[this.refName];
|
||
if (!chartDom) {
|
||
console.error('图表容器未找到!');
|
||
return;
|
||
}
|
||
|
||
if (this.myChart) {
|
||
this.myChart.dispose();
|
||
}
|
||
|
||
this.myChart = echarts.init(chartDom);
|
||
const diff = this.detailData.diff || 0
|
||
const rate = this.detailData.rate || 0
|
||
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'cross',
|
||
label: {
|
||
backgroundColor: '#6a7985'
|
||
}
|
||
},
|
||
// formatter: (params) => {
|
||
// let html = `${params[0].axisValue}<br/>`;
|
||
// params.forEach(item => {
|
||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||
// );
|
||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||
// });
|
||
// return html;
|
||
// }
|
||
},
|
||
grid: {
|
||
top: 10,
|
||
bottom: 30,
|
||
right: 50,
|
||
left: 30,
|
||
containLabel: true,
|
||
show: false // 隐藏grid背景,避免干扰
|
||
},
|
||
xAxis: {
|
||
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||
type: 'value',
|
||
// offset: 0,
|
||
// boundaryGap: true ,
|
||
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||
axisTick: { show: false },
|
||
min: 0,
|
||
//
|
||
splitNumber: 4,
|
||
axisLine: {
|
||
show: true,
|
||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||
},
|
||
axisLabel: {
|
||
color: 'rgba(0, 0, 0, 0.45)',
|
||
fontSize: 12,
|
||
interval: 0,
|
||
padding: [5, 0, 0, 0]
|
||
},
|
||
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||
},
|
||
yAxis: {
|
||
type: 'category',
|
||
axisLabel: {
|
||
color: 'rgba(0, 0, 0, 0.75)',
|
||
fontSize: 12,
|
||
interval: 0,
|
||
padding: [5, 0, 0, 0]
|
||
},
|
||
axisLine: {
|
||
show: true, // 显示Y轴轴线(关键)
|
||
lineStyle: {
|
||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||
width: 1, // 轴线宽度
|
||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||
}
|
||
},
|
||
axisTick: { show: false },
|
||
// padding: [300, 100, 100, 100],
|
||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||
},
|
||
series: [
|
||
{
|
||
// name: '预算',
|
||
type: 'bar',
|
||
barWidth: 24,
|
||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||
data: [{
|
||
value: this.detailData.real,
|
||
label: {
|
||
show: true,
|
||
position: 'right',
|
||
offset: [0, 25],
|
||
// 固定label尺寸:68px×20px
|
||
width: 68,
|
||
height: 20,
|
||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||
formatter: (params) => {
|
||
|
||
// const flags = flags || [];
|
||
const currentDiff = diff || 0;
|
||
const currentFlag = flagValue || 0;
|
||
// console.log('flags[params.dataIndex]', flags);
|
||
|
||
const prefix = currentFlag === 1 ? '+' : '-';
|
||
|
||
// 根据标志位选择不同的样式类
|
||
if (currentFlag === 1) {
|
||
// 达标 - 使用 rate-achieved 样式
|
||
return `{achieved|${currentDiff}}{text|差值}`;
|
||
} else {
|
||
// 未达标 - 使用 rate-unachieved 样式
|
||
return `{unachieved|${currentDiff}}{text|差值}`;
|
||
}
|
||
},
|
||
backgroundColor: {
|
||
type: 'linear',
|
||
x: 0, y: 0, x2: 0, y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||
{ offset: 0.2, color: '#ffffff' },
|
||
{ offset: 1, color: '#ffffff' }
|
||
]
|
||
},
|
||
shadowColor: 'rgba(191,203,215,0.5)',
|
||
shadowBlur: 2,
|
||
shadowOffsetX: 0,
|
||
shadowOffsetY: 2,
|
||
borderRadius: 4,
|
||
borderColor: '#BFCBD577',
|
||
borderWidth: 0,
|
||
lineHeight: 20,
|
||
rich: {
|
||
text: {
|
||
width: 'auto',
|
||
padding: [5, 10, 5, 0],
|
||
align: 'center',
|
||
color: '#464646',
|
||
fontSize: 11,
|
||
lineHeight: 20
|
||
},
|
||
achieved: {
|
||
width: 'auto',
|
||
padding: [5, 0, 5, 10],
|
||
align: 'center',
|
||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||
fontSize: 11,
|
||
lineHeight: 20
|
||
},
|
||
// 未达标样式
|
||
unachieved: {
|
||
width: 'auto',
|
||
padding: [5, 0, 5, 10],
|
||
align: 'center',
|
||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||
fontSize: 11,
|
||
lineHeight: 20
|
||
}
|
||
}
|
||
},
|
||
itemStyle: {
|
||
color: flagValue === 1
|
||
? {
|
||
type: 'linear',
|
||
x: 0, y: 0, x2: 0, y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||
]
|
||
}
|
||
: {
|
||
type: 'linear',
|
||
x: 0, y: 0, x2: 0, y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||
]
|
||
},
|
||
borderRadius: [4, 4, 0, 0]
|
||
}
|
||
}, {
|
||
value: this.detailData.target,
|
||
label: {
|
||
show: true,
|
||
position: 'right',
|
||
offset: [0, 25],
|
||
// 固定label尺寸:68px×20px
|
||
width: 68,
|
||
height: 20,
|
||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||
formatter: function (params) {
|
||
return `{value|完成率}{rate|${rate}%}`;
|
||
},
|
||
// 核心样式:匹配CSS需求
|
||
backgroundColor: {
|
||
type: 'linear',
|
||
x: 0,
|
||
y: 0,
|
||
x2: 0,
|
||
y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||
{ offset: 1, color: '#ffffff' }
|
||
]
|
||
},
|
||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||
shadowColor: 'rgba(191,203,215,0.5)',
|
||
shadowBlur: 2,
|
||
shadowOffsetX: 0,
|
||
shadowOffsetY: 2,
|
||
// 圆角:4px
|
||
borderRadius: 4,
|
||
// 移除边框
|
||
borderColor: '#BFCBD577',
|
||
borderWidth: 0,
|
||
// 文字垂直居中(针对富文本)
|
||
lineHeight: 20,
|
||
rich: {
|
||
value: {
|
||
// 缩小宽度和内边距,适配68px容器
|
||
width: 'auto', // 自动宽度,替代固定40px
|
||
padding: [5, 0, 5, 10], // 缩小内边距
|
||
align: 'center',
|
||
color: '#464646', // 文字灰色
|
||
fontSize: 11, // 缩小字体,适配小尺寸
|
||
lineHeight: 20 // 垂直居中
|
||
},
|
||
rate: {
|
||
width: 'auto',
|
||
padding: [5, 10, 5, 0],
|
||
align: 'center',
|
||
color: '#0B58FF', // 数字蓝色
|
||
fontSize: 11,
|
||
lineHeight: 20
|
||
}
|
||
}
|
||
},
|
||
itemStyle: {
|
||
// 预算的渐变颜色(蓝系渐变)
|
||
color: {
|
||
type: 'linear',
|
||
x: 1, y: 0, x2: 0, y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||
]
|
||
},
|
||
borderRadius: [4, 4, 0, 0],
|
||
borderWidth: 0
|
||
},
|
||
},],
|
||
|
||
},
|
||
]
|
||
};
|
||
|
||
option && this.myChart.setOption(option);
|
||
|
||
// 窗口缩放适配和销毁逻辑保持不变
|
||
window.addEventListener('resize', () => {
|
||
this.myChart && this.myChart.resize();
|
||
});
|
||
|
||
this.$once('hook:destroyed', () => {
|
||
window.removeEventListener('resize', () => {
|
||
this.myChart && this.myChart.resize();
|
||
});
|
||
this.myChart && this.myChart.dispose();
|
||
});
|
||
}
|
||
},
|
||
};
|
||
</script>
|