浏览代码

修改px2echarts的方法;重构DeviceDonumtChart和TrafficTimeSpace组件;

画安 2 天之前
父节点
当前提交
453b44d8a5
共有 3 个文件被更改,包括 69 次插入143 次删除
  1. 17 47
      src/components/ui/DeviceDonutChart.vue
  2. 41 90
      src/components/ui/TrafficTimeSpace.vue
  3. 11 6
      src/mixins/echartsResize.js

+ 17 - 47
src/components/ui/DeviceDonutChart.vue

@@ -17,22 +17,17 @@
 
 <script>
 import * as echarts from 'echarts';
-
-// 设定设计稿基准宽度 (与你大屏的设计稿尺寸一致)
-const DESIGN_WIDTH = 1920;
+// 1. 引入全局自适应 Mixin 和像素转换神器
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
 
 export default {
   name: 'DeviceDonutChart',
+  // 2. 注册混入,自动接管窗口监听、重绘和销毁!
+  mixins: [echartsResize],
   props: {
     online: { type: Number, required: true },
     total: { type: Number, required: true }
   },
-  data() {
-    return {
-      chart: null,
-      resizeTimer: null
-    };
-  },
   computed: {
     offline() {
       return this.total - this.online;
@@ -47,32 +42,19 @@ export default {
   },
   mounted() {
     this.initChart();
-    // 监听窗口大小变化
-    window.addEventListener('resize', this.handleResize);
-  },
-  beforeDestroy() {
-    window.removeEventListener('resize', this.handleResize);
-    if (this.chart) {
-      this.chart.dispose();
-      this.chart = null;
-    }
+    // 【清理】:删除了手写的 window.addEventListener
   },
+  // 【清理】:彻底删除了 beforeDestroy 里的卸载逻辑
   methods: {
-    // 1. 【新增】:获取当前屏幕真实的缩放比例
-    getRealScale() {
-      return window.innerWidth / DESIGN_WIDTH;
-    },
-
     initChart() {
-      this.chart = echarts.init(this.$refs.chartRef);
+      // 3. 按照 mixin 约定,将 ECharts 实例赋值给 this.$_chart
+      this.$_chart = echarts.init(this.$refs.chartRef);
       this.updateChart();
     },
 
+    // Mixin 会在窗口变化时,自动算出新比例,并静默调用这个 updateChart!
     updateChart() {
-      if (!this.chart) return;
-
-      // 2. 【新增】:每次更新图表时,获取最新的缩放比例
-      const scale = this.getRealScale();
+      if (!this.$_chart) return;
 
       const option = {
         title: {
@@ -81,16 +63,15 @@ export default {
           top: 'center',
           textStyle: {
             rich: {
-              // 3. 【核心修复】:将原本写死的字体大小(28, 14)和边距乘以 scale!
-              // 使用 Math.round 保证字体像素是整数,渲染更清晰
+              // 4. 【核心替换】:全部换成优雅的 px2echarts(),干掉所有手写的 scale 乘法
               percent: { 
-                fontSize: Math.round(28 * scale), 
+                fontSize: px2echarts(28), 
                 color: '#ffffff', 
                 fontWeight: 'bold', 
-                padding: [0, 0, Math.round(8 * scale), 0] 
+                padding: [0, 0, px2echarts(8), 0] 
               },
               count: { 
-                fontSize: Math.round(14 * scale), 
+                fontSize: px2echarts(14), 
                 color: '#e2e8f0' 
               }
             }
@@ -112,26 +93,15 @@ export default {
         ]
       };
       
-      this.chart.setOption(option);
-    },
-
-    handleResize() {
-      if (this.resizeTimer) clearTimeout(this.resizeTimer);
-      this.resizeTimer = setTimeout(() => {
-        if (this.chart) {
-          // 4. 【核心修复】:窗口缩放时,不仅要重置 Canvas 画布大小
-          this.chart.resize(); 
-          // 还要重新执行 updateChart 触发 setOption,这样新计算出来的 scale 字体才能生效!
-          this.updateChart();  
-        }
-      }, 100);
+      this.$_chart.setOption(option);
     }
+    // 【清理】:彻底删除了冗长的 handleResize 和 getRealScale 方法
   }
 };
 </script>
 
 <style scoped>
-/* ================== CSS 部分无需修改,由于你使用了 postcss-pxtorem,这里的 px 会自动转为 rem 并自适应 ================== */
+/* ================== CSS 保持原样 ================== */
 .chart-wrapper {
   display: flex;
   align-items: center;

+ 41 - 90
src/components/ui/TrafficTimeSpace.vue

@@ -4,12 +4,13 @@
 
 <script>
 import * as echarts from 'echarts';
-
-// 定义大屏设计稿基准宽度 (假设为 1920)
-const DESIGN_WIDTH = 1920;
+// 1. 引入我们的防变形神器和混入
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js'; 
 
 export default {
   name: 'TrafficTimeSpace',
+  // 2. 注册混入,自动接管组件的图表缩放、数据重绘和销毁生命周期
+  mixins: [echartsResize], 
   props: {
     intersections: { type: Array, required: true },
     distances: { type: Array, required: true },
@@ -24,9 +25,9 @@ export default {
   },
   data() {
     return {
-      chart: null,
       scrollTimer: null,
       currentViewTime: 0
+      // 删除了 chart 实例,交由 mixin 的 $_chart 管理
     };
   },
   computed: {
@@ -52,15 +53,7 @@ export default {
   },
   beforeDestroy() {
     this.stopScroll();
-    if (this._resizeObserver) {
-      this._resizeObserver.disconnect();
-      this._resizeObserver = null;
-    }
-    if (this.chart) {
-      this.chart.dispose();
-      this.chart = null;
-    }
-    window.removeEventListener('resize', this._resizeHandler);
+    // 所有的 resize 和 chart.dispose 统统删掉,mixin 会帮你兜底清理!
   },
   watch: {
     waveData() { this.updateChart(); },
@@ -68,65 +61,31 @@ export default {
     autoScroll(val) { val ? this.startScroll() : this.stopScroll(); }
   },
   methods: {
-    // 【新增】:获取真实缩放比例
-    getRealScale() {
-      return window.innerWidth / DESIGN_WIDTH;
-    },
-
     initChart() {
-      this.chart = echarts.init(this.$refs.chartContainer);
-      
-      // 窗口变化时的防抖处理
-      this._resizeHandler = () => {
-        if (this.chart) {
-          this.chart.resize();
-          this.updateChart(); // 【关键】:尺寸变了,需要重新生成 option 刷新字体和线条粗细!
-        }
-      };
-      window.addEventListener('resize', this._resizeHandler);
-
-      // 容器变化时的处理 (拖拽拉伸弹窗)
-      if (typeof ResizeObserver !== 'undefined') {
-        this._roaPending = false;
-        this._resizeObserver = new ResizeObserver(() => {
-          if (!this._roaPending) {
-            this._roaPending = true;
-            requestAnimationFrame(() => {
-              this._roaPending = false;
-              if (this.chart) {
-                this.chart.resize();
-                this.updateChart(); // 【关键】:同样需要重新渲染内部配置
-              }
-            });
-          }
-        });
-        this._resizeObserver.observe(this.$refs.chartContainer);
-      }
-
+      // 3. 将实例赋值给 $_chart (mixin 规定的变量)
+      this.$_chart = echarts.init(this.$refs.chartContainer);
       this.updateChart();
     },
 
+    // mixin 中的 resize 会在窗口变化时自动调用这个方法
     updateChart() {
-      if (!this.chart) return;
+      if (!this.$_chart) return;
 
       const self = this;
       const distances = this.distances;
       const intersections = this.reversedIntersections;
       const maxDist = this.maxDistance;
-      
-      // 拿到当前缩放比例
-      const scale = this.getRealScale();
 
-      this.chart.setOption({
+      this.$_chart.setOption({
         backgroundColor: 'transparent',
         animation: false,
         tooltip: { show: false },
-        // 网格的边距也乘一下,防止屏幕缩小时文字被切掉
         grid: { 
-          left: Math.round(90 * scale), 
-          right: Math.round(15 * scale), 
-          top: Math.round(10 * scale), 
-          bottom: Math.round(30 * scale) 
+          // 4. 网格全部使用 px2echarts
+          left: px2echarts(80), 
+          right: px2echarts(15), 
+          top: px2echarts(10), 
+          bottom: px2echarts(25) 
         },
         xAxis: {
           type: 'value',
@@ -135,7 +94,7 @@ export default {
           axisLabel: { 
             color: '#7b95b9', 
             formatter: '{value}s', 
-            fontSize: Math.round(10 * scale) // 【修复】坐标轴字体随比例缩放
+            fontSize: px2echarts(10) // 5. 坐标轴字体
           },
           splitLine: { show: true, lineStyle: { color: '#1a305d', type: 'solid' } },
           axisLine: { lineStyle: { color: '#31548e' } }
@@ -149,7 +108,7 @@ export default {
             interval: 0,
             color: '#9cb1d4',
             fontWeight: 'bold',
-            fontSize: Math.round(10 * scale), // 【修复】路口名字体随比例缩放
+            fontSize: px2echarts(10), // 5. 坐标轴字体
             formatter: value => distances.includes(value) ? intersections[distances.indexOf(value)] : ''
           },
           splitLine: { show: true, lineStyle: { color: '#1a305d' } }
@@ -157,21 +116,21 @@ export default {
         series: [
           {
             type: 'custom',
-            renderItem: function (params, api) { return self.renderWave(params, api, scale); },
+            renderItem: function (params, api) { return self.renderWave(params, api); },
             data: this.echartsWaveData,
             clip: true,
             z: 1
           },
           {
             type: 'custom',
-            renderItem: function (params, api) { return self.renderRedBackground(params, api, scale); },
+            renderItem: function (params, api) { return self.renderRedBackground(params, api); },
             data: this.echartsRedData,
             clip: true,
             z: 2
           },
           {
             type: 'custom',
-            renderItem: function (params, api) { return self.renderGreenLight(params, api, scale); },
+            renderItem: function (params, api) { return self.renderGreenLight(params, api); },
             data: this.echartsGreenData,
             clip: true,
             z: 3
@@ -180,38 +139,40 @@ export default {
       });
     },
 
-    // 注意:这里的 renderItem 都把 scale 参数传进去了
-    renderRedBackground(params, api, scale) {
+    // 6. renderItem 里的所有死像素全换成了 px2echarts
+    renderRedBackground(params, api) {
       const y = api.value(0);
       const startX = api.coord([0, y])[0];
       const endX = api.coord([this.maxDataTime, y])[0];
-      // 高度和 Y轴偏移量 都要乘上 scale
-      const rectHeight = Math.round(6 * scale);
-      const rectOffsetY = Math.round(3 * scale);
-      
       return {
         type: 'rect',
-        shape: { x: startX, y: api.coord([0, y])[1] - rectOffsetY, width: endX - startX, height: rectHeight },
+        shape: { 
+          x: startX, 
+          y: api.coord([0, y])[1] - px2echarts(3), 
+          width: endX - startX, 
+          height: px2echarts(6) 
+        },
         style: { fill: '#f02828' }
       };
     },
 
-    renderGreenLight(params, api, scale) {
+    renderGreenLight(params, api) {
       const y = api.value(0);
       const p1 = api.coord([api.value(1), y]);
       const p2 = api.coord([api.value(2), y]);
-      // 高度和 Y轴偏移量 都要乘上 scale
-      const rectHeight = Math.round(8 * scale);
-      const rectOffsetY = Math.round(4 * scale);
-
       return {
         type: 'rect',
-        shape: { x: p1[0], y: p1[1] - rectOffsetY, width: p2[0] - p1[0], height: rectHeight },
+        shape: { 
+          x: p1[0], 
+          y: p1[1] - px2echarts(4), 
+          width: p2[0] - p1[0], 
+          height: px2echarts(8) 
+        },
         style: api.style({ fill: '#68e75f' })
       };
     },
 
-    renderWave(params, api, scale) {
+    renderWave(params, api) {
       const yBottom = api.value(0), yTop = api.value(1);
       const xBL = api.value(2), xBR = api.value(3), xTL = api.value(4), xTR = api.value(5);
       const text = api.value(6), dir = api.value(7);
@@ -221,9 +182,6 @@ export default {
       const angle = -Math.atan2(ptTL[1] - ptBL[1], ptTL[0] - ptBL[0]);
       const fillColor = dir === 'up' ? this.upWaveColor : this.downWaveColor;
 
-      // 动态字体大小
-      const fontSize = Math.round(12 * scale);
-
       return {
         type: 'group',
         children: [
@@ -242,7 +200,7 @@ export default {
             style: {
               text: text,
               fill: this.waveLabelColor,
-              font: `bold ${fontSize}px sans-serif`, // 【修复】绿波带上的说明文字按比例缩放
+              font: `bold ${px2echarts(12)}px sans-serif`,
               textAlign: 'center',
               textVerticalAlign: 'middle'
             }
@@ -258,9 +216,9 @@ export default {
         if (this.currentViewTime > this.maxDataTime - this.viewWindow) {
           this.currentViewTime = 0;
         }
-        if (this.chart) {
-          // 这里仅仅更新 X 轴视图,非常轻量
-          this.chart.setOption({
+        if (this.$_chart) {
+          // 注意这里也要换成 $_chart
+          this.$_chart.setOption({
             xAxis: { min: this.currentViewTime, max: this.currentViewTime + this.viewWindow }
           });
         }
@@ -272,13 +230,6 @@ export default {
         clearInterval(this.scrollTimer);
         this.scrollTimer = null;
       }
-    },
-
-    resize() {
-      if (this.chart) {
-        this.chart.resize();
-        this.updateChart(); // 对外暴露的 resize 方法也加上重新渲染配置
-      }
     }
   }
 };

+ 11 - 6
src/mixins/echartsResize.js

@@ -1,8 +1,10 @@
-import { pageScale } from '@/utils/rem.js';
+// 【核心修改】:弃用引入的死变量,直接使用动态计算
+const DESIGN_WIDTH = 1920; // 替换为你大屏的实际设计稿宽度
 
-// 【神仙工具函数】专门解决 ECharts 内部字号、线宽在大屏上太小的问题
+// 实时计算的转换工具
 export function px2echarts(px) {
-  return px * pageScale; 
+  const scale = window.innerWidth / DESIGN_WIDTH;
+  return Math.round(px * scale); // 加 round 保证像素点为整数,防止字体虚化
 }
 
 export default {
@@ -35,11 +37,14 @@ export default {
 
       this.$_resizeHandler = debounce(() => {
         if (this.$_chart) {
-          // 容器尺寸改变时,强制 ECharts 重绘
+          // 1. 强制 ECharts 重绘画布物理大小
           this.$_chart.resize();
           
-          // 如果需要内部字体也跟着变,可以重新 setOption(可选)
-          // 很多时候单纯的 resize 就足够了,具体看业务需求
+          // 2. 【核心黑科技】:如果组件内部写了 updateChart 方法,自动触发它!
+          // 这样就可以让组件使用新的 px2echarts 重新 setOption
+          if (typeof this.updateChart === 'function') {
+            this.updateChart();
+          }
         }
       }, 100);