Просмотр исходного кода

灾害预警绿色版本界面样式调优,智能注氮系统界面及功能开发-提交

lxh 1 день назад
Родитель
Сommit
7e5b13de33
20 измененных файлов с 1546 добавлено и 124 удалено
  1. 1 0
      src/views/vent/deviceManager/configurationTable/types.ts
  2. 0 0
      src/views/vent/home/configurable/components/detail/CustomChart.vue
  3. 28 22
      src/views/vent/home/configurable/configurable.data.ts
  4. 17 10
      src/views/vent/home/configurable/green/components/CustomList-green.vue
  5. 11 1
      src/views/vent/home/configurable/green/components/CustomTable-green.vue
  6. 76 11
      src/views/vent/home/configurable/green/components/MiniBoard-green.vue
  7. 6 1
      src/views/vent/home/configurable/green/components/ModuleOriginal-green.vue
  8. 218 7
      src/views/vent/home/configurable/green/components/content-green.vue
  9. 53 54
      src/views/vent/home/configurable/green/components/dz-chart.vue
  10. 6 1
      src/views/vent/home/configurable/green/components/moduleDual-green.vue
  11. 2 3
      src/views/vent/home/configurable/green/fire-green.vue
  12. 6 0
      src/views/vent/home/configurable/green/fusion-warn-green.vue
  13. 1 1
      src/views/vent/home/configurable/green/vent-green.vue
  14. 308 0
      src/views/vent/monitorManager/compressor/components/ceshi.json
  15. 1 1
      src/views/vent/monitorManager/compressor/components/nitrogenHome.vue
  16. 610 0
      src/views/vent/monitorManager/compressor/components/nitrogenHomeNew.vue
  17. 2 4
      src/views/vent/monitorManager/compressor/nitrogen.data.ts
  18. 181 3
      src/views/vent/monitorManager/compressor/nitrogen.dixia.threejs.ts
  19. 12 4
      src/views/vent/monitorManager/compressor/nitrogen.threejs.ts
  20. 7 1
      src/views/vent/monitorManager/nitrogen/nitrogen.threejs.ts

+ 1 - 0
src/views/vent/deviceManager/configurationTable/types.ts

@@ -290,6 +290,7 @@ export interface ModuleDataChart extends ReadFrom {
     | 'bar_cylinder'
     | 'bar_cylinder_wide'
     | 'pie_halo'
+    | 'pie_rose'
     | 'line_enhance'
     | 'pie_drag'
     | 'scatter';

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/views/vent/home/configurable/components/detail/CustomChart.vue


+ 28 - 22
src/views/vent/home/configurable/configurable.data.ts

@@ -481,12 +481,12 @@ export const testConfigVent: Config[] = [
         direction: 'column',
         items: [
           {
-            name: 'qh_curve',
-            basis: '67%',
+            name: 'board',
+            basis: '38%',
           },
           {
-            name: 'board',
-            basis: '33%',
+            name: 'qh_curve',
+            basis: '62%',
           },
         ],
       },
@@ -529,7 +529,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:15px;left:15px;',
+      position: 'top:5px;left:15px;',
     },
   },
   {
@@ -589,7 +589,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:295px;left:15px;',
+      position: 'top:290px;left:15px;',
     },
   },
   // {
