260 lines
7.0 KiB
Vue
260 lines
7.0 KiB
Vue
<template>
|
||
<!-- 外层容器:图表 + 自定义图例 -->
|
||
<div class="chart-legend-wrapper">
|
||
<!-- 图表容器 -->
|
||
<div :ref="chartRef" id="coreLineChart" class="chart-container"></div>
|
||
<!-- 自定义图例(三排三列 + 带数值) -->
|
||
<div class="custom-legend">
|
||
<div class="legend-item" v-for="(item, index) in chartData" :key="index"
|
||
>
|
||
<!-- 左侧:颜色块 + 名称(垂直居中对齐) -->
|
||
<div class="legend-left">
|
||
<div class="legend-color" :style="{ backgroundColor: customColors[index] }"></div>
|
||
<span class="legend-name">{{ item.name }}</span>
|
||
</div>
|
||
<!-- 右侧:数值(与左侧垂直居中对齐) -->
|
||
<span class="legend-value">{{ item.value }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import * as echarts from 'echarts';
|
||
|
||
export default {
|
||
name: 'Container',
|
||
props: {
|
||
chartRef: {
|
||
type: String,
|
||
required: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
myChart: null,
|
||
selectedStatus: [],
|
||
customColors: [
|
||
'rgba(255, 235, 106, 1)',
|
||
'rgba(255, 206, 106, 1)',
|
||
'rgba(255, 151, 71, 1)',
|
||
'rgba(152, 92, 255, 1)',
|
||
'rgba(113, 100, 255, 1)',
|
||
'rgba(143, 146, 255, 1)',
|
||
'rgba(40, 138, 255, 1)',
|
||
'rgba(80, 181, 255, 1)',
|
||
'rgba(168, 233, 255, 1)',
|
||
],
|
||
chartData: [
|
||
{ value: 348, name: '暂停中' },
|
||
{ value: 435, name: '已终止-回收' },
|
||
{ value: 580, name: '已终止-待回收' },
|
||
{ value: 484, name: '已终止' },
|
||
{ value: 484, name: '已完成' },
|
||
{ value: 484, name: '执行中' },
|
||
{ value: 484, name: '异常' },
|
||
{ value: 484, name: '待执行' },
|
||
{ value: 484, name: '待下发' },
|
||
|
||
]
|
||
};
|
||
},
|
||
mounted() {
|
||
this.$nextTick(() => {
|
||
this.initData();
|
||
});
|
||
},
|
||
methods: {
|
||
initData() {
|
||
const chartDom = this.$refs[this.chartRef];
|
||
if (!chartDom) {
|
||
console.error(`图表容器未找到!请确认父组件传递的 chartRef 为 "${this.chartRef}"`);
|
||
return;
|
||
}
|
||
this.myChart = echarts.init(chartDom);
|
||
this.selectedStatus = this.chartData.map(() => true);
|
||
const total = this.chartData.reduce((sum, item) => sum + item.value, 0);
|
||
|
||
const option = {
|
||
legend: { show: false },
|
||
tooltip: {
|
||
show: true,
|
||
trigger: 'item',
|
||
backgroundColor: '#FFFFFF',
|
||
boxShadow: '0px 4px 8px 0px rgba(0, 0, 0, 0.16)',
|
||
borderRadius: '4px',
|
||
width: 153,
|
||
height: 42,
|
||
borderWidth: 0,
|
||
textStyle: {
|
||
color: 'rgba(0, 0, 0, 0.85)',
|
||
fontSize: 14,
|
||
lineHeight: 1.5
|
||
},
|
||
formatter: (params) => {
|
||
const color = this.customColors[params.dataIndex];
|
||
const percent = ((params.value / total) * 100).toFixed(1);
|
||
return `
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<div style="width: 12px; height: 12px; background: ${color};"></div>
|
||
<div style="display: flex; align-items: center; gap: 20px;">
|
||
<span>${params.name}</span>
|
||
<span>${params.value}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
},
|
||
title: [
|
||
{
|
||
text: total.toString(),
|
||
left: 'center',
|
||
top: '35%',
|
||
textStyle: {
|
||
fontSize: 23,
|
||
letterSpacing: 5,
|
||
color: 'rgba(0, 0, 0, 0.65)',
|
||
fontFamily: 'PingFangSC, PingFang SC'
|
||
}
|
||
},
|
||
{
|
||
text: '总数',
|
||
left: 'center',
|
||
top: '50%',
|
||
textStyle: {
|
||
fontSize: 16,
|
||
color: 'rgba(0, 0, 0, 0.65)',
|
||
fontFamily: 'PingFangSC, PingFang SC'
|
||
}
|
||
}
|
||
],
|
||
series: [
|
||
{
|
||
type: 'pie',
|
||
radius: ['70%', '100%'],
|
||
center: ['50%', '50%'],
|
||
avoidLabelOverlap: false,
|
||
label: { show: false },
|
||
labelLine: { show: false },
|
||
itemStyle: {
|
||
color: (params) => this.customColors[params.dataIndex],
|
||
borderWidth: 2,
|
||
borderColor: '#fff'
|
||
},
|
||
data: this.chartData.map((item, index) => ({
|
||
...item,
|
||
itemStyle: { color: this.customColors[index] }
|
||
})),
|
||
selected: this.selectedStatus.reduce((obj, selected, index) => {
|
||
obj[this.chartData[index].name] = selected;
|
||
return obj;
|
||
}, {})
|
||
}
|
||
]
|
||
};
|
||
|
||
option && this.myChart.setOption(option);
|
||
window.addEventListener('resize', () => this.myChart?.resize());
|
||
this.$once('hook:destroyed', () => {
|
||
window.removeEventListener('resize', () => this.myChart?.resize());
|
||
this.myChart?.dispose();
|
||
});
|
||
},
|
||
handleLegendClick(index) {
|
||
this.selectedStatus[index] = !this.selectedStatus[index];
|
||
this.myChart.setOption({
|
||
series: [{
|
||
selected: this.selectedStatus.reduce((obj, selected, i) => {
|
||
obj[this.chartData[i].name] = selected;
|
||
return obj;
|
||
}, {})
|
||
}]
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
// 外层容器:横向排列,垂直居中
|
||
.chart-legend-wrapper {
|
||
width: 100%;
|
||
height: 170px;
|
||
display: flex;
|
||
align-items: center; // 整体垂直居中
|
||
}
|
||
|
||
// 图表容器
|
||
.chart-container {
|
||
height: 180px;
|
||
width: 30%;
|
||
}
|
||
|
||
// 图例容器:三列网格布局,垂直居中
|
||
.custom-legend {
|
||
margin-left: 10px;
|
||
box-sizing: border-box;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr); // 三列
|
||
grid-template-rows: repeat(3, 1fr); // 两行(6个item刚好2行3列)
|
||
// gap: 15px 10px; // 行列间距
|
||
align-items: center; // 网格内垂直居中
|
||
}
|
||
|
||
// 单个图例项:横向排列,垂直居中
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: left; // 内部元素垂直居中
|
||
flex-direction: column;
|
||
width: 100%;
|
||
cursor: pointer;
|
||
// gap: 10px; // 左右两部分间距
|
||
}
|
||
|
||
// 左侧:颜色块 + 名称(垂直居中)
|
||
.legend-left {
|
||
display: flex;
|
||
align-items: center; // 颜色块与名称垂直居中
|
||
gap: 8px; // 颜色块与名称间距
|
||
flex: 1; // 占满剩余空间,确保右侧数值靠右
|
||
}
|
||
|
||
// 颜色块
|
||
.legend-color {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 2px;
|
||
flex-shrink: 0; // 防止被压缩
|
||
}
|
||
|
||
// 名称
|
||
.legend-name {
|
||
font-size: 14px;
|
||
color: rgba(140, 140, 140, 1);
|
||
font-family: 'PingFangSC, PingFang SC';
|
||
white-space: nowrap; // 防止换行
|
||
}
|
||
|
||
// 数值
|
||
.legend-value {
|
||
font-size: 14px;
|
||
margin-left: 16px;
|
||
color: rgba(0, 0, 0, 0.65);
|
||
font-weight: 500;
|
||
white-space: nowrap; // 防止换行
|
||
}
|
||
|
||
// 未选中状态
|
||
// .legend-item.unselected {
|
||
|
||
// .legend-name,
|
||
// .legend-value {
|
||
// color: rgba(0, 0, 0, 0.3);
|
||
// }
|
||
|
||
// .legend-color {
|
||
// opacity: 0.5;
|
||
// }
|
||
// }
|
||
</style>
|