瀏覽代碼

修改DeviceStautasPanel组件;修改DynmicDonutchart组件可以自适应大小变化;

画安 2 周之前
父節點
當前提交
ca470be85a
共有 4 個文件被更改,包括 167 次插入166 次删除
  1. 60 100
      src/components/ui/DeviceStatusPanel.vue
  2. 97 42
      src/components/ui/DynamicDonutChart.vue
  3. 6 22
      src/views/Home.vue
  4. 4 2
      src/views/StatusMonitoring.vue

+ 60 - 100
src/components/ui/DeviceStatusPanel.vue

@@ -1,45 +1,67 @@
 <template>
   <div class="device-status-panel">
-    <div class="tab-bar">
-      <div 
-        v-for="(tab, index) in tabs" 
-        :key="index"
-        class="tab-item"
-        :class="{ 'is-active': activeIndex === index }"
-        @click="handleManualSwitch(index)"
-      >
-        {{ tab.name }}
-      </div>
-    </div>
-
-    <div class="panel-body">
-      <DeviceDonutChart 
-        :online="currentData.online" 
-        :total="currentData.total" 
-      />
-    </div>
+    <TechTabs v-model="onlineStatusActiveTab" type="segmented"   @tab-click="handleTabClick">
+      <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
+        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
+      </TechTabPane>
+      <TechTabPane label="检测器" name="detector">
+        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
+      </TechTabPane>
+      <TechTabPane label="红路灯" name="trafficLight">
+        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
+      </TechTabPane>
+    </TechTabs>
   </div>
 </template>
 
 <script>
