| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- <template>
- <div class="dashboard-donut-wrapper" :style="{ gap: uiScale.gap + 'px' }">
-
- <div class="chart-container" :style="{ width: uiScale.chartBox + 'px', height: uiScale.chartBox + 'px' }">
- <div class="chart-dom" ref="chartRef"></div>
- </div>
- <div class="legend-container">
- <div v-if="showTotal" class="total-header" :style="{ fontSize: uiScale.totalFont + 'px', marginBottom: uiScale.gap + 'px' }">
- 总时长 <span class="total-num">{{ totalValue }}</span>
- </div>
-
- <div class="legend-list" :style="{ gap: (uiScale.gap * 0.6) + 'px' }">
- <div
- class="legend-item"
- v-for="(item, index) in chartData"
- :key="index"
- :style="{ fontSize: uiScale.legendFont + 'px' }"
- >
- <i class="color-square" :style="{
- backgroundColor: item.color,
- width: uiScale.square + 'px',
- height: uiScale.square + 'px',
- marginRight: (uiScale.gap * 0.6) + 'px'
- }"></i>
- <span class="item-label" :style="{ minWidth: uiScale.labelWidth + 'px', marginRight: (uiScale.gap * 0.6) + 'px' }">{{ item.label }}</span>
- <span class="item-value">{{ item.value }}</span>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import * as echarts from 'echarts';
- import echartsResizeMixin from '@/mixins/echartsResize.js';
- export default {
- name: 'PlanDonutChart',
- // 仍然保留 mixin 用于监听容器尺寸变化触发重绘
- mixins: [echartsResizeMixin],
- props: {
- chartData: { type: Array, required: true, default: () => [] },
- centerValue: { type: [Number, String], default: 0 },
- centerLabel: { type: String, default: '' },
- showTotal: { type: Boolean, default: true },
- totalValue: { type: [Number, String], default: 0 },
- scale: { type: Number, default: 0 }
- },
- data() {
- return {
- uiScale: {
- gap: 12,
- chartBox: 140,
- totalFont: 13,
- legendFont: 12,
- square: 10,
- labelWidth: 65
- }
- };
- },
- watch: {
- chartData: {
- deep: true,
- handler() {
- this.updateChart();
- }
- },
- scale() {
- this.updateChart();
- }
- },
- mounted() {
- this.initChart();
- },
- methods: {
- initChart() {
- if (!this.$refs.chartRef) return;
- this.$_chart = echarts.init(this.$refs.chartRef);
- this.updateChart();
- },
-
- // 【核心改造】获取真实的容器缩放比例
- getLocalScale() {
- // 优先使用父组件传入的 scale prop
- if (this.scale > 0) return this.scale;
- if (!this.$el) return 1;
- // 降级:读取 CSS 变量 --s
- const sVal = getComputedStyle(this.$el).getPropertyValue('--s');
- if (sVal && sVal.trim() !== '' && !isNaN(parseFloat(sVal))) {
- return parseFloat(sVal);
- }
- return window.innerWidth / 1920;
- },
- calcSize(px) {
- return Math.round(px * this.getLocalScale());
- },
- updateChart() {
- // 每次重绘时,获取最新的局部缩放比例 s
- const s = this.getLocalScale();
- // 同步更新 HTML 元素的尺寸
- this.uiScale = {
- gap: Math.round(12 * s),
- chartBox: Math.round(140 * s),
- totalFont: Math.round(13 * s),
- legendFont: Math.round(12 * s),
- square: Math.round(10 * s),
- labelWidth: Math.round(65 * s)
- };
- if (!this.$_chart) return;
- const option = {
- color: this.chartData.map(item => item.color),
- graphic: [
- {
- type: 'text',
- left: 'center',
- top: '38%',
- style: {
- text: this.centerValue,
- fill: '#ffffff',
- fontSize: Math.round(24 * s),
- fontWeight: 'bold'
- }
- },
- {
- type: 'text',
- left: 'center',
- top: '60%',
- style: {
- text: this.centerLabel,
- fill: '#a0aec0',
- fontSize: Math.round(12 * s)
- }
- }
- ],
- series: [
- {
- type: 'pie',
- radius: [Math.round(50 * s), Math.round(65 * s)],
- center: ['50%', '50%'],
- avoidLabelOverlap: false,
- label: { show: false },
- labelLine: { show: false },
- hoverAnimation: false,
- data: this.chartData.map(item => ({
- name: item.label,
- value: item.value
- }))
- }
- ]
- };
- this.$_chart.setOption(option);
- }
- }
- };
- </script>
- <style scoped>
- /* 整体容器:水平弹性布局 */
- .dashboard-donut-wrapper {
- display: flex;
- align-items: center;
- background-color: transparent;
- padding: 0;
- color: #ffffff;
- font-family: sans-serif;
- }
- .chart-container {
- display: flex;
- justify-content: center;
- align-items: center;
- flex-shrink: 0;
- }
- .chart-dom {
- width: 100%;
- height: 100%;
- }
- .legend-container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .total-header {
- color: #a0aec0;
- }
- .total-num {
- margin-left: 4px;
- color: #ffffff;
- }
- .legend-list {
- display: flex;
- flex-direction: column;
- }
- .legend-item {
- display: flex;
- align-items: center;
- color: #cbd5e1;
- white-space: nowrap;
- }
- .color-square {
- border-radius: 1px;
- display: inline-block;
- }
- .item-label {
- display: inline-block;
- }
- .item-value {
- color: #ffffff;
- }
- </style>
|