| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- <template>
- <div class="donut-chart-wrapper">
- <div class="echarts-wrapper">
- <div class="echarts-container" ref="chartRef"></div>
-
- <div class="center-text-overlay" v-if="activeFaults.length > 1">
- <span class="main-number" :style="{ fontSize: safePx2echarts(20) + 'px' }">
- {{ totalFaults }}
- </span>
- </div>
- </div>
- <div class="custom-legend">
- <div class="legend-item" v-for="(item, index) in chartData" :key="index">
- <span class="legend-name" :style="{ fontSize: safePx2echarts(12) + 'px' }">{{ item.name }}</span>
- <span class="legend-color-box" :style="{ backgroundColor: item.color }"></span>
- </div>
- </div>
- </div>
- </template>
- <script>
- import * as echarts from 'echarts';
- import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
- export default {
- name: 'DeviceStatusDonutChart',
- mixins: [echartsResize],
- props: {
- // 父组件传入的数据格式应为: [{name: '正常', value: 0, color: '#...'}, {name: '红灯故障', value: 2, color: '#...'}]
- chartData: {
- type: Array,
- required: true,
- default: () => []
- }
- },
- data() {
- return {
- chartInstance: null
- };
- },
- computed: {
- // 过滤出真正的“故障”数据(名称不包含正常,且值大于0)
- activeFaults() {
- return this.chartData.filter(item =>
- item.name.indexOf('正常') === -1 && Number(item.value) > 0
- );
- },
- // 计算故障总数(用于图3中心显示)
- totalFaults() {
- return this.activeFaults.reduce((sum, item) => sum + Number(item.value), 0);
- }
- },
- watch: {
- chartData: {
- deep: true,
- handler() {
- this.$nextTick(() => {
- if (this.chartInstance) {
- this.chartInstance.resize();
- }
- this.updateChart();
- });
- }
- }
- },
- mounted() {
- this.$nextTick(() => {
- this.initChart();
- });
- },
- beforeDestroy() {
- if (this.resizeObserver) this.resizeObserver.disconnect();
- if (this.chartInstance) this.chartInstance.dispose();
- },
- methods: {
- safePx2echarts(val) {
- return typeof px2echarts === 'function' ? px2echarts(val) : val;
- },
- initChart() {
- if (!this.$refs.chartRef) return;
- this.chartInstance = echarts.init(this.$refs.chartRef);
- this.updateChart();
- },
- updateChart() {
- if (!this.chartInstance) return;
- this.chartInstance.clear();
- const chartWidth = this.chartInstance.getWidth();
- let seriesData = [];
- if (this.activeFaults.length === 0) {
- const normalItem = this.chartData.find(item => item.name.indexOf('正常') !== -1);
- const normalColor = normalItem ? normalItem.color : '#9FE051';
- seriesData = [{
- name: '故障',
- value: 1,
- actualValue: 0,
- itemStyle: { color: normalColor },
- label: { color: normalColor },
- labelLine: { lineStyle: { color: normalColor } }
- }];
- } else {
- seriesData = this.activeFaults.map(item => ({
- name: item.name,
- value: item.value,
- itemStyle: { color: item.color },
- label: { color: item.color },
- labelLine: { lineStyle: { color: item.color } }
- }));
- }
- const option = {
- series: [
- {
- type: 'pie',
- // 【优化1】圆环再缩小一点点,给边缘文字留出绝对安全的距离
- radius: ['40%', '55%'],
- center: ['50%', '50%'],
- startAngle: 210,
- avoidLabelOverlap: true,
- label: {
- show: true,
- position: 'outside',
- // 【优化2】文字靠近圆环一点,防止撞墙
- distance: this.safePx2echarts(5),
- formatter: (params) => {
- const displayValue = params.data.actualValue !== undefined ? params.data.actualValue : params.value;
- return `{name|${params.name}}\n{val|${displayValue}}`;
- },
- rich: {
- name: {
- fontSize: this.safePx2echarts(12),
- color: 'inherit',
- fontWeight: 'bold',
- padding: [0, 0, this.safePx2echarts(4), 0]
- // 【核心修复】删掉了 width 和 overflow,让 ECharts 自然渲染
- },
- val: {
- fontSize: this.safePx2echarts(12),
- color: 'inherit',
- fontWeight: 'bold',
- padding: [this.safePx2echarts(4), 0, 0, 0]
- // 【核心修复】删掉了 width 和 overflow
- }
- }
- },
- labelLine: {
- show: true,
- // 【优化3】斜线改短,将文字往中心拉
- length: this.safePx2echarts(8),
- length2: this.safePx2echarts(15)
- },
- labelLayout: (params) => {
- const isLeft = params.labelRect.x < chartWidth / 2;
- const points = params.labelLinePoints;
-
- if (points) {
- points[2][0] = isLeft
- ? params.labelRect.x
- : params.labelRect.x + params.labelRect.width;
-
- points[1][1] = points[2][1] = params.labelRect.y + params.labelRect.height / 2;
- }
- return {
- labelLinePoints: points
- };
- },
- data: seriesData,
- animationType: 'expansion',
- animationEasing: 'elasticOut',
- animationDuration: 2000,
- animationDelay: 200,
- }
- ]
- };
- this.chartInstance.setOption(option, true);
- }
- }
- }
- </script>
- <style scoped>
- .donut-chart-wrapper {
- display: flex;
- align-items: stretch;
- justify-content: space-between;
- width: 100%;
- height: 100%;
- }
- /* 左侧饼图容器 */
- .echarts-wrapper {
- position: relative;
- width: 65%;
- flex: 1;
- min-height: 0;
- }
- .echarts-container {
- width: 100%;
- height: 100%;
- min-height: 0;
- }
- /* 饼图中心数字 */
- .center-text-overlay {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- pointer-events: none; /* 防止遮挡鼠标悬停饼图的事件 */
- z-index: 10;
- }
- .main-number {
- color: #8392b4;
- font-weight: bold;
- font-family: Arial, sans-serif;
- line-height: 1;
- }
- /* 右侧图例 */
- .custom-legend {
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 12px;
- width: 35%; /* 占据右边 35% 空间 */
- padding-right: 10px;
- }
- .legend-item {
- display: flex;
- align-items: center;
- justify-content: flex-end; /* 靠右对齐 */
- gap: 10px;
- }
- .legend-name {
- color: #00d2ff; /* 对应图片中的青色文字 */
- font-weight: 400;
- }
- .legend-color-box {
- width: 24px; /* 宽方块 */
- height: 12px;
- border-radius: 3px;
- flex-shrink: 0;
- }
- </style>
|