-import DeviceDonutChart from './DeviceDonutChart.vue';
+import TechTabs from '@/components/ui/TechTabs.vue';
+import TechTabPane from '@/components/ui/TechTabPane.vue';
+import DynamicDonutChart from '@/components/ui/DynamicDonutChart.vue';
+
+
+const mockDeviceData = {
+  'signalMachine': {
+    centerTitle: '98%',
+    centerSubTitle: '980/1000',
+    chartData: [
+      { name: '在线', value: 980, color: '#32F6F8' },
+      { name: '离线', value: 20, color: '#E4D552' }
+    ]
+  },
+  'detector': {
+    centerTitle: '85%',
+    centerSubTitle: '425/500',
+    chartData: [
+      { name: '正常', value: 425, color: '#32F6F8' },
+      { name: '故障', value: 50, color: '#faad14' },
+    ]
+  },
+  'trafficLight': {
+    centerTitle: '99%',
+    centerSubTitle: '1188/1200',
+    chartData: [
+      { name: '正常发光', value: 1188, color: '#32F6F8' },
+      { name: '灯组损坏', value: 12, color: '#ff4d4f' }
+    ]
+  }
+};
 
 export default {
   name: 'DeviceStatusPanel',
   components: {
-    DeviceDonutChart
+    TechTabs,
+    TechTabPane,
+    DynamicDonutChart
   },
   data() {
     return {
-      activeIndex: 0,
-      timer: null,
-      
-      // 测试数据与 Tab 配置
-      tabs: [
-        { name: '信号机', data: { online: 980, total: 1000 } },
-        { name: '检测器', data: { online: 450, total: 500 } },
-        { name: '相机',   data: { online: 1100, total: 1200 } }
-      ]
+      // 在线状态面板
+      onlineStatusActiveTab: 'detector',
+      onlineStatusDisplayData: mockDeviceData['detector'],
     };
   },
   computed: {
@@ -49,37 +71,21 @@ export default {
     }
   },
   mounted() {
-    this.startAutoSwitch();
+    
   },
   beforeDestroy() {
-    this.stopAutoSwitch();
+    
   },
   methods: {
-    // 开启自动切换
-    startAutoSwitch() {
-      this.timer = setInterval(() => {
-        this.activeIndex = (this.activeIndex + 1) % this.tabs.length;
-      }, 2000); // 2秒切换一次
-    },
-    
-    // 停止自动切换
-    stopAutoSwitch() {
-      if (this.timer) {
-        clearInterval(this.timer);
-        this.timer = null;
+    // 监听 Tab 切换事件
+    handleTabClick(selectedTabName) {
+      console.log('用户切换了设备类型:', selectedTabName);
+
+      // 从 mock 字典中取出对应的数据并赋值,图表会自动响应式更新!
+      if (mockDeviceData[selectedTabName]) {
+        this.onlineStatusDisplayData = mockDeviceData[selectedTabName];
       }
     },
-
-    // 用户手动点击 Tab 时
-    handleManualSwitch(index) {
-      if (this.activeIndex === index) return;
-      
-      this.activeIndex = index;
-      
-      // 核心体验优化:手动点击后,重新计算 2 秒倒计时,防止刚点完就跳走
-      this.stopAutoSwitch();
-      this.startAutoSwitch();
-    }
   }
 };
 </script>
@@ -92,50 +98,4 @@ export default {
   flex-direction: column;
   box-sizing: border-box;
 }
-
-/* ================= Tab 栏样式 ================= */
-.tab-bar {
-  display: flex;
-  height: 26px;
-  flex-shrink: 0;
-  border: 1px solid rgba(100, 150, 255, 0.2);
-  border-radius: 2px;
-  margin-bottom: 10px;
-}
-
-.tab-item {
-  flex: 1;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  color: #8b92a5;
-  font-size: 14px;
-  cursor: pointer;
-  border-right: 1px solid rgba(100, 150, 255, 0.2);
-  background: rgba(20, 30, 60, 0.4);
-  transition: all 0.3s;
-}
-.tab-item:last-child {
-  border-right: none;
-}
-
-/* 选中态样式 (还原图中带一点渐变的蓝色高亮) */
-.tab-item.is-active {
-  color: #ffffff;
-  font-weight: bold;
-  background: linear-gradient(to bottom, rgba(51, 153, 255, 0.4) 0%, rgba(51, 153, 255, 0.1) 100%);
-  border: 1px solid rgba(51, 153, 255, 0.5); /* 覆盖原本的边框让其发光 */
-  margin: -1px; /* 抵消 border 增加的 1px,防止挤压 */
-  z-index: 1;
-}
-
-/* ================= 底部图表区 ================= */
-.panel-body {
-  flex: 1;
-  position: relative;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  display: flex;
-}
 </style>

+ 97 - 42
src/components/ui/DynamicDonutChart.vue

@@ -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>

+ 6 - 22
src/views/Home.vue

@@ -17,15 +17,15 @@
             <TechTabs v-model="onlineStatusActiveTab" type="segmented" autoPlay  @tab-click="handleTabClick">
               <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px" />
               </TechTabPane>
               <TechTabPane label="检测器" name="detector">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px"/>
               </TechTabPane>
               <TechTabPane label="红路灯" name="trafficLight">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px"/>
               </TechTabPane>
             </TechTabs>
   
@@ -60,15 +60,15 @@
             <TechTabs v-model="onlineStatusActiveTab" type="segmented" autoPlay @tab-click="handleTabClick">
               <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px"/>
               </TechTabPane>
               <TechTabPane label="检测器" name="detector">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px"/>
               </TechTabPane>
               <TechTabPane label="红路灯" name="trafficLight">
                 <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="163px"/>
               </TechTabPane>
             </TechTabs>
   
@@ -122,18 +122,6 @@
         securityJsCode="a7413c674852c5eaf01d90813c5b7ef6"
       />
 
-      <!-- <div class="top-search-pos">
-        <button class="btn btn-view" @click="handleSearch()">查询</button>
-        <DropdownSelect 
-          placeholder="请选择"
-          v-model="currentMapSearch" 
-          :options="mapSearchOptions" 
-          @click.native.prevent 
-          theme="solid" />
-      </div> -->
-      <!-- <div class="map-legend-pos">
-        <MapLegend></MapLegend>
-      </div> -->
     </template>
 
   </DashboardLayout>
@@ -150,8 +138,6 @@ import TechTabPane from '@/components/ui/TechTabPane.vue';
 import TickDonutChart from '@/components/ui/TickDonutChart.vue';
 import AlarmMessageList from '@/components/ui/AlarmMessageList.vue';
 import TechTable from '@/components/ui/TechTable.vue';
-import MapLegend from '@/components/ui/MapLegend.vue';
-import DropdownSelect from '@/components/ui/DropdownSelect.vue';
 import TongzhouTrafficMap from '@/components/TongzhouTrafficMap.vue';
 
 
@@ -196,8 +182,6 @@ export default {
     TickDonutChart,
     AlarmMessageList,
     TechTable,
-    MapLegend,
-    DropdownSelect,
     TongzhouTrafficMap
   },
   data() {

+ 4 - 2
src/views/StatusMonitoring.vue

@@ -20,11 +20,11 @@
                     <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
                         @node-click="handleMenuClick" />
                 </TechTabPane>
-                <TechTabPane label="干线" name="trunkLine">
+                <TechTabPane label="干线" name="trunkLine" class="menu-scroll-view">
                     <MenuItem v-for="item in menuData" :key="item.id" :node="item" :level="0"
                         @node-click="handleMenuClick" />
                 </TechTabPane>
-                <TechTabPane label="特勤" name="specialDuty">
+                <TechTabPane label="特勤" name="specialDuty" class="menu-scroll-view">
                     <MenuItem v-for="item in menuData" :key="item.id" :node="item" :level="0"
                         @node-click="handleMenuClick" />
                 </TechTabPane>
@@ -254,6 +254,8 @@ export default {
                 showClose: config.showClose !== false, // 是否显示关闭按钮
                 data: config.data || {}            // 传给内部组件的业务数据
             });
+
+            window.dispatchEvent(new Event('resize'));
         },
 
         /**