|
|
@@ -0,0 +1,207 @@
|
|
|
+<template>
|
|
|
+ <div class="tick-donut-wrapper">
|
|
|
+ <div class="custom-legend">
|
|
|
+ <div class="legend-item" v-for="(item, index) in chartData" :key="index">
|
|
|
+ <span class="color-dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
+ <span class="legend-name">{{ item.name }}</span>
|
|
|
+ <span class="legend-value" v-if="item.value !== undefined && item.value !== null" :style="{ color: item.color }">
|
|
|
+ {{ item.value }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="echarts-container" ref="chartRef"></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import * as echarts from 'echarts';
|
|
|
+// 引入你项目的自适应 Mixin 和 px转换工具
|
|
|
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'TickDonutChart',
|
|
|
+ mixins: [echartsResize],
|
|
|
+ props: {
|
|
|
+ // 传入的图表数据,格式如:[{ name: '定周期控制', value: 400, color: '#33ccff' }, ...]
|
|
|
+ chartData: {
|
|
|
+ type: Array,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ // 中心大字,如 "650个" 或 "650↑"
|
|
|
+ centerTitle: {
|
|
|
+ type: String,
|
|
|
+ default: '0'
|
|
|
+ },
|
|
|
+ // 中心小字,如 "控制信息"
|
|
|
+ centerSubTitle: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ chartData: {
|
|
|
+ deep: true,
|
|
|
+ handler() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$_chart) this.$_chart.resize();
|
|
|
+ this.updateChart();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ centerTitle() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$_chart) this.$_chart.resize();
|
|
|
+ this.updateChart();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ centerSubTitle() {
|
|
|
+ // 同上
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$_chart) this.$_chart.resize();
|
|
|
+ this.updateChart();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initChart();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initChart() {
|
|
|
+ this.$_chart = echarts.init(this.$refs.chartRef);
|
|
|
+ this.updateChart();
|
|
|
+ },
|
|
|
+ updateChart() {
|
|
|
+ if (!this.$_chart) return;
|
|
|
+
|
|
|
+ const colorPalette = this.chartData.map(item => item.color);
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ color: colorPalette,
|
|
|
+ title: {
|
|
|
+ text: `{main|${this.centerTitle}}\n{sub|${this.centerSubTitle}}`,
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ textStyle: {
|
|
|
+ rich: {
|
|
|
+ main: {
|
|
|
+ fontSize: px2echarts(26),
|
|
|
+ color: '#ffffff',
|
|
|
+ padding: [0, 0, px2echarts(6), 0]
|
|
|
+ },
|
|
|
+ sub: {
|
|
|
+ fontSize: px2echarts(14),
|
|
|
+ color: '#cccccc'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ // 第一层:外围刻度(无底线,向外辐射)
|
|
|
+ {
|
|
|
+ type: 'gauge',
|
|
|
+ z: 1,
|
|
|
+ radius: '90%', // 刻度圈的基准半径与饼图外圈严格保持一致
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ startAngle: 90,
|
|
|
+ endAngle: -270,
|
|
|
+ splitNumber: 12,
|
|
|
+ axisLine: {
|
|
|
+ show: false // 彻底隐藏最外层的那圈底线
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: true,
|
|
|
+ splitNumber: 5,
|
|
|
+ length: px2echarts(6), // 小刻度长度
|
|
|
+ distance: 0, // 负向偏移 = 向外辐射。偏移量等于长度,刚好贴在饼图边缘!
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.4)',
|
|
|
+ width: px2echarts(1)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true,
|
|
|
+ length: px2echarts(10), // 大刻度长度
|
|
|
+ distance: 0, // 同理,刚好贴在边缘向外长 10px
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.8)',
|
|
|
+ width: px2echarts(2)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: { show: false },
|
|
|
+ pointer: { show: false },
|
|
|
+ detail: { show: false }
|
|
|
+ },
|
|
|
+ // 第二层:核心数据圆环图
|
|
|
+ {
|
|
|
+ type: 'pie',
|
|
|
+ z: 2,
|
|
|
+ radius: ['55%', '70%'], // 饼图外圈也是 75%,完美接合
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ label: { show: false },
|
|
|
+ labelLine: { show: false },
|
|
|
+ data: this.chartData
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ this.$_chart.setOption(option);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.tick-donut-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ padding: 10px 0; /* 上下预留一点空间 */
|
|
|
+}
|
|
|
+
|
|
|
+/* ================= 左侧图例样式 ================= */
|
|
|
+.custom-legend {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ width: 45%; /* 图例区占据左侧 45% */
|
|
|
+ gap: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.color-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 2px;
|
|
|
+ margin-right: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-name {
|
|
|
+ color: #cccccc; /* 文字偏灰蓝色 */
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 18px;
|
|
|
+ width: 75px; /* 固定宽度,让后面的数字对齐 */
|
|
|
+}
|
|
|
+
|
|
|
+.legend-value {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 18px;
|
|
|
+ font-family: var(--title-font-family);
|
|
|
+ font-weight: bold;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================= 右侧图表容器 ================= */
|
|
|
+.echarts-container {
|
|
|
+ width: 55%;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 180px; /* 保证图表最小高度不被压扁 */
|
|
|
+}
|
|
|
+</style>
|