@@ -725,7 +725,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:555px;left:15px;',
+      position: 'top:560px;left:15px;',
     },
   },
   {
@@ -794,7 +794,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:505px;height:260px;',
       version: '原版',
-      position: 'bottom:15px;left:420px;',
+      position: 'top:560px;left:420px;bottom:auto;',
     },
   },
   {
@@ -820,23 +820,23 @@ export const testConfigVent: Config[] = [
         link: '',
       },
       layout: {
-        direction: 'column',
+        direction: 'row',
         items: [
           {
             name: 'chart',
-            basis: '67%',
+            basis: '58%',
           },
           {
             name: 'board',
-            basis: '33%',
+            basis: '42%',
           },
         ],
       },
       board: [
         {
-          type: 'D',
+          type: 'B',
           readFrom: 'readData',
-          layout: 'label-top',
+          layout: 'val-top',
           items: [
             {
               label: '风量(m³/min)',
@@ -859,9 +859,15 @@ export const testConfigVent: Config[] = [
       table: [],
       chart: [
         {
-          type: 'pie_halo',
+          type: 'pie_rose',
           readFrom: '',
-          legend: { show: false, formatter: '{b}:{c}\n{d}%' },
+          legend: {
+            show: true,
+            bottom: 2,
+            left: 'center',
+            orient: 'horizontal',
+            labelShow: false,
+          },
           xAxis: [{ show: false }],
           yAxis: [{ show: false, name: '风量', position: 'left' }],
           series: [{ readFrom: 'piechart', xprop: 'label', yprop: 'valMock', label: '' }],
@@ -874,7 +880,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:460px;height:260px;',
       version: '原版',
-      position: 'bottom:15px;left:940px;',
+      position: 'top:560px;left:940px;bottom:auto;',
     },
   },
   {
@@ -903,12 +909,12 @@ export const testConfigVent: Config[] = [
         direction: 'column',
         items: [
           {
-            name: 'chart',
-            basis: '65%',
+            name: 'board',
+            basis: '34%',
           },
           {
-            name: 'board',
-            basis: '35%',
+            name: 'chart',
+            basis: '66%',
           },
         ],
       },
@@ -957,7 +963,7 @@ export const testConfigVent: Config[] = [
     showStyle: {
       size: 'width:460px;height:260px;',
       version: '原版',
-      position: 'bottom:15px;right:15px;',
+      position: 'top:560px;right:15px;bottom:auto;',
     },
   },
 ];
@@ -5646,7 +5652,7 @@ export const testConfigBDFire: Config[] = [
       gallery_list: [],
       table: [
         {
-          type: 'A',
+          type: 'E',
           readFrom: 'aqjkArray',
           columns: [
             {

+ 17 - 10
src/views/vent/home/configurable/green/components/CustomList-green.vue

@@ -480,34 +480,41 @@
     background-repeat: no-repeat;
     background-size: auto 100%;
     margin: 8px 10px;
+    box-sizing: border-box;
   }
 
   .list-item__content_I {
     position: relative;
     width: 100%;
     height: 100%;
+    // 内边距:避免 CO / 一氧化碳等文字贴边
+    padding: 6px 8px 4px 10px;
+    box-sizing: border-box;
   }
 
   .list-item__content_I > .list-item__label {
-    width: 60px;
-    height: 100%;
-    left: 0;
+    width: 52px;
+    height: calc(100% - 10px);
+    left: 10px;
+    top: 5px;
     position: absolute;
-    font-size: 16px;
-    line-height: 63px;
+    font-size: 15px;
+    line-height: 52px;
     color: #2f836e;
   }
 
   .list-item__content_I > .list-item__info {
-    height: 50%;
-    left: 70px;
+    height: calc(50% - 4px);
+    left: 72px;
+    top: 8px;
     position: absolute;
+    line-height: 1.2;
   }
 
   .list-item__content_I > .list-item__value {
-    top: 50%;
-    height: 50%;
-    left: 70px;
+    top: calc(50% + 2px);
+    height: calc(50% - 6px);
+    left: 72px;
     position: absolute;
     font-size: 18px;
   }

+ 11 - 1
src/views/vent/home/configurable/green/components/CustomTable-green.vue

@@ -83,7 +83,8 @@
         font-size: 14px;
       }
     }
-    .table__content_label_A {
+    .table__content_label_A,
+    .table__content_label_E {
       background-image: var(--image-content-label);
       background-size: 100% 100%;
       background-repeat: no-repeat;
@@ -151,6 +152,15 @@
         background-image: var(--image-content-text);
       }
     }
+    .table__content_list_E {
+      .table__content_list_row {
+        min-height: 46px;
+        background-size: 100% auto;
+        background-repeat: no-repeat;
+        background-position: center bottom;
+        background-image: var(--image-content-text);
+      }
+    }
     .table__content_list_C {
       .table__content_list_row {
         min-height: 50px;

+ 76 - 11
src/views/vent/home/configurable/green/components/MiniBoard-green.vue

@@ -167,27 +167,42 @@ defineEmits(['click']);
 }
 
 .mini-board_E {
-  width: 30%;
-  height: 180px;
-  padding: 20px 5px;
-  // background-image: var(--image-board_bg_1);
-  background-position: center bottom;
+  width: 31%;
+  height: 190px;
+  padding: 14px 8px 18px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: center;
+  line-height: 1.2;
+  background-position: center top;
   background-repeat: no-repeat;
   background-size: 100% 100%;
 
   &:nth-child(1) {
     background-image: var(--image-hycd);
+
+    .mini-board__value_E {
+      color: #a1dec3;
+    }
   }
 
   &:nth-child(2) {
     background-image: var(--image-dyfl);
 
+    .mini-board__value_E {
+      color: #a1d3e0;
+    }
   }
 
   &:nth-child(3) {
     background-image: var(--image-jdjl);
-  }
 
+    .mini-board__value_E {
+      color: #a1dec3;
+    }
+  }
 }
 
 .mini-board_F {
@@ -244,12 +259,23 @@ defineEmits(['click']);
 }
 
 .mini-board_K {
+  flex: 1 1 0;
   width: 50%;
+  max-width: 50%;
+  min-width: 0;
   height: 100%;
+  box-sizing: border-box;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
+  padding: 4px 6px;
+  overflow: hidden;
+
+  // label-top 模板先渲染 value,用 order 将文字置于平台上方
+  .mini-board__label_K {
+    order: -1;
+  }
 
   &:nth-child(1) {
     .mini-board__value_K {
@@ -318,14 +344,36 @@ defineEmits(['click']);
 }
 
 .mini-board__value_E {
-  font-size: 20px;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  margin-top: 8px;
+  margin-bottom: 0;
+  padding: 0 2px;
+  box-sizing: border-box;
+  color: #3afde7;
+  font-size: 15px;
   font-weight: bold;
+  line-height: 1.35;
+  white-space: normal;
+  word-break: break-all;
+  text-align: center;
 }
 
 .mini-board__label_E {
-  line-height: 20px;
-  height: 90px;
-  padding-top: 30%;
+  width: 100%;
+  height: auto;
+  min-height: 86px;
+  padding: 48px 4px 0;
+  box-sizing: border-box;
+  color: #fff;
+  font-size: 12px;
+  line-height: 1.4;
+  white-space: normal;
+  word-break: break-all;
+  text-align: center;
   background-repeat: no-repeat;
   background-position: center top;
 }
@@ -373,7 +421,24 @@ defineEmits(['click']);
 }
 
 .mini-board__label_K {
-  z-index: 999;
+  position: relative;
+  z-index: 1;
+  width: 100%;
+  margin: 0 0 8px;
+  color: #fff;
+  font-size: 13px;
+  line-height: 20px;
+  height: 20px;
+  text-align: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  flex-shrink: 0;
+}
+
+.mini-board__value_K {
+  margin-bottom: 0;
+  flex-shrink: 0;
 }
 
 // .mini-board_H_low_risk {

+ 6 - 1
src/views/vent/home/configurable/green/components/ModuleOriginal-green.vue

@@ -11,7 +11,12 @@
   >
     <slot>
       <Header :deviceType="deviceType" :moduleData="moduleData" :data="data" @select="selectedData = $event" />
-      <Content :style="{ height: header.show ? 'calc(100% - 30px)' : '100%' }" :moduleData="moduleData" :data="selectedData" />
+      <Content
+        :style="{ height: header.show ? 'calc(100% - 30px)' : '100%' }"
+        :device-type="deviceType"
+        :moduleData="moduleData"
+        :data="selectedData"
+      />
     </slot>
   </component>
 </template>

+ 218 - 7
src/views/vent/home/configurable/green/components/content-green.vue

@@ -18,13 +18,24 @@
       Not Supportted Link Or Browser
     </video>
     <div class="flex w-full h-full" :style="{ flexDirection: layout.direction }">
-      <div v-for="config in layoutConfig" :key="config.name" :style="{ flexBasis: config.basis, overflow: config.overflow ? 'hidden' : 'hidden' }">
+      <div
+        v-for="config in layoutConfig"
+        :key="config.name"
+        :style="getLayoutItemStyle(config)"
+      >
         <!-- 告示板部分 -->
         <template v-if="config.name === 'board'">
-          <div class="content__module flex flex-justify-around flex-items-center flex-wrap">
+          <div
+            class="content__module content__board flex flex-justify-around flex-items-center"
+            :class="[
+              boardClassName,
+              config.type === 'E' ? 'content__board--dust-explode' : '',
+              config.type === 'K' ? 'content__board--fire-platforms' : '',
+            ]"
+          >
             <MiniBoard
-              v-for="item in config.items"
-              :key="item.prop"
+              v-for="(item, idx) in config.items"
+              :key="item.label || idx"
               :label="item.label"
               :value="item.value"
               :dw="item.dw"
@@ -36,12 +47,17 @@
 
         <!-- 图表部分,这部分通常需要填充,有告示板、Header等内容需要填充父级 -->
         <template v-if="config.name === 'chart'">
-          <CustomChart class="content__module" :chart-config="config.config" :chart-data="config.data" />
+          <CustomChart
+            class="content__module"
+            :class="{ 'content__chart--majorpath': deviceType === 'sys_majorpath' }"
+            :chart-config="config.config"
+            :chart-data="config.data"
+          />
         </template>
         <!-- 通常列表部分 -->
         <template v-if="config.name === 'list'">
           <template v-if="config.type === 'timeline'">
-            <TimelineList class="content__module" :list-config="config.items" />
+            <TimelineList class="content__module content__timeline-green" :list-config="config.items" />
           </template>
           <template v-else-if="config.type === 'timelineNew'">
             <TimelineListNew class="content__module" :list-config="config.items" />
@@ -73,7 +89,7 @@
           <BlastDelta class="content__module" :pos-monitor="config.data" :canvasSize="{ width: 250, height: 178 }" />
         </template>
         <template v-if="config.name === 'qh_curve'">
-          <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
+          <QHCurve class="content__module content__qh-curve" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" />
@@ -137,10 +153,34 @@
   const props = defineProps<{
     data: any;
     moduleData: Config['moduleData'];
+    deviceType?: string;
   }>();
 
   const { background, layout } = props.moduleData;
 
+  const boardClassName = computed(() => {
+    if (props.deviceType === 'fanmain') return 'content__board--fanmain';
+    if (props.deviceType === 'sys_majorpath') return 'content__board--majorpath';
+    if (props.deviceType === 'sys_surface_caimei') return 'content__board--surface';
+    return 'flex-wrap';
+  });
+
+  function getLayoutItemStyle(config: { name: string; basis: string; overflow?: boolean }) {
+    const style: Record<string, string> = {
+      flexBasis: config.basis,
+      overflow: 'hidden',
+    };
+    // 选项卡在上:防止被压缩后换行叠到图表上
+    if (
+      config.name === 'board' &&
+      (props.deviceType === 'fanmain' || props.deviceType === 'sys_surface_caimei')
+    ) {
+      style.flexShrink = '0';
+      style.minHeight = '56px';
+    }
+    return style;
+  }
+
   // 获取当原始配置带 items 项时的最终 items 配置
   function getItems(raw, items: CommonItem[]) {
     return items.map((i) => {
@@ -407,6 +447,177 @@
     }
   }
 
+  .content__board {
+    padding: 2px 0 4px;
+    box-sizing: border-box;
+    align-content: center;
+    column-gap: 4px;
+  }
+
+  /* 煤尘爆炸性:三列卡片铺满内容区,优化间距与可读性 */
+  .content__board--dust-explode {
+    height: 100%;
+    padding: 6px 4px 10px;
+    gap: 6px;
+    align-items: stretch;
+    justify-content: space-around;
+    overflow: hidden;
+
+    :deep(.mini-board_E) {
+      height: 100%;
+      max-height: none;
+      min-height: 0;
+    }
+  }
+
+  /* 火灾监测:内因/外因双平台并排,避免换行叠字 */
+  .content__board--fire-platforms {
+    flex-wrap: nowrap;
+    height: 100%;
+    padding: 6px 8px 4px;
+    gap: 12px;
+    align-items: center;
+    justify-content: space-around;
+    overflow: hidden;
+    box-sizing: border-box;
+
+    :deep(.mini-board_K) {
+      flex: 1 1 0;
+      width: auto;
+      max-width: none;
+    }
+  }
+
+  /* 主通风机:三张数据卡单行对齐,避免与图表图例重叠 */
+  .content__board--fanmain {
+    flex-wrap: nowrap;
+    justify-content: space-between;
+    align-items: center;
+    padding: 4px 2px 6px;
+    gap: 4px;
+    min-height: 54px;
+    height: 100%;
+    overflow: hidden;
+
+    :deep(.mini-board_D) {
+      flex: 1 1 0;
+      width: auto;
+      min-width: 0;
+      max-width: 120px;
+      height: 48px;
+      margin: 0;
+    }
+  }
+
+  /* 工作面智能管控:选项卡在上,与主通风机类似 */
+  .content__board--surface {
+    flex-wrap: nowrap;
+    justify-content: space-between;
+    align-items: center;
+    padding: 4px 4px 6px;
+    gap: 6px;
+    min-height: 54px;
+    height: 100%;
+    overflow: hidden;
+
+    :deep(.mini-board_B) {
+      flex: 1 1 0;
+      width: auto;
+      min-width: 0;
+      max-width: 140px;
+      height: 50px;
+      margin: 0;
+      padding: 2px 6px 4px;
+    }
+
+    :deep(.mini-board__value_B) {
+      font-size: 14px;
+      line-height: 20px;
+      height: 20px;
+      color: #3ecca7;
+    }
+
+    :deep(.mini-board__label_B) {
+      font-size: 12px;
+      line-height: 14px;
+      height: 14px;
+      color: #fff;
+    }
+  }
+
+  /* 关键通风路线:右侧纵向选项卡(标签在上,绿色数值框在下) */
+  .content__board--majorpath {
+    flex-direction: column;
+    flex-wrap: nowrap;
+    justify-content: space-evenly;
+    align-items: center;
+    height: 100%;
+    padding: 2px 10px 2px 4px;
+    gap: 4px;
+    overflow: hidden;
+
+    :deep(.mini-board_B) {
+      width: 100%;
+      max-width: 150px;
+      min-width: 118px;
+      height: auto;
+      margin: 0;
+      padding: 0;
+      background-image: none;
+      justify-content: center;
+      gap: 2px;
+    }
+
+    :deep(.mini-board__label_B) {
+      font-size: 12px;
+      line-height: 16px;
+      height: 16px;
+      color: #fff;
+      margin-bottom: 0;
+    }
+
+    :deep(.mini-board__value_B) {
+      width: 100%;
+      height: 34px;
+      line-height: 34px;
+      font-size: 16px;
+      color: #3ecca7;
+      font-family: 'douyuFont';
+      margin-bottom: 0;
+      background-image: url('@/assets/images/home-green/green-tag.png');
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+  }
+
+  .content__chart--majorpath {
+    height: 100%;
+    overflow: hidden;
+  }
+
+  .content__qh-curve {
+    padding-top: 2px;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  /* 预警监测时间轴圆点/轴线:适配绿色主题,不影响其他页面 */
+  .content__timeline-green {
+    :deep(.timeline-item__dot) {
+      background-color: #3ecca7;
+      box-shadow: 0 0 6px rgba(62, 204, 167, 0.55);
+    }
+
+    :deep(.timeline-item__dot::before) {
+      border-color: #3df6ff;
+    }
+
+    :deep(.timeline-component) {
+      background-image: linear-gradient(to top, #3ecca700, #3ecca799, #3ecca700);
+    }
+  }
+
   :deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
     /* background-color: transparent; */
     color: #fff;

+ 53 - 54
src/views/vent/home/configurable/green/components/dz-chart.vue

@@ -63,9 +63,9 @@
         },
         grid: {
           left: 10,
-          top: 10,
+          top: 22,
           right: 10,
-          bottom: 0,
+          bottom: 6,
         },
         yAxis: [
           {
@@ -86,7 +86,9 @@
         series: [
           {
             type: 'bar',
-            barWidth: 18,
+            // 类目间距加大,保证条形图与下一行文字有边距;条形高度适中
+            barWidth: 14,
+            barCategoryGap: '62%',
             silent: true,
             itemStyle: {
               normal: {
@@ -173,75 +175,72 @@
             symbolMargin: 3,
             symbol: 'rect',
             symbolClip: true,
-            symbolSize: [10, 24],
+            symbolSize: [8, 18],
             symbolPosition: 'start',
-            symbolOffset: [1, -4],
+            symbolOffset: [1, 0],
             symbolBoundingData: 10,
             data: [10, 10, 10, 10, 10],
             z: 2,
             animationEasing: 'elasticOut',
           },
           {
-            type: 'pictorialBar',
-            symbolBoundingData: 10,
+            // 透明 bar 与主条同高、同基准,保证右侧数量与左侧风险文字对齐
+            type: 'bar',
+            barWidth: 14,
+            barGap: '-100%',
             itemStyle: {
-              normal: {
-                color: 'none',
-              },
+              color: 'transparent',
             },
             label: {
-              normal: {
-                formatter: (params) => {
-                  let text;
-                  if (params.dataIndex == 0) {
-                    text = '{a| ' + params.data + '个}';
-                  } else if (params.dataIndex == 1) {
-                    text = '{b| ' + params.data + '个}';
-                  } else if (params.dataIndex == 2) {
-                    text = '{c| ' + params.data + '个}';
-                  } else if (params.dataIndex == 3) {
-                    text = '{d| ' + params.data + '个}';
-                  } else if (params.dataIndex == 4) {
-                    text = '{e| ' + params.data + '个}';
-                  } else {
-                    text = '{f| ' + params.data + '个}';
-                  }
-
-                  return text;
+              formatter: (params) => {
+                let text;
+                if (params.dataIndex == 0) {
+                  text = '{a| ' + echartY.value[params.dataIndex] + '个}';
+                } else if (params.dataIndex == 1) {
+                  text = '{b| ' + echartY.value[params.dataIndex] + '个}';
+                } else if (params.dataIndex == 2) {
+                  text = '{c| ' + echartY.value[params.dataIndex] + '个}';
+                } else if (params.dataIndex == 3) {
+                  text = '{d| ' + echartY.value[params.dataIndex] + '个}';
+                } else if (params.dataIndex == 4) {
+                  text = '{e| ' + echartY.value[params.dataIndex] + '个}';
+                } else {
+                  text = '{f| ' + echartY.value[params.dataIndex] + '个}';
+                }
+                return text;
+              },
+              rich: {
+                a: {
+                  color: '#ff0000',
                 },
-                rich: {
-                  a: {
-                    color: '#ff0000',
-                  },
-                  b: {
-                    color: '#ff7700',
-                  },
-                  c: {
-                    color: '#d8b66f',
-                  },
-                  d: {
-                    color: '#dbbf2e',
-                  },
-                  e: {
-                    color: '#61b4c5',
-                  },
-                  f: {
-                    color: '#fff',
-                  },
+                b: {
+                  color: '#ff7700',
+                },
+                c: {
+                  color: '#d8b66f',
+                },
+                d: {
+                  color: '#dbbf2e',
+                },
+                e: {
+                  color: '#61b4c5',
+                },
+                f: {
+                  color: '#fff',
                 },
-                position: [350, -12],
-                show: true,
               },
+              position: [350, -22],
+              show: true,
             },
-            data: echartY.value,
+            data: [10, 10, 10, 10, 10],
             z: 0,
           },
           {
             name: '外框',
             type: 'bar',
-            barGap: '-130%',
+            barGap: '-100%',
             data: [10, 10, 10, 10, 10],
-            barWidth: 29,
+            barWidth: 20,
             itemStyle: {
               normal: {
                 barBorderRadius: [0, 0, 0, 0],
@@ -256,8 +255,8 @@
             type: 'scatter',
             name: '条形',
             symbol: 'roundRect',
-            symbolSize: [6, 14],
-            symbolOffset: [3, -3],
+            symbolSize: [5, 11],
+            symbolOffset: [3, 0],
             symbolKeepAspect: true,
             itemStyle: {
               normal: {

+ 6 - 1
src/views/vent/home/configurable/green/components/moduleDual-green.vue

@@ -205,6 +205,11 @@
         color: #fff;
         font-family: 'douyuFont';
         // font-size: 12px;
+
+        .deactived {
+          cursor: pointer;
+          color: #a5a9ac;
+        }
       }
 
       .common-navR {
@@ -258,7 +263,7 @@
 
   .deactived {
     cursor: pointer;
-    color: #8087a1;
+    color: #a5a9ac;
   }
 
   :deep(.zxm-select-selector) {

+ 2 - 3
src/views/vent/home/configurable/green/fire-green.vue

@@ -227,15 +227,14 @@
     .tcontent-area {
       display: flex;
       position: absolute;
-      top: 50%;
+      top: 0;
       left: 0;
       box-sizing: border-box;
       align-items: center;
       justify-content: space-around;
       width: 100%;
-      height: 70px;
+      height: 100%;
       padding: 0 15px;
-      transform: translate(0, -40%);
 
       .tcontent-l {
         display: flex;

+ 6 - 0
src/views/vent/home/configurable/green/fusion-warn-green.vue

@@ -122,6 +122,12 @@
       height: 96px;
       position: absolute;
       z-index: 1;
+
+      // 标题与当前导航「灾害预警」增加黑色投影,提升可读性
+      :deep(.main-title),
+      :deep(.nav-menu-active) {
+        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.75);
+      }
     }
 
     .main-container {

+ 1 - 1
src/views/vent/home/configurable/green/vent-green.vue

@@ -145,8 +145,8 @@
     .main-container {
       position: absolute;
       top: 96px;
+      bottom: 20px;
       width: calc(100% - 30px);
-      height: calc(100% - 96px);
       margin: 0 15px;
       box-sizing: border-box;
     }

+ 308 - 0
src/views/vent/monitorManager/compressor/components/ceshi.json

@@ -0,0 +1,308 @@
+{
+    "success": true,
+    "message": "",
+    "code": 200,
+    "result": {
+        "cmd": "monitordata",
+        "msgTxt": [
+            {
+                "datalist": [
+                    {
+                        "msgType": null,
+                        "deviceID": "2102933971422810114",
+                        "strname": "制氮车间",
+                        "strinstallpos": "制氮车间",
+                        "fsectarea": "null",
+                        "planedVa": null,
+                        "regulation": null,
+                        "deviceRegulation": null,
+                        "stationname": "制氮车间",
+                        "stationtype": "redisother",
+                        "deviceType": "nitrogen_auto",
+                        "typeName": "制氮机",
+                        "netStatus": 1,
+                        "warnFlag": 0,
+                        "warnLevel": 0,
+                        "warnLevel_str": "正常",
+                        "syswarnLevel": null,
+                        "syswarnLevel_str": null,
+                        "syswarnLevel_des": null,
+                        "warnTime": null,
+                        "readTime": "2026-10-16 11:16:35",
+                        "warnDes": "",
+                        "frontGateOpenCtrl": null,
+                        "rearGateOpenCtrl": null,
+                        "readData": {
+                            "kyj_3h_pqwd": "19",
+                            "jz2_lgj_rkyl": "0",
+                            "jz1_dqyl": "0.00",
+                            "sign": "0",
+                            "jz3_jzkz_str": "是",
+                            "jz2_ld_wd": "12.42",
+                            "jz3_ysj_cw": "20.83",
+                            "jz2_ljll": "1227264",
+                            "kyj_2h_yx_str": "否",
+                            "jz2_yuxing": "0",
+                            "kyj_1h_pqyl": "0",
+                            "kyj_6h_pqwd": "18",
+                            "jz3_dqwd": "19.63",
+                            "jz3_yuxing": "0",
+                            "jz1_yuxing": "0",
+                            "jz1_jrqx_wd": "21.40",
+                            "jz3_zhudan_str": "否",
+                            "kyj_1h_yx": "0",
+                            "jz3_fjkz_str": "否",
+                            "jz2_lgj_ckyl": "0",
+                            "jz1_jzkz_str": "是",
+                            "jz1_jzkz": "1",
+                            "kyj_4h_pqyl": "0",
+                            "jz2_zhudan_str": "否",
+                            "kyj_2h_pqwd": "18",
+                            "jz2_yuxing_str": "否",
+                            "jz2_ysj_jw": "19.71",
+                            "jz2_zhudan": "0",
+                            "jz3_bcljll": "0",
+                            "jz2_ssll": "0.00",
+                            "jz2_jrqx_wd": "19.77",
+                            "jz2_dqyl": "0.00",
+                            "jz1_fjkz": "0",
+                            "jz1_bcljll": "0",
+                            "jz1_ysj_cw": "21.64",
+                            "kyj_2h_yx": "0",
+                            "jz2_fjkz_str": "否",
+                            "kyj_5h_pqwd": "19",
+                            "jz3_ljll": "110000",
+                            "jz1_zhudan_str": "否",
+                            "jz3_lgj_ckyl": "0",
+                            "jz1_ljyxsj": "1161",
+                            "jz3_ljyxsj": "516",
+                            "jz1_yuxing_str": "否",
+                            "kyj_3h_yx": "0",
+                            "kyj_5h_yx_str": "否",
+                            "kyj_4h_yx": "0",
+                            "jz3_bcljsj": "11",
+                            "jz2_fjkz": "0",
+                            "jz2_bcljsj": "14",
+                            "kyj_3h_yx_str": "否",
+                            "jz1_bcljsj": "17",
+                            "jz1_dqwd": "19.88",
+                            "kyj_1h_yx_str": "否",
+                            "jz1_fjkz_str": "否",
+                            "kyj_3h_pqyl": "0",
+                            "jz2_jzkz": "1",
+                            "jz3_ssll": "0.00",
+                            "kyj_6h_pqyl": "0",
+                            "jz2_jzkz_str": "是",
+                            "kyj_1h_pqwd": "19",
+                            "jz3_ld_wd": "6.26",
+                            "jz3_dqyl": "0.00",
+                            "jz3_yuxing_str": "否",
+                            "kyj_5h_yx": "0",
+                            "jz3_lgj_rkyl": "0",
+                            "jz3_hyl": "20.63",
+                            "jz1_lgj_ckyl": "0",
+                            "jz1_ljll": "1460000",
+                            "jz1_ld_wd": "18.92",
+                            "jz2_dqwd": "20.25",
+                            "jz1_zhudan": "0",
+                            "jz1_lgj_rkyl": "0",
+                            "kyj_4h_pqwd": "19",
+                            "jz3_fjkz": "0",
+                            "jz3_ysj_jw": "20.02",
+                            "kyj_6h_yx": "0",
+                            "tTime": "2026-10-16 11:16:31",
+                            "kyj_2h_pqyl": "0",
+                            "jz2_bcljll": "0",
+                            "jz2_hyl": "20.50",
+                            "jz3_jrqx_wd": "21.13",
+                            "jz2_ysj_cw": "20.93",
+                            "kyj_6h_yx_str": "否",
+                            "jz3_jzkz": "1",
+                            "jz1_ysj_jw": "20.02",
+                            "jz3_zhudan": "0",
+                            "kyj_4h_yx_str": "否",
+                            "kyj_5h_pqyl": "0",
+                            "jz2_ljyxsj": "356",
+                            "isRun": "-2",
+                            "jz1_ssll": "0.00",
+                            "jz1_hyl": "20.75"
+                        },
+                        "readDataDes": null,
+                        "limits": null,
+                        "summaryHour": [],
+                        "summaryDay": [],
+                        "history": [],
+                        "dayhistory": [],
+                        "totalInfo": null,
+                        "sign": null,
+                        "cameras": [],
+                        "links": [],
+                        "avgParam": {},
+                        "other1": null,
+                        "other2": null,
+                        "other3": null,
+                        "remarkInfo": null,
+                        "linkInfo": null,
+                        "addrIndex": null,
+                        "warnLogNotOkCount": 0,
+                        "otherInfo": null,
+                        "orderNum": 1,
+                        "testFlag": 0,
+                        "strRemark": null,
+                        "isAutosync": null,
+                        "isBusSup": null,
+                        "ventMode": null,
+                        "groupNumber": "",
+                        "isUnderground": ""
+                    }
+                ],
+                "avginfo": {
+                    "warnFlag": {
+                        "value": 0
+                    }
+                },
+                "typeName": "制氮机",
+                "type": "nitrogen_auto"
+            },
+            {
+                "subtype": "sys_nitrogen",
+                "datalist": [
+                    {
+                        "msgType": null,
+                        "deviceID": "2109541169397633025",
+                        "strname": "空压机房",
+                        "strinstallpos": "空压机房",
+                        "fsectarea": "null",
+                        "planedVa": null,
+                        "regulation": null,
+                        "deviceRegulation": null,
+                        "stationname": null,
+                        "stationtype": null,
+                        "deviceType": "sys_nitrogen",
+                        "typeName": "注氮",
+                        "netStatus": 1,
+                        "warnFlag": 0,
+                        "warnLevel": 0,
+                        "warnLevel_str": "正常",
+                        "syswarnLevel": null,
+                        "syswarnLevel_str": null,
+                        "syswarnLevel_des": null,
+                        "warnTime": null,
+                        "readTime": "2026-10-16 11:16:35",
+                        "warnDes": null,
+                        "frontGateOpenCtrl": null,
+                        "rearGateOpenCtrl": null,
+                        "readData": {
+                            "sign": "0",
+                            "tTime": "2026-10-16 11:16:30",
+                            "isRun": "-2"
+                        },
+                        "readDataDes": null,
+                        "limits": null,
+                        "summaryHour": [],
+                        "summaryDay": [],
+                        "history": [],
+                        "dayhistory": [],
+                        "totalInfo": {
+                            "unitNum": 0
+                        },
+                        "sign": null,
+                        "cameras": [],
+                        "links": [],
+                        "avgParam": {},
+                        "other1": null,
+                        "other2": null,
+                        "other3": null,
+                        "remarkInfo": null,
+                        "linkInfo": null,
+                        "addrIndex": null,
+                        "warnLogNotOkCount": 0,
+                        "otherInfo": null,
+                        "orderNum": null,
+                        "testFlag": null,
+                        "strRemark": null,
+                        "isAutosync": null,
+                        "isBusSup": null,
+                        "ventMode": null,
+                        "coalSeamThick": "",
+                        "strikelength": "",
+                        "coalThickness": "",
+                        "faceRetM3": "",
+                        "totalDrillingType": "",
+                        "faceLen": "",
+                        "linkEmptyFlag": "0",
+                        "totalDate": "",
+                        "stepOut1": "",
+                        "mainCoalseam": "",
+                        "drillingType": "",
+                        "coalSeam": "",
+                        "stepOut2": "",
+                        "pressureRationality1": "",
+                        "pressureRationality2": "",
+                        "userM3": "",
+                        "gasReserves": "",
+                        "linkEmpty": "",
+                        "beginDrainageDate": "",
+                        "goafManage": "",
+                        "dischargeGasEmission": "",
+                        "useM3": "",
+                        "ventCapacity": "",
+                        "stepMid2": "",
+                        "stepMid1": "",
+                        "zoneRationality1": "",
+                        "manageCoalMethod": "",
+                        "miningArea": "",
+                        "cuteyeLength": "",
+                        "totalGasVolume": "",
+                        "gasLevel": "",
+                        "supportNum": "",
+                        "totalComplteQuantity": "",
+                        "stepIn1": "",
+                        "stepIn2": "",
+                        "totalOutM3": "",
+                        "coalReserves": "",
+                        "faceIntM3": "",
+                        "cumulativeScalar": "",
+                        "totalIntM3": "",
+                        "totalAverageRate": "",
+                        "equalarea": "",
+                        "coalSpoCha": "",
+                        "thickness": "",
+                        "unitNum": "",
+                        "meterCount1": "",
+                        "meterCount2": "",
+                        "goafName": "",
+                        "meterCount3": "",
+                        "meterCount4": "",
+                        "meterCount5": "",
+                        "coalProduction": "",
+                        "drillinFootage": "",
+                        "totalRetM3": "",
+                        "useM3Perent": "",
+                        "ventRationality": "",
+                        "zoneRationality2": "",
+                        "minFirePeriod": "",
+                        "cumulativeFlow": "",
+                        "scalarRecognition": "",
+                        "coalSeamAngle": "",
+                        "totalPlanM3": "",
+                        "facePlanM3": "",
+                        "coalseam": "",
+                        "coalSeamThickness": "",
+                        "workingFaceLengeh": "",
+                        "flength": "",
+                        "panel": "",
+                        "leakage": "",
+                        "workingFaceZouxiangLength": "",
+                        "workingFaceQieyanLength": "",
+                        "originalGasContent": "",
+                        "workingFaceHeight": ""
+                    }
+                ],
+                "typeName": "综合监测系统",
+                "type": "sys"
+            }
+        ]
+    },
+    "timestamp": 1792120599543
+}

+ 1 - 1
src/views/vent/monitorManager/compressor/components/nitrogenHome.vue

@@ -345,7 +345,7 @@
 
   onMounted(async () => {
     await getMonitor(true);
-    await mountedThree().then(() => {
+    await mountedThree('nitrogen_bjhz').then(() => {
       loading.value = false;
     });
   });

+ 610 - 0
src/views/vent/monitorManager/compressor/components/nitrogenHomeNew.vue

@@ -0,0 +1,610 @@
+<template>
+  <div
+    id="nitrogenCss3D"
+    class="threejs-Object-CSS"
+    style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 2; top: 0px; left: 0px"
+  >
+    <a-spin :spinning="loading" />
+    <template>
+      <div
+        v-for="card in floatCards"
+        :key="card.id"
+        :id="card.domId"
+        class="modal-monitor model-float-card"
+        :style="{ visibility: moduleTypeName === 'ZDJ' ? 'visible' : 'hidden' }"
+      >
+        <fourBorderBg>
+          <div class="title">{{ card.title }}</div>
+          <div class="monitor-item" v-for="param in card.params" :key="param.code">
+            <span class="monitor-title">{{ param.unit ? `${param.label}(${param.unit})` : param.label }}:</span>
+            <span class="monitor-val"
+              ><span class="val">{{ param.value }}</span></span
+            >
+          </div>
+        </fourBorderBg>
+      </div>
+    </template>
+  </div>
+  <div id="nitrogen3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"></div>
+  <div class="nitrogen-home">
+    <div class="nitrogen-container">
+      <div class="top-box">
+        <!-- 左边监测数据 -->
+        <div class="lr-box left-box" :class="{ 'left-box-kyj': moduleTypeName === 'KYJ', 'left-box-zdj': moduleTypeName === 'ZDJ' }">
+          <div class="item item-l" v-for="card in unitCards" :key="card.id">
+            <ventBox1>
+              <template #title>
+                <div>{{ card.title }}</div>
+              </template>
+              <template #container>
+                <div class="monitor-box">
+                  <div class="parameter-title group-parameter-title"
+                    ><SvgIcon class="icon" size="38" name="device-group-paramer" /><span>机组参数</span></div
+                  >
+                  <div class="parameter-scroll">
+                    <div class="state-item" v-for="(row, index) in card.rows" :key="index">
+                      <div class="item-col" v-for="param in row" :key="param.code">
+                        <span class="state-title">{{ param.unit ? `${param.label}(${param.unit})` : param.label }} :</span>
+                        <span class="state-val">{{ param.value }}</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </ventBox1>
+          </div>
+        </div>
+        <!-- 右边监测曲线:空压机房不展示 -->
+        <div class="lr-box right-box" v-if="moduleTypeName !== 'KYJ'">
+          <ventBox1>
+            <template #title>
+              <div>设备实时监测曲线</div>
+            </template>
+            <template #container>
+              <BarAndLineCustom xAxisPropType="readTime" :chartData="flowChartData" height="240px" :propTypeArr="['flowRate']" :option="flowChartOption" />
+            </template>
+          </ventBox1>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup name="nitrogenHome">
+  import { onMounted, onUnmounted, ref, watch, nextTick } from 'vue';
+  import ventBox1 from '/@/components/vent/ventBox1.vue';
+  import fourBorderBg from '../../../comment/components/fourBorderBg.vue';
+  import { mountedThree, destroy, attachMonitorCards } from '../nitrogen.threejs';
+  import { list } from '../nitrogen.api';
+  import { SvgIcon } from '/@/components/Icon';
+  import BarAndLineCustom from '/@/components/chart/BarAndLineCustom.vue';
+  import { zhudanOption } from '../nitrogen.data.ts';
+
+  interface PointField {
+    code: string;
+    label: string;
+    unit: string;
+  }
+
+  interface UnitParam {
+    code: string;
+    label: string;
+    value: string;
+    unit: string;
+  }
+
+  interface UnitCard {
+    id: string;
+    title: string;
+    rows: UnitParam[][];
+  }
+
+  interface FlowPoint {
+    readTime: string;
+    flowRate: number;
+  }
+
+  interface FloatCard {
+    id: string;
+    domId: string;
+    title: string;
+    params: UnitParam[];
+  }
+
+  const props = defineProps({
+    deviceId: {
+      type: String,
+      require: true,
+    },
+    modalType: {
+      type: String,
+      require: true,
+    },
+    moduleTypeName: {
+      type: String,
+      default: '',
+    },
+  });
+  const loading = ref(true);
+
+  const nitrogenFields: PointField[] = [
+    { code: 'dqyl', label: '氮气压力', unit: 'MPa' },
+    { code: 'ysj_cw', label: '压缩机出温', unit: '℃' },
+    { code: 'ysj_jw', label: '压缩机进温', unit: '℃' },
+    { code: 'ld_wd', label: '露点温度', unit: '℃' },
+    { code: 'jrqx_wd', label: '加热器温度', unit: '℃' },
+    { code: 'lgj_ckyl', label: '冷干机出口压力', unit: 'MPa' },
+    { code: 'lgj_rkyl', label: '冷干机入口压力', unit: 'MPa' },
+    { code: 'ljyxsj', label: '累计运行时间', unit: 'h' },
+    { code: 'bcljsj', label: '本次累计时间', unit: 'h' },
+    { code: 'dqwd', label: '氮气温度', unit: '℃' },
+    { code: 'ljll', label: '累计流量', unit: 'm³' },
+    { code: 'bcljll', label: '本次累计流量', unit: 'm³' },
+    { code: 'hyl', label: '含氧量', unit: '%' },
+    { code: 'ssll', label: '瞬时流量', unit: 'm³/h' },
+  ];
+
+  const compressorFields: PointField[] = [
+    { code: 'pqwd', label: '排气温度', unit: '℃' },
+    { code: 'pqyl', label: '排气压力', unit: 'MPa' },
+  ];
+
+  const modelFloatFields: PointField[] = [
+    { code: 'ljyxsj', label: '累计运行时间', unit: 'h' },
+    { code: 'bcljsj', label: '本次累计时间', unit: 'h' },
+    { code: 'dqwd', label: '氮气温度', unit: '℃' },
+    { code: 'ljll', label: '累计流量', unit: 'm³' },
+    { code: 'ssll', label: '瞬时流量', unit: 'm³/h' },
+  ];
+
+  const modelFloatCodes = modelFloatFields.map((field) => field.code);
+
+  const unitCards = ref<UnitCard[]>([]);
+  const floatCards = ref<FloatCard[]>(buildFloatCards({}));
+  const flowChartData = ref<FlowPoint[]>([]);
+  const flowPointLimit = 12;
+  const flowYAxis = { ...zhudanOption.yAxis[0], min: 0 };
+  delete flowYAxis.max;
+  const flowChartOption = {
+    ...zhudanOption,
+    yAxis: [flowYAxis],
+  };
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  async function getMonitor(flag?) {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = await setTimeout(
+        async () => {
+          if (props.deviceId) {
+            await getDataSource();
+          }
+          if (timer) {
+            timer = null;
+          }
+          await getMonitor();
+        },
+        flag ? 0 : 1000
+      );
+    }
+  }
+
+  async function getReadData() {
+    const res = await list({ devicetype: 'sys', systemID: props.deviceId, type: 'all' });
+    const msgTxt = res?.result?.msgTxt || [];
+    const nitrogenBlock = msgTxt.find((item) => item.type === 'nitrogen_auto');
+    return nitrogenBlock?.datalist?.[0]?.readData || {};
+  }
+
+  function toRows(params: UnitParam[]) {
+    const rows: UnitParam[][] = [];
+    for (let index = 0; index < params.length; index += 2) {
+      rows.push(params.slice(index, index + 2));
+    }
+    return rows;
+  }
+
+  function readValue(readData: Record<string, string>, code: string) {
+    const value = readData[code];
+    if (value === undefined || value === null || value === '') return '-';
+    return String(value);
+  }
+
+  function buildFloatCards(readData: Record<string, string>) {
+    return [1, 2, 3].map((unitNo) => ({
+      id: `float-jz${unitNo}`,
+      domId: `nitrogenMonitor${unitNo}`,
+      title: `${unitNo}号制氮机`,
+      params: modelFloatFields.map((field) => ({
+        code: `jz${unitNo}_${field.code}`,
+        label: field.label,
+        value: readValue(readData, `jz${unitNo}_${field.code}`),
+        unit: field.unit,
+      })),
+    }));
+  }
+
+  function buildNitrogenCards(readData: Record<string, string>) {
+    const unitNos = new Set<number>();
+    Object.keys(readData).forEach((key) => {
+      const matched = key.match(/^jz(\d+)_/);
+      if (matched) unitNos.add(Number(matched[1]));
+    });
+    return Array.from(unitNos)
+      .sort((prev, next) => prev - next)
+      .map((unitNo) => {
+        const params = nitrogenFields
+          .filter((field) => !modelFloatCodes.includes(field.code))
+          .map((field) => ({
+          code: `jz${unitNo}_${field.code}`,
+          label: field.label,
+          value: readValue(readData, `jz${unitNo}_${field.code}`),
+          unit: field.unit,
+        }));
+        return {
+          id: `jz${unitNo}`,
+          title: `${unitNo}号制氮机`,
+          rows: toRows(params),
+        };
+      });
+  }
+
+  function buildCompressorCards(readData: Record<string, string>) {
+    const unitNos = new Set<number>();
+    Object.keys(readData).forEach((key) => {
+      const matched = key.match(/^kyj_(\d+)h_/);
+      if (matched) unitNos.add(Number(matched[1]));
+    });
+    return Array.from(unitNos)
+      .sort((prev, next) => prev - next)
+      .map((unitNo) => {
+        const params = compressorFields.map((field) => ({
+          code: `kyj_${unitNo}h_${field.code}`,
+          label: field.label,
+          value: readValue(readData, `kyj_${unitNo}h_${field.code}`),
+          unit: field.unit,
+        }));
+        return {
+          id: `kyj${unitNo}`,
+          title: `${unitNo}号空压机`,
+          rows: toRows(params),
+        };
+      });
+  }
+
+  function formatClock(date: Date) {
+    const pad = (num: number) => String(num).padStart(2, '0');
+    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
+  }
+
+  function appendFlowPoint(readData: Record<string, string>) {
+    const flowList = [1, 2, 3]
+      .map((unitNo) => Number(readData[`jz${unitNo}_ssll`]))
+      .filter((value) => !Number.isNaN(value));
+    if (!flowList.length) return;
+    const flowRate = Number(flowList.reduce((sum, value) => sum + value, 0).toFixed(2));
+    const readTime = formatClock(new Date());
+    const points = flowChartData.value.slice();
+    const lastPoint = points[points.length - 1];
+    if (lastPoint && lastPoint.readTime === readTime) {
+      points[points.length - 1] = { readTime, flowRate };
+      flowChartData.value = points;
+      return;
+    }
+    flowChartData.value = points.concat({ readTime, flowRate }).slice(-flowPointLimit);
+  }
+
+  async function buildUnitCards() {
+    const readData = await getReadData();
+    floatCards.value = buildFloatCards(readData);
+    if (props.moduleTypeName === 'KYJ') {
+      unitCards.value = buildCompressorCards(readData);
+    } else if (props.moduleTypeName === 'ZDJ') {
+      unitCards.value = buildNitrogenCards(readData);
+    } else {
+      unitCards.value = [];
+    }
+    return readData;
+  }
+
+  async function getDataSource() {
+    const readData = await buildUnitCards();
+    if (props.moduleTypeName !== 'KYJ') {
+      appendFlowPoint(readData);
+    }
+  }
+
+  watch(
+    () => props.moduleTypeName,
+    () => {
+      buildUnitCards();
+    },
+    { immediate: true }
+  );
+
+  onMounted(async () => {
+    await getMonitor(true);
+    await mountedThree('nitrogen_bjhz').then(() => {
+      loading.value = false;
+    });
+    await nextTick();
+    attachMonitorCards();
+  });
+
+  onUnmounted(() => {
+    destroy();
+    if (timer) {
+      clearTimeout(timer);
+      timer = undefined;
+    }
+  });
+</script>
+
+<style lang="less" scoped>
+  #nitrogenCss3D {
+    .model-float-card {
+      :deep(.win) {
+        margin: 0;
+      }
+    }
+    .modal-monitor {
+      width: 200px;
+      position: absolute;
+      left: 0px;
+      top: 0px;
+    }
+    &:deep(.win) {
+      margin: 0 !important;
+      background: #00000044;
+    }
+  }
+  .nitrogen-home {
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    z-index: 9999;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    pointer-events: none;
+    top: 60px;
+    .nitrogen-container {
+      width: 100%;
+      height: calc(100% - 100px);
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 100px;
+      .top-box {
+        width: 100%;
+        padding: 10px;
+        overflow: hidden;
+        display: flex;
+        justify-content: space-between;
+
+        .lr-box {
+          height: fit-content;
+          display: flex;
+          flex-direction: column;
+          position: relative;
+          overflow: hidden;
+          z-index: 9999;
+          pointer-events: auto;
+        }
+
+        .item {
+          width: 335px;
+          height: auto;
+          position: relative;
+          border-radius: 5px;
+          margin-top: 10px;
+          margin-bottom: 0px;
+          pointer-events: auto;
+          color: #fff;
+          overflow: hidden;
+          &:first-child {
+            margin-top: 0px;
+          }
+
+          .state-item {
+            display: grid;
+            grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+            column-gap: 8px;
+            padding: 3px 2px;
+
+            .item-col {
+              width: auto;
+              min-width: 0;
+              min-height: 28px;
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+              gap: 8px;
+              margin-right: 0;
+              padding: 3px 6px;
+              background-image: linear-gradient(to right, #39a3ff14, #39a3ff05);
+
+              .state-title {
+                color: #ffffffcc;
+                flex: 1;
+                min-width: 0;
+                font-size: 12px;
+                line-height: 1.35;
+              }
+
+              .state-val {
+                flex: none;
+                color: #00eefffe;
+                text-align: right;
+                font-size: 13px;
+                line-height: 1.35;
+                white-space: nowrap;
+              }
+            }
+          }
+        }
+
+        .item-l {
+          width: 100%;
+          .monitor-box {
+            width: 100%;
+            .parameter-title {
+              position: relative;
+              width: 100%;
+              height: 14px;
+              margin-top: 10px;
+              .icon,
+              span {
+                position: absolute;
+                top: -10px;
+              }
+            }
+            .group-parameter-title {
+              background-image: linear-gradient(to right, #39a3ff50, #39a3ff00);
+
+              .icon {
+                left: -12px;
+                top: -17px;
+              }
+              span {
+                left: 18px;
+              }
+              .item-col {
+                background-image: linear-gradient(to right, #39a3ff00, #39a3ff10);
+              }
+            }
+          }
+        }
+
+        .right-box {
+          width: 330px;
+          margin-top: 0;
+        }
+
+        .left-box {
+          width: 460px;
+          margin-top: 80px;
+          max-height: calc(100vh - 250px);
+          overflow-x: hidden;
+          overflow-y: auto;
+          padding-right: 6px;
+          padding-bottom: 12px;
+
+          .item {
+            overflow: visible;
+          }
+
+          :deep(.vent-box1-bg) {
+            overflow: visible;
+          }
+
+          :deep(.box1-top),
+          :deep(.box1-bottom) {
+            background-size: 100% 100%;
+            background-repeat: no-repeat;
+          }
+
+          :deep(.box1-bottom) {
+            position: relative;
+            bottom: auto;
+            flex-shrink: 0;
+          }
+
+          :deep(.box-container) {
+            padding-bottom: 10px;
+          }
+        }
+
+        .left-box.left-box-kyj,
+        .left-box.left-box-zdj {
+          gap: 16px;
+
+          .item {
+            margin-top: 0;
+            margin-bottom: 0;
+          }
+        }
+
+        .left-box.left-box-kyj {
+          margin-top: 0;
+          max-height: 816px;
+          padding-bottom: 8px;
+        }
+
+        .left-box.left-box-zdj {
+          gap: 8px;
+          margin-top: 0;
+          max-height: none;
+          overflow: visible;
+          padding-right: 0;
+          padding-bottom: 8px;
+
+          :deep(.box-container) {
+            padding: 10px 10px 0;
+          }
+
+          .parameter-title {
+            height: 16px;
+            margin-top: 8px;
+          }
+
+          .parameter-scroll {
+            max-height: none;
+            overflow: visible;
+          }
+
+          .state-item {
+            padding: 3px 0;
+
+            .item-col {
+              min-height: 26px;
+              padding: 2px 6px;
+            }
+          }
+        }
+      }
+      &:deep(.win) {
+        width: 100%;
+        margin: 0 !important;
+      }
+    }
+  }
+
+  &:deep(.main) {
+    .title {
+      height: 34px;
+      text-align: center;
+      font-weight: 600;
+      color: #7af5ff;
+      // background-image: url('../../../assets/img/yfj/light.png');
+      background-repeat: no-repeat;
+      background-position-x: center;
+      background-position-y: 100%;
+      background-size: 80%;
+      font-size: 16px;
+    }
+    .monitor-item {
+      width: 200px;
+      display: flex;
+      flex-direction: row;
+      width: auto;
+      margin-bottom: 3px;
+
+        .monitor-val {
+        color: #ffb700;
+        display: flex;
+        width: auto;
+
+        .val {
+          width: 80px;
+          font-size: 14px;
+        }
+      }
+    }
+    .monitor-title {
+      width: 132px;
+      color: #7af5ff;
+      font-weight: 400;
+      font-size: 14px;
+    }
+  }
+</style>

+ 2 - 4
src/views/vent/monitorManager/compressor/nitrogen.data.ts

@@ -251,10 +251,8 @@ export function getMonitorComponent() {
       nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome_dtyj.vue'));
       return nitrogenHome;
     default:
-      // nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome_blt.vue'));
-      // nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome_bet.vue'));
-      nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome.vue'));
-      // nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome_ln.vue'));
+      //nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHome.vue'));
+      nitrogenHome = defineAsyncComponent(() => import('./components/nitrogenHomeNew.vue'));
       return nitrogenHome;
   }
 }

+ 181 - 3
src/views/vent/monitorManager/compressor/nitrogen.dixia.threejs.ts

@@ -1,6 +1,6 @@
 import * as THREE from 'three';
 
-import { getTextCanvas } from '/@/utils/threejs/util';
+import { getTextCanvas, setModalCenter } from '/@/utils/threejs/util';
 import { CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
 // import * as dat from 'dat.gui';
 // const gui = new dat.GUI();
@@ -8,19 +8,37 @@ import { CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
 
 class NitrogenUnderground {
   model;
+  /** 场景中的视图标识,切换逻辑依赖该名称 */
   modelName = 'nitrogenUnderground';
+  /** 实际加载的 gltf 名称,默认与原地下模型一致 */
+  loadName = 'nitrogenUnderground';
   group: THREE.Object3D = new THREE.Object3D();
   animationTimer;
   isLRAnimation = true;
   direction = 1;
   nitrogenNum = 0;
 
-  constructor(model) {
+  constructor(model, loadName = 'nitrogenUnderground') {
     this.model = model;
+    this.loadName = loadName || 'nitrogenUnderground';
     this.group.name = this.modelName;
   }
 
   addLight() {
+    if (this.loadName !== 'nitrogenUnderground') {
+      const box = new THREE.Box3().setFromObject(this.group);
+      const size = box.getSize(new THREE.Vector3());
+      const center = box.getCenter(new THREE.Vector3());
+      const maxDim = Math.max(size.x, size.y, size.z) || 1;
+      const ambient = new THREE.AmbientLight(0xffffff, 0.85);
+      this.group.add(ambient);
+      const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
+      directionalLight.position.set(center.x + maxDim, center.y + maxDim * 1.2, center.z + maxDim);
+      directionalLight.target.position.copy(center);
+      this.group.add(directionalLight.target);
+      this.group.add(directionalLight);
+      return;
+    }
     const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
     directionalLight.position.set(64, 215, 500);
     directionalLight.target = this.group;
@@ -44,15 +62,169 @@ class NitrogenUnderground {
 
   // 设置模型位置
   setModalPosition() {
+    if (this.loadName !== 'nitrogenUnderground') {
+      this.group.scale.set(1, 1, 1);
+      setModalCenter(this.group);
+      return;
+    }
     this.group?.scale.set(22, 22, 22);
     this.group?.position.set(-10, 25, 20);
   }
 
+  /**
+   * 替换模型时按包围盒取景;原地下模型返回 null,继续使用写死机位。
+   */
+  getCameraConfig() {
+    if (this.loadName === 'nitrogenUnderground' || !this.group) return null;
+    const box = new THREE.Box3().setFromObject(this.group);
+    if (box.isEmpty()) return null;
+    const sphere = box.getBoundingSphere(new THREE.Sphere());
+    const center = sphere.center;
+    const radius = sphere.radius || 1;
+    const size = box.getSize(new THREE.Vector3());
+    const cam = this.model?.camera;
+    const vFov = (((cam?.fov as number) ?? 50) * Math.PI) / 180;
+    const aspect = (cam?.aspect as number) || 1.8;
+    const hFov = 2 * Math.atan(Math.tan(vFov / 2) * aspect);
+    const fill = 0.62;
+    const dByWidth = size.x / 2 / Math.tan(hFov / 2) / fill;
+    const dByHeight = size.y / 2 / Math.tan(vFov / 2) / fill;
+    const dFloor = (radius / Math.sin(Math.min(vFov, hFov) / 2)) * 0.9;
+    const distance = Math.max(dByWidth, dByHeight, dFloor, 1);
+    // 前左上方斜视,蓝色机组在画面左侧、黄色设备在右侧
+    const dir = new THREE.Vector3(-0.95, 0.82, 1.05).normalize();
+    const pos = center.clone().add(dir.clone().multiplyScalar(distance));
+    const lookDir = dir.clone().negate();
+    const screenUp = new THREE.Vector3(0, 1, 0).sub(lookDir.clone().multiplyScalar(lookDir.y)).normalize();
+    const target = center.clone().add(screenUp.multiplyScalar(-Math.max(size.x, size.z, size.y) * 0.13));
+    return {
+      camera: { x: pos.x, y: pos.y, z: pos.z },
+      target: { x: target.x, y: target.y, z: target.z },
+    };
+  }
+
   resetModel() {
     clearTimeout(this.animationTimer);
   }
 
+  /**
+   * 北京华正模型左侧三台蓝色制氮机。
+   * 网格很碎,不能按单块高度过滤,否则锚点为空、卡片不会挂上。
+   * 从画面右侧到左侧依次是 1 号、2 号、3 号,卡片锚在三台机体中心上方。
+   */
+  private findNitrogenMachineAnchors() {
+    this.group.updateMatrixWorld(true);
+    const groupBox = new THREE.Box3().setFromObject(this.group);
+    const groupSize = groupBox.getSize(new THREE.Vector3());
+    const maxDim = Math.max(groupSize.x, groupSize.y, groupSize.z) || 1;
+    const pieces: { center: THREE.Vector3; maxY: number; volume: number }[] = [];
+
+    this.group.traverse((obj) => {
+      const mesh = obj as THREE.Mesh;
+      if (!mesh.isMesh) return;
+      const box = new THREE.Box3().setFromObject(mesh);
+      if (box.isEmpty()) return;
+      const size = box.getSize(new THREE.Vector3());
+      const isFloor = size.y < groupSize.y * 0.06 && Math.max(size.x, size.z) > maxDim * 0.35;
+      const isTiny = Math.max(size.x, size.y, size.z) < maxDim * 0.02;
+      if (isFloor || isTiny) return;
+      pieces.push({
+        center: box.getCenter(new THREE.Vector3()),
+        maxY: box.max.y,
+        volume: size.x * size.y * size.z,
+      });
+    });
+
+    const threshold = maxDim * 0.1;
+    const clusters: { center: THREE.Vector3; sum: THREE.Vector3; maxY: number; volume: number; count: number }[] = [];
+    pieces.forEach((piece) => {
+      const cluster = clusters.find((current) => current.center.distanceTo(piece.center) < threshold);
+      if (!cluster) {
+        clusters.push({
+          center: piece.center.clone(),
+          sum: piece.center.clone(),
+          maxY: piece.maxY,
+          volume: piece.volume,
+          count: 1,
+        });
+        return;
+      }
+      cluster.sum.add(piece.center);
+      cluster.count += 1;
+      cluster.center.copy(cluster.sum).multiplyScalar(1 / cluster.count);
+      cluster.volume += piece.volume;
+      cluster.maxY = Math.max(cluster.maxY, piece.maxY);
+    });
+
+    const maxVolume = Math.max(...clusters.map((cluster) => cluster.volume), 0);
+    const large = clusters.filter((cluster) => cluster.volume > maxVolume * 0.08);
+    const source = large.length >= 3 ? large : clusters;
+    return this.pickMachineTrio(source);
+  }
+
+  private pickMachineTrio(clusters: { center: THREE.Vector3; maxY: number }[]) {
+    const splitByAxis = (axis: 'x' | 'z') => {
+      if (clusters.length < 2) return null;
+      const sorted = clusters.slice().sort((prev, next) => prev.center[axis] - next.center[axis]);
+      let gapIndex = 0;
+      let gap = -1;
+      for (let index = 1; index < sorted.length; index += 1) {
+        const currentGap = sorted[index].center[axis] - sorted[index - 1].center[axis];
+        if (currentGap > gap) {
+          gap = currentGap;
+          gapIndex = index;
+        }
+      }
+      const beforeGap = sorted.slice(0, gapIndex);
+      const afterGap = sorted.slice(gapIndex);
+      if (beforeGap.length >= 3) return beforeGap.slice(-3);
+      if (afterGap.length >= 3) return afterGap.slice(0, 3);
+      if (beforeGap.length === 3) return beforeGap;
+      if (afterGap.length === 3) return afterGap;
+      return null;
+    };
+
+    const trio = splitByAxis('x') || splitByAxis('z') || clusters.slice(0, 3);
+    if (trio.length < 2) return trio;
+    const spanX = Math.max(...trio.map((item) => item.center.x)) - Math.min(...trio.map((item) => item.center.x));
+    const spanZ = Math.max(...trio.map((item) => item.center.z)) - Math.min(...trio.map((item) => item.center.z));
+    const axis = spanX >= spanZ ? 'x' : 'z';
+    const ordered = trio.slice().sort((prev, next) => prev.center[axis] - next.center[axis]);
+    const frontDir = new THREE.Vector3(-0.95, 0, 1.05).normalize();
+    if (ordered[ordered.length - 1].center.dot(frontDir) > ordered[0].center.dot(frontDir)) ordered.reverse();
+    return ordered;
+  }
+
+  private addBjhzMonitorCards() {
+    if (!this.group) return;
+    const anchors = this.findNitrogenMachineAnchors();
+    if (anchors.length < 3) return;
+    const groupBox = new THREE.Box3().setFromObject(this.group);
+    const groupSize = groupBox.getSize(new THREE.Vector3());
+    const maxDim = Math.max(groupSize.x, groupSize.y, groupSize.z) || 1;
+    const scale = maxDim * 0.0011;
+
+    anchors.slice(0, 3).forEach((anchor, index) => {
+      const element = document.getElementById('nitrogenMonitor' + (index + 1)) as HTMLElement;
+      let sprite = this.group.getObjectByName('monitorNitrogenText' + index) as CSS3DSprite | undefined;
+      if (!sprite) {
+        if (!element) return;
+        element.style.pointerEvents = 'none';
+        sprite = new CSS3DSprite(element);
+        sprite.name = 'monitorNitrogenText' + index;
+        this.group.add(sprite);
+      }
+      sprite.scale.set(scale, scale, scale);
+      const localTop = this.group.worldToLocal(new THREE.Vector3(anchor.center.x, anchor.maxY, anchor.center.z));
+      sprite.position.set(localTop.x, localTop.y + Math.max(groupSize.y * 0.08, maxDim * 0.02), localTop.z);
+    });
+  }
+
   addCssText = () => {
+    if (this.loadName !== 'nitrogenUnderground') {
+      this.addBjhzMonitorCards();
+      return;
+    }
     if (this.nitrogenNum > 0) {
       for (let i = 0; i < 2; i++) {
         if (this.group && !this.group.getObjectByName('monitorNitrogenText' + i)) {
@@ -99,8 +271,14 @@ class NitrogenUnderground {
 
   mountedThree() {
     return new Promise((resolve) => {
-      this.model.setGLTFModel([this.modelName]).then((gltf) => {
+      this.model.setGLTFModel([this.loadName]).then((gltf) => {
+        if (!gltf?.[0]) {
+          console.warn(`[nitrogen] 模型 ${this.loadName} 加载失败`);
+          resolve(null);
+          return;
+        }
         this.group = gltf[0];
+        this.group.name = this.modelName;
         this.setModalPosition();
         this.addLight();
         resolve(null);

+ 12 - 4
src/views/vent/monitorManager/compressor/nitrogen.threejs.ts

@@ -32,6 +32,10 @@ const mouseEvent = (event) => {
 };
 
 /* 添加监控数据 */
+export const attachMonitorCards = () => {
+  nitrogenUndergroundObj?.addCssText?.call(nitrogenUndergroundObj);
+};
+
 export const addText = () => {
   if (modalType === 'nitrogen') {
     return nitrogenObj?.addCssText?.call(nitrogenObj);
@@ -121,8 +125,9 @@ export const setModelType = (type, nitrogenNum?) => {
       nitrogenUndergroundObj.addCssText();
       const oldCameraPosition = { x: -1000, y: 100, z: 500 };
       const oldControlsPosition = { x: -10, y: 10, z: 10 };
-      const newCameraPosition = { x: 21.7662617168586, y: 65.76772405405443, z: 106.19314654690366 },
-        newControlsPosition = { x: -13.223797639205223, y: 13.119121404116392, z: 0.7650646324543227 };
+      const fitted = nitrogenUndergroundObj.getCameraConfig?.();
+      const newCameraPosition = fitted?.camera || { x: 21.7662617168586, y: 65.76772405405443, z: 106.19314654690366 },
+        newControlsPosition = fitted?.target || { x: -13.223797639205223, y: 13.119121404116392, z: 0.7650646324543227 };
 
       setTimeout(async () => {
         model?.scene?.add(group);
@@ -157,7 +162,10 @@ export const setModelType = (type, nitrogenNum?) => {
   });
 };
 
-export const mountedThree = () => {
+/**
+ * @param undergroundModelName 地下三维模型名称,默认 nitrogenUnderground
+ */
+export const mountedThree = (undergroundModelName = 'nitrogenUnderground') => {
   return new Promise(async (resolve) => {
     model = new UseThree('#nitrogen3D', '#nitrogenCss3D');
     model.setEnvMap('test1.hdr');
@@ -167,7 +175,7 @@ export const mountedThree = () => {
     nitrogenObj = new NitrogenOverground(model);
     await nitrogenObj.mountedThree(4);
 
-    nitrogenUndergroundObj = new NitrogenUnderground(model);
+    nitrogenUndergroundObj = new NitrogenUnderground(model, undergroundModelName);
     await nitrogenUndergroundObj.mountedThree();
 
     // 地下管路模型(基底 + 注氮机),作为新增视图与上面两个共存

+ 7 - 1
src/views/vent/monitorManager/nitrogen/nitrogen.threejs.ts

@@ -119,7 +119,11 @@ export const setModelType = (modalTypeName) => {
   });
 };
 
-export const mountedThree = (monitorDataGroupArr) => {
+/**
+ * @param monitorDataGroupArr 监测数据分组
+ * @param gltfModelName 三维模型名称(对应 /model/glft/ 下的 glb),默认 compressor
+ */
+export const mountedThree = (monitorDataGroupArr, gltfModelName = 'compressor') => {
   // debugger;
   return new Promise(async (resolve) => {
     model = new UseThree('#compressor3D');
@@ -131,6 +135,8 @@ export const mountedThree = (monitorDataGroupArr) => {
     for (let i = 0; i < monitorDataGroupArr.length; i++) {
       const monitorDataGroup = monitorDataGroupArr[i];
       const compressorObj = new Nitrogen(model);
+      // 先设置 gltf 模型名再加载,加载完成后再改回分组标识名供切换使用
+      compressorObj.modelName = gltfModelName;
       await compressorObj.mountedThree(monitorDataGroup, '#compressorCss3D' + (i + 1));
       compressorObj.modelName = 'compressor' + (i + 1);
       compressorObjArr.push(compressorObj);

Некоторые файлы не были показаны из-за большого количества измененных файлов