|
|
@@ -1,5 +1,12 @@
|
|
|
<template>
|
|
|
- <div class="donut-chart-wrapper">
|
|
|
+ <div
|
|
|
+ class="donut-chart-wrapper"
|
|
|
+ :style="{
|
|
|
+ height: finalHeight,
|
|
|
+ width: finalWidth,
|
|
|
+ '--chart-scale': scale
|
|
|
+ }"
|
|
|
+ >
|
|
|
<div class="custom-legend">
|
|
|
<div class="legend-box" v-for="(item, index) in chartData" :key="index">
|
|
|
<span class="color-dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
@@ -13,44 +20,79 @@
|
|
|
|
|
|
<script>
|
|
|
import * as echarts from 'echarts';
|
|
|
-// 1. 引入你的全局自适应 Mixin 和像素转换工具
|
|
|
-import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
|
|
|
+import echartsResize from '@/mixins/echartsResize.js';
|
|
|
|
|
|
export default {
|
|
|
name: 'DynamicDonutChart',
|
|
|
- // 2. 注册混入,自动接管自适应逻辑
|
|
|
mixins: [echartsResize],
|
|
|
props: {
|
|
|
- // 传入的图表数据:[{ name: '正常', value: 425, color: '#32F6F8' }, ...]
|
|
|
chartData: {
|
|
|
type: Array,
|
|
|
required: true
|
|
|
},
|
|
|
- // 圆环中间的大字(如 98%)
|
|
|
centerTitle: {
|
|
|
type: String,
|
|
|
default: ''
|
|
|
},
|
|
|
- // 圆环中间的小字(如 980/1000)
|
|
|
centerSubTitle: {
|
|
|
type: String,
|
|
|
default: ''
|
|
|
+ },
|
|
|
+ // 默认高度 163px
|
|
|
+ customHeight: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: '163px'
|
|
|
+ },
|
|
|
+ // 默认宽度为空,用于判断用户是否传入
|
|
|
+ customWidth: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ // 解析传入的高度数值
|
|
|
+ parsedHeight() {
|
|
|
+ return this.getPxValue(this.customHeight) || 163;
|
|
|
+ },
|
|
|
+ // 解析或计算宽度数值
|
|
|
+ parsedWidth() {
|
|
|
+ // 如果用户传入了宽度,直接解析
|
|
|
+ if (this.customWidth) {
|
|
|
+ return this.getPxValue(this.customWidth) || 376;
|
|
|
+ }
|
|
|
+ // 【核心逻辑】:如果只传入高度,根据默认比例 (376 / 163) 自动计算宽度
|
|
|
+ return this.parsedHeight * (376 / 163);
|
|
|
+ },
|
|
|
+ // 最终绑定到 style 的高度
|
|
|
+ finalHeight() {
|
|
|
+ return this.formatSize(this.customHeight, this.parsedHeight);
|
|
|
+ },
|
|
|
+ // 最终绑定到 style 的宽度
|
|
|
+ finalWidth() {
|
|
|
+ return this.customWidth
|
|
|
+ ? this.formatSize(this.customWidth, this.parsedWidth)
|
|
|
+ : `${this.parsedWidth}px`;
|
|
|
+ },
|
|
|
+ // 【核心计算】:计算缩放因子
|
|
|
+ scale() {
|
|
|
+ const scaleW = this.parsedWidth / 376;
|
|
|
+ const scaleH = this.parsedHeight / 163;
|
|
|
+
|
|
|
+ // 按照宽高比来等比例缩放:取宽、高中缩放比例较小的一个
|
|
|
+ // 这样能保证内容完整包裹在设定的宽高内而不发生溢出或变形
|
|
|
+ return Math.min(scaleW, scaleH);
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
|
- // 深度监听数组变化,触发重绘
|
|
|
chartData: {
|
|
|
deep: true,
|
|
|
handler() {
|
|
|
this.$nextTick(() => {
|
|
|
- if (this.$_chart) {
|
|
|
- this.$_chart.resize(); // 在更新数据前,先强制重算尺寸
|
|
|
- }
|
|
|
+ if (this.$_chart) this.$_chart.resize();
|
|
|
this.updateChart();
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
- // 监听标题变化
|
|
|
centerTitle() {
|
|
|
this.$nextTick(() => {
|
|
|
if (this.$_chart) this.$_chart.resize();
|
|
|
@@ -62,47 +104,64 @@ export default {
|
|
|
if (this.$_chart) this.$_chart.resize();
|
|
|
this.updateChart();
|
|
|
});
|
|
|
+ },
|
|
|
+ // 监听缩放比例变化,触发重绘
|
|
|
+ scale() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$_chart) this.$_chart.resize();
|
|
|
+ this.updateChart();
|
|
|
+ });
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
this.initChart();
|
|
|
- // 清理:无需手动监听 resize
|
|
|
},
|
|
|
- // 清理:彻底删除 beforeDestroy
|
|
|
methods: {
|
|
|
+ // 提取数值的辅助函数
|
|
|
+ getPxValue(val) {
|
|
|
+ if (!val) return null;
|
|
|
+ if (typeof val === 'number') return val;
|
|
|
+ if (typeof val === 'string' && val.endsWith('px')) {
|
|
|
+ return parseFloat(val);
|
|
|
+ }
|
|
|
+ return null; // 无法解析(例如传入 100%)时返回 null
|
|
|
+ },
|
|
|
+ // 格式化输出 CSS 的尺寸值
|
|
|
+ formatSize(originalVal, parsedVal) {
|
|
|
+ // 如果包含 %,直接返回原始值(保留原生弹性),否则返回 px 字符串
|
|
|
+ if (typeof originalVal === 'string' && originalVal.includes('%')) {
|
|
|
+ return originalVal;
|
|
|
+ }
|
|
|
+ return `${parsedVal}px`;
|
|
|
+ },
|
|
|
initChart() {
|
|
|
- // 3. 按照 mixin 约定,将实例挂载到 this.$_chart
|
|
|
this.$_chart = echarts.init(this.$refs.chartRef);
|
|
|
this.updateChart();
|
|
|
},
|
|
|
-
|
|
|
- // 4. Mixin 会在窗口变化时自动静默调用此方法
|
|
|
updateChart() {
|
|
|
if (!this.$_chart) return;
|
|
|
|
|
|
const colorPalette = this.chartData.map(item => item.color);
|
|
|
+ const scale = this.scale;
|
|
|
|
|
|
const option = {
|
|
|
color: colorPalette,
|
|
|
title: {
|
|
|
- // 【核心改造】:使用富文本结构,用 \n 换行
|
|
|
text: `{main|${this.centerTitle}}\n{sub|${this.centerSubTitle}}`,
|
|
|
left: 'center',
|
|
|
top: 'center',
|
|
|
textStyle: {
|
|
|
rich: {
|
|
|
- // 全面使用 px2echarts 包裹尺寸数值
|
|
|
main: {
|
|
|
- fontSize: px2echarts(26),
|
|
|
+ fontSize: 26 * scale,
|
|
|
color: '#ffffff',
|
|
|
fontWeight: 'bold',
|
|
|
fontFamily: 'Arial',
|
|
|
- // 用 padding 的 bottom 值代替原来的 itemGap,实现精准缩放
|
|
|
- padding: [0, 0, px2echarts(6), 0]
|
|
|
+ padding: [0, 0, 6 * scale, 0]
|
|
|
},
|
|
|
sub: {
|
|
|
- fontSize: px2echarts(14),
|
|
|
- lineHeight: px2echarts(18),
|
|
|
+ fontSize: 14 * scale,
|
|
|
+ lineHeight: 18 * scale,
|
|
|
color: '#cccccc'
|
|
|
}
|
|
|
}
|
|
|
@@ -111,8 +170,6 @@ export default {
|
|
|
series: [
|
|
|
{
|
|
|
type: 'pie',
|
|
|
- // 半径原则上也可以写成具体像素如 [px2echarts(60), px2echarts(80)]
|
|
|
- // 但写成百分比原生就自带外层容器的自适应,通常保留百分比即可
|
|
|
radius: ['60%', '80%'],
|
|
|
center: ['50%', '50%'],
|
|
|
avoidLabelOverlap: false,
|
|
|
@@ -131,49 +188,47 @@ export default {
|
|
|
|
|
|
<style scoped>
|
|
|
.donut-chart-wrapper {
|
|
|
+ --chart-scale: 1;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+ /* 取消写死的 100%,由内联 style 动态接管 */
|
|
|
}
|
|
|
|
|
|
-/* 左侧图例样式 */
|
|
|
.custom-legend {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
justify-content: center;
|
|
|
- gap: 12px;
|
|
|
+ gap: calc(12px * var(--chart-scale));
|
|
|
width: 35%;
|
|
|
- margin-right: 8px;
|
|
|
+ margin-right: calc(8px * var(--chart-scale));
|
|
|
}
|
|
|
|
|
|
.legend-box {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background: linear-gradient( 90deg, #2D426C 0%, rgba(45,66,108,0) 100%);
|
|
|
- height: 40px;
|
|
|
+ background: linear-gradient(90deg, #2D426C 0%, rgba(45,66,108,0) 100%);
|
|
|
+ height: calc(40px * var(--chart-scale));
|
|
|
}
|
|
|
|
|
|
.color-dot {
|
|
|
- width: 8px;
|
|
|
- height: 8px;
|
|
|
- margin-right: 8px;
|
|
|
- margin-left: 16px;
|
|
|
+ width: calc(8px * var(--chart-scale));
|
|
|
+ height: calc(8px * var(--chart-scale));
|
|
|
+ margin-right: calc(8px * var(--chart-scale));
|
|
|
+ margin-left: calc(16px * var(--chart-scale));
|
|
|
flex-shrink: 0;
|
|
|
- border-radius: 2px;
|
|
|
+ border-radius: calc(2px * var(--chart-scale));
|
|
|
}
|
|
|
|
|
|
.legend-name {
|
|
|
font-weight: 400;
|
|
|
- font-size: 12px;
|
|
|
+ font-size: calc(12px * var(--chart-scale));
|
|
|
color: rgba(255, 255, 255, 0.60);
|
|
|
- line-height: 18px;
|
|
|
+ line-height: calc(18px * var(--chart-scale));
|
|
|
}
|
|
|
|
|
|
-/* 右侧图表样式 */
|
|
|
.echarts-container {
|
|
|
width: 65%;
|
|
|
height: 100%;
|
|
|
- min-height: 160px;
|
|
|
+ min-height: 0;
|
|
|
}
|
|
|
</style>
|