DeviceDonutChart.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <template>
  2. <div class="chart-wrapper">
  3. <div class="echarts-container" ref="chartRef"></div>
  4. <div class="custom-legend">
  5. <div class="legend-item">
  6. <span class="dot dot-online"></span>
  7. <span class="label">在线</span>
  8. </div>
  9. <div class="legend-item">
  10. <span class="dot dot-offline"></span>
  11. <span class="label">离线</span>
  12. </div>
  13. </div>
  14. </div>
  15. </template>
  16. <script>
  17. import * as echarts from 'echarts';
  18. // 设定设计稿基准宽度 (与你大屏的设计稿尺寸一致)
  19. const DESIGN_WIDTH = 1920;
  20. export default {
  21. name: 'DeviceDonutChart',
  22. props: {
  23. online: { type: Number, required: true },
  24. total: { type: Number, required: true }
  25. },
  26. data() {
  27. return {
  28. chart: null,
  29. resizeTimer: null
  30. };
  31. },
  32. computed: {
  33. offline() {
  34. return this.total - this.online;
  35. },
  36. percent() {
  37. return this.total === 0 ? 0 : Math.round((this.online / this.total) * 100);
  38. }
  39. },
  40. watch: {
  41. online() { this.updateChart(); },
  42. total() { this.updateChart(); }
  43. },
  44. mounted() {
  45. this.initChart();
  46. // 监听窗口大小变化
  47. window.addEventListener('resize', this.handleResize);
  48. },
  49. beforeDestroy() {
  50. window.removeEventListener('resize', this.handleResize);
  51. if (this.chart) {
  52. this.chart.dispose();
  53. this.chart = null;
  54. }
  55. },
  56. methods: {
  57. // 1. 【新增】:获取当前屏幕真实的缩放比例
  58. getRealScale() {
  59. return window.innerWidth / DESIGN_WIDTH;
  60. },
  61. initChart() {
  62. this.chart = echarts.init(this.$refs.chartRef);
  63. this.updateChart();
  64. },
  65. updateChart() {
  66. if (!this.chart) return;
  67. // 2. 【新增】:每次更新图表时,获取最新的缩放比例
  68. const scale = this.getRealScale();
  69. const option = {
  70. title: {
  71. text: `{percent|${this.percent}%}\n{count|${this.online}/${this.total}}`,
  72. left: 'center',
  73. top: 'center',
  74. textStyle: {
  75. rich: {
  76. // 3. 【核心修复】:将原本写死的字体大小(28, 14)和边距乘以 scale!
  77. // 使用 Math.round 保证字体像素是整数,渲染更清晰
  78. percent: {
  79. fontSize: Math.round(28 * scale),
  80. color: '#ffffff',
  81. fontWeight: 'bold',
  82. padding: [0, 0, Math.round(8 * scale), 0]
  83. },
  84. count: {
  85. fontSize: Math.round(14 * scale),
  86. color: '#e2e8f0'
  87. }
  88. }
  89. }
  90. },
  91. series: [
  92. {
  93. type: 'pie',
  94. radius: ['65%', '85%'],
  95. center: ['50%', '50%'],
  96. avoidLabelOverlap: false,
  97. label: { show: false },
  98. labelLine: { show: false },
  99. data: [
  100. { value: this.online, name: '在线', itemStyle: { color: '#33ccff' } },
  101. { value: this.offline, name: '离线', itemStyle: { color: '#ff7744' } }
  102. ]
  103. }
  104. ]
  105. };
  106. this.chart.setOption(option);
  107. },
  108. handleResize() {
  109. if (this.resizeTimer) clearTimeout(this.resizeTimer);
  110. this.resizeTimer = setTimeout(() => {
  111. if (this.chart) {
  112. // 4. 【核心修复】:窗口缩放时,不仅要重置 Canvas 画布大小
  113. this.chart.resize();
  114. // 还要重新执行 updateChart 触发 setOption,这样新计算出来的 scale 字体才能生效!
  115. this.updateChart();
  116. }
  117. }, 100);
  118. }
  119. }
  120. };
  121. </script>
  122. <style scoped>
  123. /* ================== CSS 部分无需修改,由于你使用了 postcss-pxtorem,这里的 px 会自动转为 rem 并自适应 ================== */
  124. .chart-wrapper {
  125. display: flex;
  126. align-items: center;
  127. width: 100%;
  128. height: 100%;
  129. }
  130. .echarts-container {
  131. flex: 1;
  132. height: 100%;
  133. min-height: 160px;
  134. }
  135. .custom-legend {
  136. width: 120px;
  137. display: flex;
  138. flex-direction: column;
  139. justify-content: center;
  140. gap: 15px;
  141. margin-right: 10px;
  142. }
  143. .legend-item {
  144. display: flex;
  145. align-items: center;
  146. background: rgba(255, 255, 255, 0.08);
  147. padding: 8px 15px;
  148. border-radius: 2px;
  149. }
  150. .dot {
  151. width: 8px;
  152. height: 8px;
  153. border-radius: 2px;
  154. margin-right: 10px;
  155. }
  156. .dot-online { background-color: #33ccff; }
  157. .dot-offline { background-color: #ff7744; }
  158. .label {
  159. color: #c0c4cc;
  160. font-size: 13px;
  161. }
  162. </style>