소스 검색

[Feat 0000] 新增局部风机模拟瓦斯排放、主风机一键倒机模拟

hongrunxia 8 시간 전
부모
커밋
7c563535fb

+ 2 - 0
public/js/config.js

@@ -22,4 +22,6 @@ const VENT_PARAM = {
   isShowQy: true, // 是否显示气压
   is2DModel: false, // 是否尽最大可能使用2D模型
   isLoginByCode: false, // 登录页面是否有验证码
+  loadText: '智能通风管控系统',
+  fanSwitchoverSimulation: true, // 不停风倒机模拟功能: true=启用模拟演示, false=使用实际控制
 }

+ 251 - 208
src/views/vent/monitorManager/fanLocalMonitor/components/deviceFaultDetailModal.vue

@@ -1,16 +1,15 @@
 <template>
-  <a-modal v-model:visible="visible" width="1200px" :title="title" centered destroyOnClose :footer="null"
-    @cancel="handleCancel">
+  <a-modal v-model:visible="visible" width="1200px" :title="title" centered destroyOnClose :footer="null" @cancel="handleCancel">
     <div class="basic-top">
       <div class="top-l">
-        <faultEchartPie :normalNum="normalNum" :abnormalNum="abnormalNum"></faultEchartPie>
+        <faultEchartPie :normalNum="normalNum" :abnormalNum="abnormalNum" />
       </div>
       <div class="top-r">
-        <div class="basic-title">异常测点信息</div>
+        <div class="basic-title">异常测点信息</div>
         <div class="list-r">
           <div class="list-item" v-for="(item, index) in faultList" :key="index">
-            <span style="margin-right: 5px;">{{ `${index + 1}.` }}</span>
-            <span>{{ item.label }}</span>
+            <span style="margin-right: 5px; color: #f50707">{{ `${index + 1}.` }}</span>
+            <span style="color: #f50707">{{ item.label }}</span>
           </div>
         </div>
       </div>
@@ -19,53 +18,91 @@
       <div class="basic-content">
         <div class="basic-title">风速监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'windSpeed'" :addData="windsValue" :legendName="'风速'"
-            :echartColor="'#ff0000'"></faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'windSpeed'"
+            :addData="windsValue"
+            :legendName="'风速'"
+            :echartColor="'#ff0000'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">风量监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'wind'" :addData="windValue" :legendName="'风量'"
-            :echartColor="'#2ec1dd'"></faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'wind'"
+            :addData="windValue"
+            :legendName="'风量'"
+            :echartColor="'#2ec1dd'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">电流监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'Dl'" :addData="dlValue" :legendName="'电流'"
-            :echartColor="'#fbc21c'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'Dl'"
+            :addData="dlValue"
+            :legendName="'电流'"
+            :echartColor="'#fbc21c'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">电压监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'Dy'" :addData="dyValue" :legendName="'电压'"
-            :echartColor="'#259745'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'Dy'"
+            :addData="dyValue"
+            :legendName="'电压'"
+            :echartColor="'#259745'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">瓦斯浓度监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'wsnd'" :addData="wsndValue" :legendName="'瓦斯浓度'"
-            :echartColor="'#0fcb74'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'wsnd'"
+            :addData="wsndValue"
+            :legendName="'瓦斯浓度'"
+            :echartColor="'#0fcb74'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">振动监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'Zd'" :addData="zdValue" :legendName="'振动'"
-            :echartColor="'#00a9ff'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'Zd'"
+            :addData="zdValue"
+            :legendName="'振动'"
+            :echartColor="'#00a9ff'"
+          />
         </div>
       </div>
     </div>
@@ -73,205 +110,211 @@
 </template>
 
 <script setup lang="ts">
-import { computed, reactive, ref, watch } from 'vue'
-import faultEchartPie from './faultEchartPie.vue'
-import faultEchartLine from './faultEchartLine.vue'
+  import { computed, reactive, ref, watch } from 'vue';
+  import faultEchartPie from './faultEchartPie.vue';
+  import faultEchartLine from './faultEchartLine.vue';
 
-let props = defineProps({
-  //是否显示弹窗
-  isShowModal: {
-    type: Boolean
-  },
-  //当前激活风机索引
-  warningMonitorRowIndex: {
-    type: Number
-  },
-  rightColumns: {
-    type: Array as any
-  },
-  selectData: {
-    type: Object as any
-  },
-  deviceID: {
-    type: String
-  },
-  deviceType: {
-    type: String
-  }
-})
-
-let visible = ref(false)
-let title = ref('风机故障诊断分析')
-let faultList = ref<any[]>([])//异常信息列表
-let normalNum = ref(0)//正常测点数量
-let abnormalNum = ref(0)//
-
-let windsValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1WindSpeed'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2WindSpeed'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
-}) //实时风速
-let windValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Wind'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Wind'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let props = defineProps({
+    //是否显示弹窗
+    isShowModal: {
+      type: Boolean,
+    },
+    //当前激活风机索引
+    warningMonitorRowIndex: {
+      type: Number,
+    },
+    rightColumns: {
+      type: Array as any,
+    },
+    selectData: {
+      type: Object as any,
+    },
+    deviceID: {
+      type: String,
+    },
+    deviceType: {
+      type: String,
+    },
+  });
 
-}) //实时风量
-let dlValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dl'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dl'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let visible = ref(false);
+  let title = ref('风机故障诊断分析');
+  let faultList = ref<any[]>([]); //异常信息列表
+  let normalNum = ref(0); //正常测点数量
+  let abnormalNum = ref(0); //
 
-}) //实时电流
-let dyValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dy'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dy'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let windsValue = computed(() => {
+    return {
+      y:
+        props.warningMonitorRowIndex == 0
+          ? props.selectData['Fan1WindSpeed']
+          : props.warningMonitorRowIndex == 1
+            ? props.selectData['Fan2WindSpeed']
+            : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时风速
+  let windValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Wind'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Wind'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时风量
+  let dlValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dl'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dl'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时电流
+  let dyValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dy'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dy'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时电压
+  let wsndValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Wsnd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Wsnd'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时瓦斯浓度
+  let zdValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Zd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Zd'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时振动
 
-}) //实时电压
-let wsndValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Wsnd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Wsnd'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let $emit = defineEmits(['handlerClose']);
 
-}) //实时瓦斯浓度
-let zdValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Zd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Zd'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
+  //弹窗关闭
+  function handleCancel() {
+    $emit('handlerClose', false);
   }
 
-}) //实时振动
-
-let $emit = defineEmits(['handlerClose'])
-
+  watch(
+    () => props.isShowModal,
+    (newV, oldV) => {
+      visible.value = newV;
+    }
+  );
 
-//弹窗关闭
-function handleCancel() {
-  $emit('handlerClose', false)
-}
-
-watch(() => props.isShowModal, (newV, oldV) => {
-  visible.value = newV
-})
-
-watch(() => props.warningMonitorRowIndex, (newV, oldV) => {
-  // if (props.rightColumns.length) {
-  //   let dataList: any[] = []
-  //   props.rightColumns.forEach((el) => {
-  //     if (el.dataIndex.startsWith('Fan')) {
-  //       if (newV == 0 && props.selectData[el.dataIndex.replace('Fan', 'Fan1')] != undefined) {
-  //         dataList.push({ label: el.title })
-  //       } else if (newV == 1 && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != '0') {
-  //         dataList.push({ label: el.title })
-  //       } else if (newV == 2 && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != '0') {
-  //         dataList.push({ label: el.title })
-  //       }
-  //     } else {
-  //       if (props.selectData[el.dataIndex] != undefined && props.selectData[el.dataIndex] != '0') {
-  //         dataList.push({ label: el.title })
-  //       }
-  //     }
-  //   })
-  //   faultList.value = dataList
-  //   abnormalNum.value = dataList.length
-  //   normalNum.value = props.rightColumns.length - dataList.length
-  //   console.log(dataList, 'dataList===')
-  // }
-  if (newV == 0) {
-    faultList.value = [
-      { label: '2025年7月12日,风速超限' },
-      { label: '2025年7月21日,风量波动' },
-      { label: '2025年8月4日,轴温偏高' },
-      { label: '2025年8月17日,径向振动过大' },
-      { label: '2025年8月23日,油温高' },
-      { label: '2025年8月26日,电机故障' },
-    ]
-  } else if (newV == 1) {
-    faultList.value = [
-      { label: '2025年8月2日,风速超限' },
-      { label: '2025年8月11日,振动过高' },
-      { label: '2025年8月24日,轴温偏高' },
-      { label: '2025年9月17日,径向振动过大' },
-      { label: '2025年9月23日,油温高' },
-      { label: '2025年9月26日,蝶阀故障' },
-    ]
-  } else {
-    faultList.value = [
-      { label: '2025年8月2日,风门故障' },
-      { label: '2025年8月11日,风量波动' },
-      { label: '2025年8月24日,轴温偏高' },
-      { label: '2025年9月17日,径向振动过大' },
-      { label: '2025年9月23日,变频故障反馈' },
-      { label: '2025年9月26日,电机故障' },
-    ]
-  }
-  abnormalNum.value = faultList.value.length
-  normalNum.value = Math.ceil(Math.random() * 10)
-}, { immediate: true })
+  watch(
+    () => props.warningMonitorRowIndex,
+    (newV, oldV) => {
+      // if (props.rightColumns.length) {
+      //   let dataList: any[] = []
+      //   props.rightColumns.forEach((el) => {
+      //     if (el.dataIndex.startsWith('Fan')) {
+      //       if (newV == 0 && props.selectData[el.dataIndex.replace('Fan', 'Fan1')] != undefined) {
+      //         dataList.push({ label: el.title })
+      //       } else if (newV == 1 && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != '0') {
+      //         dataList.push({ label: el.title })
+      //       } else if (newV == 2 && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != '0') {
+      //         dataList.push({ label: el.title })
+      //       }
+      //     } else {
+      //       if (props.selectData[el.dataIndex] != undefined && props.selectData[el.dataIndex] != '0') {
+      //         dataList.push({ label: el.title })
+      //       }
+      //     }
+      //   })
+      //   faultList.value = dataList
+      //   abnormalNum.value = dataList.length
+      //   normalNum.value = props.rightColumns.length - dataList.length
+      //   console.log(dataList, 'dataList===')
+      // }
+      if (newV == 0) {
+        faultList.value = [
+          { label: '2025年7月12日,风速超限' },
+          { label: '2025年7月21日,风量波动' },
+          { label: '2025年8月4日,轴温偏高' },
+          { label: '2025年8月17日,径向振动过大' },
+          { label: '2025年8月23日,油温高' },
+          { label: '2025年8月26日,电机故障' },
+        ];
+      } else if (newV == 1) {
+        faultList.value = [
+          { label: '2025年8月2日,风速超限' },
+          { label: '2025年8月11日,振动过高' },
+          { label: '2025年8月24日,轴温偏高' },
+          { label: '2025年9月17日,径向振动过大' },
+          { label: '2025年9月23日,油温高' },
+          { label: '2025年9月26日,蝶阀故障' },
+        ];
+      } else {
+        faultList.value = [
+          { label: '2025年8月2日,风门故障' },
+          { label: '2025年8月11日,风量波动' },
+          { label: '2025年8月24日,轴温偏高' },
+          { label: '2025年9月17日,径向振动过大' },
+          { label: '2025年9月23日,变频故障反馈' },
+          { label: '2025年9月26日,电机故障' },
+        ];
+      }
+      abnormalNum.value = faultList.value.length;
+      normalNum.value = 36;
+    },
+    { immediate: true }
+  );
 </script>
 
 <style lang="less" scoped>
-.basic-top {
-  width: calc(100% - 30px);
-  height: 220px;
-  margin: 10px;
-  display: flex;
+  .basic-top {
+    width: calc(100% - 30px);
+    height: 200px;
+    margin: 10px;
+    display: flex;
 
-  .top-l {
-    width: 320px;
-    margin-right: 10px;
-  }
+    .top-l {
+      width: 320px;
+      margin-right: 10px;
+    }
 
-  .top-r {
-    width: calc(100% - 330px);
-  }
+    .top-r {
+      width: calc(100% - 330px);
+    }
 
-  .list-r {
-    height: calc(100% - 25px);
-    padding: 0px 10px;
-    box-sizing: border-box;
-    overflow-y: auto;
-    border: 1px solid #3062a2;
-  }
+    .list-r {
+      height: calc(100% - 25px);
+      padding: 0px 10px;
+      box-sizing: border-box;
+      overflow-y: auto;
+      border: 1px solid #3062a2;
+    }
 
-  .list-item {
-    margin: 5px 0px;
+    .list-item {
+      margin: 5px 0px;
+    }
   }
-}
 
-.basic-title {
-  height: 20px;
-  line-height: 20px;
-  font-weight: bold;
-  margin-bottom: 5px;
-}
+  .basic-title {
+    height: 20px;
+    line-height: 20px;
+    font-weight: bold;
+    margin-bottom: 5px;
+  }
 
-.basic-bot {
-  width: calc(100% - 30px);
-  height: 600px;
-  margin: 10px;
-  display: flex;
-  justify-content: space-between;
-  flex-wrap: wrap;
-  overflow-y: auto;
+  .basic-bot {
+    width: calc(100% - 30px);
+    height: 400px;
+    margin: 10px;
+    display: flex;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    overflow-y: auto;
 
-  .basic-content {
-    width: calc(50% - 5px);
-    height: 240px;
-    margin-bottom: 10px;
-    padding: 5px 10px;
-    box-sizing: border-box;
-    border: 1px solid #3062a2;
-  }
+    .basic-content {
+      width: calc(50% - 5px);
+      height: 240px;
+      margin-bottom: 10px;
+      padding: 5px 10px;
+      box-sizing: border-box;
+      border: 1px solid #3062a2;
+    }
 
-  .echart-box {
-    height: calc(100% - 25px);
+    .echart-box {
+      height: calc(100% - 25px);
+    }
   }
-}
 </style>

+ 202 - 202
src/views/vent/monitorManager/fanLocalMonitor/components/dischargeGas.vue

@@ -62,237 +62,237 @@
 </template>
 
 <script lang="ts" setup>
-import { BasicModal, useModalInner } from '/@/components/Modal';
-import { ref, nextTick, onMounted, watch } from 'vue';
-import { option, chartsColumnListGas, echatsOption1 } from '../fanLocal.data';
-import BarAndLine from '/@/components/chart/BarAndLine.vue';
-import { autoAdjust } from '../fanLocal.api';
-import { message } from 'ant-design-vue';
-const props = defineProps({
-  data: {
-    type: Object,
-    default: () => {},
-  },
-  gasMax: {
-    type: Number,
-  },
-  fanlocalId: {
-    type: Number,
-  },
-});
-const emit = defineEmits(['close', 'register', 'openModal']);
-// 注册 modal
-const [register, { closeModal }] = useModalInner((data) => {
-  nextTick(() => {
-    if (option['xAxis']) option['xAxis']['data'] = xData;
-    option['series'] = yDataList;
-    initEcharts();
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { ref, nextTick, onMounted, watch } from 'vue';
+  import { option, chartsColumnListGas, echatsOption1 } from '../fanLocal.data';
+  import BarAndLine from '/@/components/chart/BarAndLine.vue';
+  import { autoAdjust } from '../fanLocal.api';
+  import { message } from 'ant-design-vue';
+  const props = defineProps({
+    data: {
+      type: Object,
+      default: () => {},
+    },
+    gasMax: {
+      type: Number,
+    },
+    fanlocalId: {
+      type: Number,
+    },
   });
-});
-
-const ChartRef = ref();
-const myChart = ref();
-const refresh = ref(true);
-const xData: any[] = [];
-const yDataList: [] = [];
-const echartsData = ref<Record<string, any>[]>([]);
-const monitorData = ref<Record<string, any>>({});
-const modalIsShow = ref<boolean>(false); // 是否显示模态框
-const modalTitle = ref('');
-const passWord = ref('');
-watch(
-  () => props.data,
-  (newVal) => {
-    // 创建新对象,合并 newVal 和 targetVolume
-    const combinedData = {
-      gasT1: newVal.gasT1,
-      gasT2: newVal.gasT2,
-      gasT3: newVal.gasT3,
-      gasT4: newVal.gasT4,
-      readTime: newVal.readTime || new Date().toISOString(), // 确保有时间字段
-    };
-    monitorData.value = combinedData;
-    if (echartsData.value.length > 20) {
-      echartsData.value.shift();
-    }
-    echartsData.value = [...echartsData.value, combinedData];
-  }
-);
-function initEcharts() {
-  if (ChartRef.value) {
-    myChart.value = echarts.init(ChartRef.value);
-    option && myChart.value.setOption(option);
-    refresh.value = false;
+  const emit = defineEmits(['close', 'register', 'openModal']);
+  // 注册 modal
+  const [register, { closeModal }] = useModalInner((data) => {
     nextTick(() => {
-      setTimeout(() => {
-        refresh.value = true;
-      }, 0);
+      if (option['xAxis']) option['xAxis']['data'] = xData;
+      option['series'] = yDataList;
+      initEcharts();
     });
+  });
+
+  const ChartRef = ref();
+  const myChart = ref();
+  const refresh = ref(true);
+  const xData: any[] = [];
+  const yDataList: [] = [];
+  const echartsData = ref<Record<string, any>[]>([]);
+  const monitorData = ref<Record<string, any>>({});
+  const modalIsShow = ref<boolean>(false); // 是否显示模态框
+  const modalTitle = ref('');
+  const passWord = ref('');
+  watch(
+    () => props.data,
+    (newVal) => {
+      // 创建新对象,合并 newVal 和 targetVolume
+      const combinedData = {
+        gasT1: newVal.gasT1,
+        gasT2: newVal.gasT2,
+        gasT3: newVal.gasT3,
+        gasT4: newVal.gasT4,
+        readTime: newVal.readTime || new Date().toISOString(), // 确保有时间字段
+      };
+      monitorData.value = combinedData;
+      if (echartsData.value.length > 20) {
+        echartsData.value.shift();
+      }
+      echartsData.value = [...echartsData.value, combinedData];
+    }
+  );
+  function initEcharts() {
+    if (ChartRef.value) {
+      myChart.value = echarts.init(ChartRef.value);
+      option && myChart.value.setOption(option);
+      refresh.value = false;
+      nextTick(() => {
+        setTimeout(() => {
+          refresh.value = true;
+        }, 0);
+      });
+    }
   }
-}
 
-function onHide() {
-  closeModal();
-}
-function stop() {
-  modalIsShow.value = true;
-  passWord.value == '';
-}
-function cancel() {
-  modalIsShow.value = false;
-}
-function handleOk() {
-  if (passWord.value == '') {
-    message.warning('请输入密码!');
-    return;
+  function onHide() {
+    closeModal();
   }
-  handerFn();
-}
-function handerFn() {
-  if (passWord.value != '123456') {
-    message.error('密码错误,请重新输入!');
-    return;
+  function stop() {
+    modalIsShow.value = true;
+    passWord.value == '';
+  }
+  function cancel() {
+    modalIsShow.value = false;
+  }
+  function handleOk() {
+    if (passWord.value == '') {
+      message.warning('请输入密码!');
+      return;
+    }
+    handerFn();
+  }
+  function handerFn() {
+    if (passWord.value != '123456') {
+      message.error('密码错误,请重新输入!');
+      return;
+    }
+    const params = { auto: 0, fanlocalId: props.fanlocalId };
+    autoAdjust(params)
+      .then(() => {
+        message.success('指令已下发成功!');
+        modalIsShow.value = false;
+        passWord.value == '';
+      })
+      .catch(() => {
+        message.error('指令下发失败');
+      });
+  }
+  onMounted(() => {});
+  function onSubmit() {
+    emit('close');
+    closeModal();
   }
-  const params = { auto: 0, fanlocalId: props.fanlocalId };
-  autoAdjust(params)
-    .then(() => {
-      message.success('指令已下发成功!');
-      modalIsShow.value = false;
-      passWord.value == '';
-    })
-    .catch(() => {
-      message.error('指令下发失败');
-    });
-}
-onMounted(() => {});
-function onSubmit() {
-  emit('close');
-  closeModal();
-}
 </script>
 
 <style scoped lang="less">
-.modal-box {
-  display: flex;
-  flex-direction: row;
-  background-color: #ffffff05;
-  padding: 10px 8px 0 8px;
-  border: 1px solid #00d8ff22;
-  position: relative;
-  // min-height: 600px;
-  .left-box {
-    flex: 1; /* 占据 3/4 的空间 */
-    background-image: url(../../../../../assets/images/dischargeGas.svg);
-    background-repeat: no-repeat;
-    background-size: contain; /* 确保背景图片完整显示 */
-    background-position: center; /* 确保背景图片居中 */
-  }
-  .right-box {
-    flex: 1; /* 占据 3/4 的空间 */
-    height: 400px;
-    width: 100%;
+  .modal-box {
+    display: flex;
+    flex-direction: row;
+    background-color: #ffffff05;
+    padding: 10px 8px 0 8px;
+    border: 1px solid #00d8ff22;
+    position: relative;
+    // min-height: 600px;
+    .left-box {
+      flex: 1; /* 占据 3/4 的空间 */
+      background-image: url(../../../../../assets/images/dischargeGas.svg);
+      background-repeat: no-repeat;
+      background-size: contain; /* 确保背景图片完整显示 */
+      background-position: center; /* 确保背景图片居中 */
+    }
+    .right-box {
+      flex: 1; /* 占据 3/4 的空间 */
+      height: 400px;
+      width: 100%;
+    }
   }
-}
-.setting-box {
-  width: 100%;
-  height: 70px;
-  margin: 10px 0;
-  background-color: #ffffff05;
-  border: 1px solid #00d8ff22;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-
-  .right-inputs {
+  .setting-box {
     width: 100%;
+    height: 70px;
+    margin: 10px 0;
+    background-color: #ffffff05;
+    border: 1px solid #00d8ff22;
     display: flex;
-    height: 40px;
-    margin: 0 10px;
+    align-items: center;
     justify-content: space-between;
-  }
-  .left-buttons {
-    display: flex;
-    height: 40px;
 
-    .btn {
+    .right-inputs {
+      width: 100%;
+      display: flex;
+      height: 40px;
       margin: 0 10px;
+      justify-content: space-between;
     }
-  }
-  .border-clip {
-    width: 1px;
-    height: 25px;
-    border-right: 1px solid #8b8b8b77;
-  }
-  .input-title {
-    max-width: 150px;
-  }
-  .input-box {
-    width: 120px !important;
-    background: transparent !important;
-    border-color: #00d8ff44 !important;
-    margin-right: 20px;
-    color: #fff !important;
-  }
-  .btn {
-    padding: 8px 20px;
-    position: relative;
-    margin: 10px;
-    border-radius: 2px;
-    color: #fff;
-    width: fit-content;
-    cursor: pointer;
-    float: right;
-    &::before {
-      position: absolute;
-      display: block;
-      content: '';
-      width: calc(100% - 4px);
-      height: calc(100% - 4px);
-      top: 2px;
-      left: 2px;
+    .left-buttons {
+      display: flex;
+      height: 40px;
+
+      .btn {
+        margin: 0 10px;
+      }
+    }
+    .border-clip {
+      width: 1px;
+      height: 25px;
+      border-right: 1px solid #8b8b8b77;
+    }
+    .input-title {
+      max-width: 150px;
+    }
+    .input-box {
+      width: 120px !important;
+      background: transparent !important;
+      border-color: #00d8ff44 !important;
+      margin-right: 20px;
+      color: #fff !important;
+    }
+    .btn {
+      padding: 8px 20px;
+      position: relative;
+      margin: 10px;
       border-radius: 2px;
-      z-index: -1;
+      color: #fff;
+      width: fit-content;
+      cursor: pointer;
+      float: right;
+      &::before {
+        position: absolute;
+        display: block;
+        content: '';
+        width: calc(100% - 4px);
+        height: calc(100% - 4px);
+        top: 2px;
+        left: 2px;
+        border-radius: 2px;
+        z-index: -1;
+      }
     }
-  }
 
-  .btn1 {
-    border: 1px solid #5cfaff;
+    .btn1 {
+      border: 1px solid #5cfaff;
 
-    &::before {
-      background-image: linear-gradient(#2effee92, #0cb1d592);
-    }
+      &::before {
+        background-image: linear-gradient(#2effee92, #0cb1d592);
+      }
 
-    &:hover {
-      border: 1px solid #5cfaffaa;
+      &:hover {
+        border: 1px solid #5cfaffaa;
 
-      &::before {
-        background-image: linear-gradient(#2effee72, #0cb1d572);
+        &::before {
+          background-image: linear-gradient(#2effee72, #0cb1d572);
+        }
       }
     }
   }
-}
 
-@keyframes open {
-  0% {
-    height: 0px;
-  }
-  100% {
-    height: fit-content;
+  @keyframes open {
+    0% {
+      height: 0px;
+    }
+    100% {
+      height: fit-content;
+    }
   }
-}
 
-@keyframes close {
-  0% {
-    height: fit-content;
+  @keyframes close {
+    0% {
+      height: fit-content;
+    }
+    100% {
+      height: 0px;
+    }
+  }
+  :deep(.zxm-divider-inner-text) {
+    color: #cacaca88 !important;
   }
-  100% {
-    height: 0px;
+  :deep(.zxm-form-item) {
+    margin-bottom: 10px;
   }
-}
-:deep(.zxm-divider-inner-text) {
-  color: #cacaca88 !important;
-}
-:deep(.zxm-form-item) {
-  margin-bottom: 10px;
-}
 </style>

+ 91 - 91
src/views/vent/monitorManager/fanLocalMonitor/components/faultEchartPie.vue

@@ -3,108 +3,108 @@
 </template>
 
 <script setup lang="ts">
-import { nextTick, onMounted, ref } from 'vue'
-import * as echarts from 'echarts';
+  import { nextTick, onMounted, ref } from 'vue';
+  import * as echarts from 'echarts';
 
-let props = defineProps({
-  //正常测点
-  normalNum: {
-    type: Number
-  },
-  //异常测点
-  abnormalNum: {
-    type: Number
-  }
-})
-let faultPie = ref(null)
-
-function getOption() {
-  nextTick(() => {
-    let myChart = echarts.init(faultPie.value);
-    let option = {
-      tooltip: {
-        trigger: "item",
-        formatter: "{b} : {c}",
-        backgroundColor: 'rgba(0, 0, 0, .6)',
-        textStyle: {
-          color: '#fff',
-          fontSize: 12,
-        },
-      },
+  let props = defineProps({
+    //正常测点
+    normalNum: {
+      type: Number,
+    },
+    //异常测点
+    abnormalNum: {
+      type: Number,
+    },
+  });
+  let faultPie = ref(null);
 
-      // 图例移到右边(竖排)
-      legend: {
-        right: 'center',
-        top: "bottom",
-        type: 'plain',
-        textStyle: {
-          color: '#fff',
-          fontSize: 12,
+  function getOption() {
+    nextTick(() => {
+      let myChart = echarts.init(faultPie.value);
+      let option = {
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b} : {c}',
+          backgroundColor: 'rgba(0, 0, 0, .6)',
+          textStyle: {
+            color: '#fff',
+            fontSize: 12,
+          },
         },
-        // icon:'rect',
-        itemGap: 10,
-        itemWidth: 10,
-        // icon: 'path://M0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z',
-      },
 
-      series: [
-        {
-          name: "",
-          type: "pie",
-          radius: ["50%", "75%"],
-          center: ["50%", "47%"],  // ⭐ 为让位图例,整体饼图左移
-          label: {
-            color: "#fff",
+        // 图例移到右边(竖排)
+        legend: {
+          right: 'center',
+          top: 'bottom',
+          type: 'plain',
+          textStyle: {
+            color: '#fff',
             fontSize: 12,
-            show: false
           },
+          // icon:'rect',
+          itemGap: 10,
+          itemWidth: 10,
+          // icon: 'path://M0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z',
+        },
 
-          data: [
-            {
-              name: '正常测点',
-              value: props.normalNum || 0,
-              itemStyle: {
-                borderRadius: 10, // ⭐ 扇形圆角
-                color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
-                  { offset: 0, color: '#14ee59' },
-                  { offset: 1, color: '#14ee59' },
-                ]),
-                shadowBlur: 18,
-                shadowColor: "rgba(0,0,0,0.25)",
-              },
+        series: [
+          {
+            name: '',
+            type: 'pie',
+            radius: ['50%', '75%'],
+            center: ['50%', '47%'], // ⭐ 为让位图例,整体饼图左移
+            label: {
+              color: '#fff',
+              fontSize: 12,
+              show: false,
             },
-            {
-              name: '异常测点',
-              value: props.abnormalNum || 0,
-              itemStyle: {
-                borderRadius: 10, // ⭐ 扇形圆角
-                color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
-                  { offset: 0, color: '#f20707' },
-                  { offset: 1, color: '#f20707' },
-                ]),
-                shadowBlur: 18,
-                shadowColor: "rgba(0,0,0,0.25)",
+
+            data: [
+              {
+                name: '正常测点',
+                value: props.normalNum || 0,
+                itemStyle: {
+                  borderRadius: 10, // ⭐ 扇形圆角
+                  color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
+                    { offset: 0, color: '#14ee59' },
+                    { offset: 1, color: '#14ee59' },
+                  ]),
+                  shadowBlur: 18,
+                  shadowColor: 'rgba(0,0,0,0.25)',
+                },
               },
-            }
-          ],
-        },
-      ],
-    };
-    myChart.setOption(option);
-    window.onresize = function () {
-      myChart.resize();
-    };
-  });
-}
+              {
+                name: '异常测点',
+                value: props.abnormalNum || 0,
+                itemStyle: {
+                  borderRadius: 10, // ⭐ 扇形圆角
+                  color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
+                    { offset: 0, color: '#f20707' },
+                    { offset: 1, color: '#f20707' },
+                  ]),
+                  shadowBlur: 18,
+                  shadowColor: 'rgba(0,0,0,0.25)',
+                },
+              },
+            ],
+          },
+        ],
+      };
+      myChart.setOption(option);
+      window.onresize = function () {
+        myChart.resize();
+      };
+    });
+  }
 
-onMounted(() => {
-  getOption()
-})
+  onMounted(() => {
+    getOption();
+  });
 </script>
 
 <style lang="less" scoped>
-.faultPie {
-  width: 100%;
-  height: 100%;
-}
+  .faultPie {
+    width: 100%;
+    height: 100%;
+  }
 </style>

+ 203 - 0
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.data.ts

@@ -4095,6 +4095,209 @@ export const lineFormData = reactive({
   max: null,
 });
 
+// ==================== 瓦斯排放仿真数据定义 ====================
+
+/** 仿真状态枚举 */
+export enum GasDischargeSimState {
+  IDLE = 'IDLE',
+  RUNNING = 'RUNNING',
+  PAUSED = 'PAUSED',
+  WINDING_DOWN = 'WINDING_DOWN',
+  COMPLETED = 'COMPLETED',
+  ABORTED = 'ABORTED',
+}
+
+/** 仿真状态中文映射 */
+export const GAS_DISCHARGE_STEP_NAMES: Record<GasDischargeSimState, string> = {
+  [GasDischargeSimState.IDLE]: '待机',
+  [GasDischargeSimState.RUNNING]: '排放中',
+  [GasDischargeSimState.PAUSED]: '已暂停',
+  [GasDischargeSimState.WINDING_DOWN]: '降频收尾',
+  [GasDischargeSimState.COMPLETED]: '排放完成',
+  [GasDischargeSimState.ABORTED]: '已终止',
+};
+
+/** 仿真状态对应颜色 */
+export const GAS_DISCHARGE_STATE_COLOR: Record<GasDischargeSimState, string> = {
+  [GasDischargeSimState.IDLE]: '#8c8c8c',
+  [GasDischargeSimState.RUNNING]: '#1890ff',
+  [GasDischargeSimState.PAUSED]: '#faad14',
+  [GasDischargeSimState.WINDING_DOWN]: '#faad14',
+  [GasDischargeSimState.COMPLETED]: '#52c41a',
+  [GasDischargeSimState.ABORTED]: '#8c8c8c',
+};
+
+/** 分段逻辑表行 */
+export interface SegmentTableRow {
+  gas1Min: number;
+  gas1Max: number;
+  targetHz: number;
+  stepHz: number; // 每步渐进增量
+  label: string;
+}
+
+/**
+ * 5档渐进排放表 — 按 T1 浓度区间决定目标频率和每步渐进量。
+ * 步进 0.3-0.6Hz/周期 × 10min/周期 ≈ 48步精细扫描全频段
+ */
+export const GAS_DISCHARGE_SEGMENT_TABLE: SegmentTableRow[] = [
+  { gas1Min: 0, gas1Max: 0.5, targetHz: 35, stepHz: 0, label: '维持当前' },
+  { gas1Min: 0.5, gas1Max: 0.8, targetHz: 40, stepHz: 2, label: '轻微排放' },
+  { gas1Min: 0.8, gas1Max: 1.0, targetHz: 45, stepHz: 3, label: '加强排放' },
+  { gas1Min: 1.0, gas1Max: 1.5, targetHz: 48, stepHz: 5, label: '高浓度排放' },
+  { gas1Min: 1.5, gas1Max: Infinity, targetHz: 50, stepHz: 5, label: '满频排放' },
+];
+
+/** 根据gas1浓度查找目标频率和步进 */
+export function lookupFrequencyByGas1(gas1: number): { frequency: number; stepHz: number; label: string } {
+  for (const row of GAS_DISCHARGE_SEGMENT_TABLE) {
+    if (gas1 >= row.gas1Min && gas1 < row.gas1Max) {
+      return { frequency: row.targetHz, stepHz: row.stepHz, label: row.label };
+    }
+  }
+  return { frequency: 50, stepHz: 2, label: '满频排放' };
+}
+
+/**
+ * 仿真可配置参数 — 用户可在弹窗参数配置中修改,启动时冻结到 config 中。
+ *
+ * 浓度相关:initialGas1/2/3, emissionRate, co2EmissionRatio
+ * 空间参数:workfaceVolume(越大浓度变化越慢), tunnelLength(越长风越大), crossSection(风速=风量/60/A)
+ * 风机参数:ratedAirVolume(@50Hz), frequencyK(线性系数), normalFrequencyHz(从真实数据读)
+ * 物理约束:maxWindSpeed(4m/s上限,防煤尘爆炸), minWindSpeed(0.25m/s下限,最低排尘风速)
+ * 时间控制:stableDurationSec(达标稳定需30min), cycleDurationMin(两次调频间隔10min)
+ * 回风模型:decayFactor(gas2 = delayedGas1 × factor,默认0.82即18%衰减)
+ * 时间基准:baseTime(日志/曲线的时间起点,如 2025-05-02 13:02:01)
+ */
+export interface GasDischargeConfig {
+  initialGas1: number; // 工作面初始瓦斯 % (1.5)
+  initialGas2: number; // 回风初始瓦斯 % (0.2)
+  initialGas3: number; // 入口瓦斯 % (0)
+  emissionRate: number; // 瓦斯涌出量 m³/s (0.015)
+  co2EmissionRatio: number; // CO₂/瓦斯涌出比率 (0.5)
+  workfaceVolume: number; // 工作面体积 m³ (600)
+  tunnelLength: number; // 回风巷长度 m (150)
+  ratedAirVolume: number; // 额定风量@50Hz m³/min (400)
+  frequencyK: number; // 风量线性系数 k (1.0)
+  normalFrequencyHz: number; // 正常频率取真实数据 (35)
+  decayFactor: number; // 回风衰减系 (0.82 = 18%衰减)
+  crossSection: number; // 巷道断面积 m² (6)
+  maxWindSpeed: number; // 风速上限 m/s (4.0)
+  minWindSpeed: number; // 风速下限 m/s (0.25)
+  warningThreshold: number; // 预警提示阈值 % (0.8)
+  dangerThreshold: number; // 超限危险阈值 % (1.0)
+  stopGasThreshold: number; // 达标阈值 % (1.0)
+  stableDurationSec: number; // 达标持续 秒 (1800 = 30分)
+  baseTime: string; // 时间基准 'YYYY-MM-DDTHH:MM:SS'
+  cycleDurationMin: number; // 调频周期 分 (10)
+}
+
+/** 仿真默认参数 */
+export const GAS_DISCHARGE_DEFAULT_CONFIG: GasDischargeConfig = {
+  initialGas1: 1.48,
+  initialGas2: 0.2,
+  initialGas3: 0.0,
+  emissionRate: 0.025,
+  co2EmissionRatio: 0.8,
+  workfaceVolume: 600,
+  tunnelLength: 800,
+  ratedAirVolume: 400,
+  frequencyK: 1.0,
+  normalFrequencyHz: 29,
+  decayFactor: 0.32,
+  crossSection: 8,
+  maxWindSpeed: 4.0,
+  minWindSpeed: 0.25,
+  warningThreshold: 0.8,
+  dangerThreshold: 1.0,
+  stopGasThreshold: 1.0,
+  stableDurationSec: 1800,
+  baseTime: '2025-05-02T13:02:01',
+  cycleDurationMin: 10,
+};
+
+/** 仿真数据点 */
+export interface GasDischargeSimDataPoint {
+  simTime: number;
+  realTime: string;
+  T;
+  gas1: number;
+  gas2: number;
+  gas3: number;
+  co2: number;
+  frequencyHz: number;
+  airVolumeM3: number;
+  windSpeed: number; // 风速 m/s
+  state: GasDischargeSimState;
+}
+
+/** 事件日志条目 */
+export interface GasDischargeEventLogEntry {
+  id: number;
+  simTime: number;
+  realTime: string;
+  eventType: 'info' | 'warning' | 'state_change' | 'frequency_adjust' | 'speed_limit';
+  message: string;
+  gas1: number;
+  gas2: number;
+  co2: number;
+  frequencyHz: number;
+  airVolumeM3: number;
+  windSpeed: number;
+}
+
+/** 瓦斯排放仿真趋势图列定义 */
+export const chartsColumnListGasDischarge = [
+  {
+    legend: '工作面瓦斯',
+    seriesName: '(%)',
+    ymax: 4,
+    yname: '%',
+    linetype: 'line',
+    yaxispos: 'left',
+    color: '#00FFA8',
+    sort: 1,
+    xRotate: 0,
+    dataIndex: 'gas1',
+  },
+  {
+    legend: '回风瓦斯',
+    seriesName: '(%)',
+    ymax: 2,
+    yname: '%',
+    linetype: 'line',
+    yaxispos: 'left',
+    color: '#FDB146',
+    sort: 2,
+    xRotate: 0,
+    dataIndex: 'gas2',
+  },
+  {
+    legend: '频率',
+    seriesName: '(Hz)',
+    ymax: 50,
+    yname: 'Hz',
+    linetype: 'line',
+    yaxispos: 'right',
+    color: '#AE19FF',
+    sort: 3,
+    xRotate: 0,
+    dataIndex: 'frequencyHz',
+  },
+  {
+    legend: '风量',
+    seriesName: '(m³/min)',
+    ymax: 400,
+    yname: 'm³/min',
+    linetype: 'line',
+    yaxispos: 'right',
+    color: '#03C2EC',
+    sort: 4,
+    xRotate: 0,
+    dataIndex: 'airVolumeM3',
+  },
+];
+
 const componentsCaches = new Map<string, any>();
 export function getModelComponent(is2DModel: boolean = false, type: string = '') {
   if (!is2DModel) return EntryThree;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 967 - 816
src/views/vent/monitorManager/fanLocalMonitor/index.vue


+ 245 - 208
src/views/vent/monitorManager/mainFanMonitor/components/deviceFaultDetailModal.vue

@@ -1,16 +1,15 @@
 <template>
-  <a-modal v-model:visible="visible" width="1200px" :title="title" centered destroyOnClose :footer="null"
-    @cancel="handleCancel">
+  <a-modal v-model:visible="visible" width="1200px" :title="title" centered destroyOnClose :footer="null" @cancel="handleCancel">
     <div class="basic-top">
       <div class="top-l">
-        <faultEchartPie :normalNum="normalNum" :abnormalNum="abnormalNum"></faultEchartPie>
+        <faultEchartPie :normalNum="normalNum" :abnormalNum="abnormalNum" />
       </div>
       <div class="top-r">
         <div class="basic-title">异常测点信息</div>
         <div class="list-r">
           <div class="list-item" v-for="(item, index) in faultList" :key="index">
-            <span style="margin-right: 5px;">{{ `${index + 1}.` }}</span>
-            <span>{{ item.label }}</span>
+            <span style="margin-right: 5px; color: #f50707">{{ `${index + 1}.` }}</span>
+            <span style="color: #f50707">{{ item.label }}</span>
           </div>
         </div>
       </div>
@@ -19,53 +18,91 @@
       <div class="basic-content">
         <div class="basic-title">轴温监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'tempZw'" :addData="tempzwValue" :legendName="'轴温'"
-            :echartColor="'#ff0000'"></faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'tempZw'"
+            :addData="tempzwValue"
+            :legendName="'轴温'"
+            :echartColor="'#ff0000'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">电机温度监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'tempDj'" :addData="tempdjValue" :legendName="'电机温度'"
-            :echartColor="'#2ec1dd'"></faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'tempDj'"
+            :addData="tempdjValue"
+            :legendName="'电机温度'"
+            :echartColor="'#2ec1dd'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">电流监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'Dl'" :addData="dlValue" :legendName="'电流'"
-            :echartColor="'#fbc21c'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'Dl'"
+            :addData="dlValue"
+            :legendName="'电流'"
+            :echartColor="'#fbc21c'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">电压监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'Dy'" :addData="dyValue" :legendName="'电压'"
-            :echartColor="'#259745'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'Dy'"
+            :addData="dyValue"
+            :legendName="'电压'"
+            :echartColor="'#259745'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">径向振动监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'jxzd'" :addData="jxzdValue" :legendName="'径向振动'"
-            :echartColor="'#0fcb74'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'jxzd'"
+            :addData="jxzdValue"
+            :legendName="'径向振动'"
+            :echartColor="'#0fcb74'"
+          />
         </div>
       </div>
       <div class="basic-content">
         <div class="basic-title">垂直振动监测曲线</div>
         <div class="echart-box">
-          <faultEchartLine :selectData="selectData" :warningMonitorRowIndex="warningMonitorRowIndex"
-            :deviceId="deviceID" :deviceType="deviceType" :Type="'czzd'" :addData="czzdValue" :legendName="'垂直振动'"
-            :echartColor="'#00a9ff'">
-          </faultEchartLine>
+          <faultEchartLine
+            :selectData="selectData"
+            :warningMonitorRowIndex="warningMonitorRowIndex"
+            :deviceId="deviceID"
+            :deviceType="deviceType"
+            :Type="'czzd'"
+            :addData="czzdValue"
+            :legendName="'垂直振动'"
+            :echartColor="'#00a9ff'"
+          />
         </div>
       </div>
     </div>
@@ -73,206 +110,206 @@
 </template>
 
 <script setup lang="ts">
-import { computed, reactive, ref, watch } from 'vue'
-import faultEchartPie from './faultEchartPie.vue'
-import faultEchartLine from './faultEchartLine.vue'
+  import { computed, reactive, ref, watch } from 'vue';
+  import faultEchartPie from './faultEchartPie.vue';
+  import faultEchartLine from './faultEchartLine.vue';
 
-let props = defineProps({
-  //是否显示弹窗
-  isShowModal: {
-    type: Boolean
-  },
-  //当前激活风机索引
-  warningMonitorRowIndex: {
-    type: Number
-  },
-  rightColumns: {
-    type: Array as any
-  },
-  selectData: {
-    type: Object as any
-  },
-  deviceID: {
-    type: String
-  },
-  deviceType: {
-    type: String
-  }
-})
-
-let visible = ref(false)
-let title = ref('风机故障诊断分析')
-let faultList = ref<any[]>([])//异常信息列表
-let normalNum = ref(0)//正常测点数量
-let abnormalNum = ref(0)//
+  let props = defineProps({
+    //是否显示弹窗
+    isShowModal: {
+      type: Boolean,
+    },
+    //当前激活风机索引
+    warningMonitorRowIndex: {
+      type: Number,
+    },
+    rightColumns: {
+      type: Array as any,
+    },
+    selectData: {
+      type: Object as any,
+    },
+    deviceID: {
+      type: String,
+    },
+    deviceType: {
+      type: String,
+    },
+  });
 
-let tempzwValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Tempzw'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Tempzw'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let visible = ref(false);
+  let title = ref('风机故障诊断分析');
+  let faultList = ref<any[]>([]); //异常信息列表
+  let normalNum = ref(0); //正常测点数量
+  let abnormalNum = ref(0); //
 
-}) //实时轴温
-let tempdjValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Tempdj'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Tempdj'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let tempzwValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Tempzw'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Tempzw'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时轴温
+  let tempdjValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Tempdj'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Tempdj'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时电机温度
+  let dlValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dl'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dl'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时电流
+  let dyValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dy'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dy'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时电压
+  let jxzdValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Jxzd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Jxzd'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时径向振动
+  let czzdValue = computed(() => {
+    return {
+      y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Czzd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Czzd'] : '',
+      x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' ')),
+    };
+  }); //实时垂直振动
 
-}) //实时电机温度
-let dlValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dl'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dl'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
+  let $emit = defineEmits(['handlerClose']);
 
-}) //实时电流
-let dyValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Dy'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Dy'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
+  //弹窗关闭
+  function handleCancel() {
+    $emit('handlerClose', false);
   }
 
-}) //实时电压
-let jxzdValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Jxzd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Jxzd'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
-
-}) //实时径向振动
-let czzdValue = computed(() => {
-  return {
-    y: props.warningMonitorRowIndex == 0 ? props.selectData['Fan1Czzd'] : props.warningMonitorRowIndex == 1 ? props.selectData['Fan2Czzd'] : '',
-    x: props.selectData['tTime'].substring(props.selectData['tTime'].lastIndexOf(' '))
-  }
-
-}) //实时垂直振动
-
-let $emit = defineEmits(['handlerClose'])
-
+  watch(
+    () => props.isShowModal,
+    (newV, oldV) => {
+      visible.value = newV;
+    }
+  );
 
-//弹窗关闭
-function handleCancel() {
-  $emit('handlerClose', false)
-}
-
-watch(() => props.isShowModal, (newV, oldV) => {
-  visible.value = newV
-})
-
-watch(() => props.warningMonitorRowIndex, (newV, oldV) => {
-  // if (props.rightColumns.length) {
-  //   let dataList: any[] = []
-  //   props.rightColumns.forEach((el) => {
-  //     if (el.dataIndex.startsWith('Fan')) {
-  //       if (newV == 0 && props.selectData[el.dataIndex.replace('Fan', 'Fan1')] != undefined) {
-  //         dataList.push({ label: el.title })
-  //       } else if (newV == 1 && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != '0') {
-  //         dataList.push({ label: el.title })
-  //       } else if (newV == 2 && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != '0') {
-  //         dataList.push({ label: el.title })
-  //       }
-  //     } else {
-  //       if (props.selectData[el.dataIndex] != undefined && props.selectData[el.dataIndex] != '0') {
-  //         dataList.push({ label: el.title })
-  //       }
-  //     }
-  //   })
-  //   faultList.value = dataList
-  //   abnormalNum.value = dataList.length
-  //   normalNum.value = props.rightColumns.length - dataList.length
-  //   console.log(dataList, 'dataList===')
-  // }
-  if (newV == 0) {
-    faultList.value = [
-      { label: '2025年7月12日,风速超限' },
-      { label: '2025年7月21日,风量波动' },
-      { label: '2025年8月4日,轴温偏高' },
-      { label: '2025年8月17日,径向振动过大' },
-      { label: '2025年8月23日,油温高' },
-      { label: '2025年8月26日,电机故障' },
-    ]
-  } else if (newV == 1) {
-    faultList.value = [
-      { label: '2025年8月2日,风速超限' },
-      { label: '2025年8月11日,振动过高' },
-      { label: '2025年8月24日,轴温偏高' },
-      { label: '2025年9月17日,径向振动过大' },
-      { label: '2025年9月23日,油温高' },
-      { label: '2025年9月26日,蝶阀故障' },
-    ]
-  } else {
-    faultList.value = [
-      { label: '2025年8月2日,风门故障' },
-      { label: '2025年8月11日,风量波动' },
-      { label: '2025年8月24日,轴温偏高' },
-      { label: '2025年9月17日,径向振动过大' },
-      { label: '2025年9月23日,变频故障反馈' },
-      { label: '2025年9月26日,电机故障' },
-    ]
-  }
-  abnormalNum.value = faultList.value.length
-  normalNum.value = Math.ceil(Math.random() * 10)
-}, { immediate: true })
+  watch(
+    () => props.warningMonitorRowIndex,
+    (newV, oldV) => {
+      // if (props.rightColumns.length) {
+      //   let dataList: any[] = []
+      //   props.rightColumns.forEach((el) => {
+      //     if (el.dataIndex.startsWith('Fan')) {
+      //       if (newV == 0 && props.selectData[el.dataIndex.replace('Fan', 'Fan1')] != undefined) {
+      //         dataList.push({ label: el.title })
+      //       } else if (newV == 1 && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan2')] != '0') {
+      //         dataList.push({ label: el.title })
+      //       } else if (newV == 2 && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != undefined && props.selectData[el.dataIndex.replace('Fan', 'Fan3')] != '0') {
+      //         dataList.push({ label: el.title })
+      //       }
+      //     } else {
+      //       if (props.selectData[el.dataIndex] != undefined && props.selectData[el.dataIndex] != '0') {
+      //         dataList.push({ label: el.title })
+      //       }
+      //     }
+      //   })
+      //   faultList.value = dataList
+      //   abnormalNum.value = dataList.length
+      //   normalNum.value = props.rightColumns.length - dataList.length
+      //   console.log(dataList, 'dataList===')
+      // }
+      if (newV == 0) {
+        faultList.value = [
+          { label: '2025年3月17日,风速超限' },
+          { label: '2025年4月21日,风量波动' },
+          { label: '2025年4月22日,轴温偏高' },
+          { label: '2025年5月10日,径向振动过大' },
+          { label: '2025年6月23日,油温高' },
+          { label: '2025年8月26日,电机故障' },
+        ];
+      } else if (newV == 1) {
+        faultList.value = [
+          { label: '2025年8月2日,风速超限' },
+          { label: '2025年8月11日,振动过高' },
+          { label: '2025年8月24日,轴温偏高' },
+          { label: '2025年9月17日,径向振动过大' },
+          { label: '2025年9月23日,油温高' },
+          { label: '2025年9月26日,蝶阀故障' },
+        ];
+      } else {
+        faultList.value = [
+          { label: '2025年8月2日,风门故障' },
+          { label: '2025年8月11日,风量波动' },
+          { label: '2025年8月24日,轴温偏高' },
+          { label: '2025年9月17日,径向振动过大' },
+          { label: '2025年9月23日,变频故障反馈' },
+          { label: '2025年9月26日,电机故障' },
+        ];
+      }
+      abnormalNum.value = faultList.value.length;
+      normalNum.value = 45;
+    },
+    { immediate: true }
+  );
 </script>
 
 <style lang="less" scoped>
-.basic-top {
-  width: calc(100% - 30px);
-  height: 220px;
-  margin: 10px;
-  display: flex;
+  .basic-top {
+    width: calc(100% - 30px);
+    height: 200px;
+    margin: 10px;
+    display: flex;
 
-  .top-l {
-    width: 320px;
-    margin-right: 10px;
-  }
+    .top-l {
+      width: 320px;
+      margin-right: 10px;
+    }
 
-  .top-r {
-    width: calc(100% - 330px);
-  }
+    .top-r {
+      width: calc(100% - 330px);
+    }
 
-  .list-r {
-    height: calc(100% - 25px);
-    padding: 0px 10px;
-    box-sizing: border-box;
-    overflow-y: auto;
-    border: 1px solid #3062a2;
-  }
+    .list-r {
+      height: calc(100% - 25px);
+      padding: 0px 10px;
+      box-sizing: border-box;
+      overflow-y: auto;
+      border: 1px solid #3062a2;
+    }
 
-  .list-item {
-    margin: 5px 0px;
+    .list-item {
+      margin: 5px 0px;
+    }
   }
-}
 
-.basic-title {
-  height: 20px;
-  line-height: 20px;
-  font-weight: bold;
-  margin-bottom: 5px;
-}
+  .basic-title {
+    height: 20px;
+    line-height: 20px;
+    font-weight: bold;
+    margin-bottom: 5px;
+  }
 
-.basic-bot {
-  width: calc(100% - 30px);
-  height: 600px;
-  margin: 10px;
-  display: flex;
-  justify-content: space-between;
-  flex-wrap: wrap;
-  overflow-y: auto;
+  .basic-bot {
+    width: calc(100% - 30px);
+    height: 400px;
+    margin: 10px;
+    display: flex;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    overflow-y: auto;
 
-  .basic-content {
-    width: calc(50% - 5px);
-    height: 240px;
-    margin-bottom: 10px;
-    padding: 5px 10px;
-    box-sizing: border-box;
-    border: 1px solid #3062a2;
-  }
+    .basic-content {
+      width: calc(50% - 5px);
+      height: 240px;
+      margin-bottom: 10px;
+      padding: 5px 10px;
+      box-sizing: border-box;
+      border: 1px solid #3062a2;
+    }
 
-  .echart-box {
-    height: calc(100% - 25px);
+    .echart-box {
+      height: calc(100% - 25px);
+    }
   }
-}
 </style>

+ 242 - 219
src/views/vent/monitorManager/mainFanMonitor/components/entryThree.vue

@@ -7,114 +7,250 @@
       style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 1; top: 0"
     >
       <div style="position: relative" v-if="selectData['modalTyoe'] && selectData['modalTyoe'] !== 'mainWindRect-Inverse'">
-        <div class="elementTag" id="inputBox1" v-if="backMonitorIsShow">
-          <div class="elementContent elementContent-r">
-            <!-- <div class="element-item"><span class="data-title">风机气压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
-            <div class="element-item"
-              ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan2FanPre ? selectData.Fan2FanPre : '-' }}</span></div
-            >
-            <div class="element-item"
-              ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan2m3 ? selectData.Fan2m3 : '-' }}</span></div
-            >
-          </div>
-        </div>
-        <div class="elementTag" id="inputBox" v-if="frontMonitorIsShow">
-          <div class="elementContent elementContent-r">
-            <!-- <div class="element-item"><span class="data-title">风机全压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
-            <div class="element-item"
-              ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan1FanPre ? selectData.Fan1FanPre : '-' }}</span></div
-            >
-            <div class="element-item"
-              ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan1m3 ? selectData.Fan1m3 : '-' }}</span></div
-            >
-          </div>
-        </div>
-        <div class="elementTag" id="inputBox2" v-if="centerMonitorIsShow">
-          <div class="elementContent elementContent-r">
-            <!-- <div class="element-item"><span class="data-title">风机气压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
-            <div class="element-item"
-              ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan3FanPre ? selectData.Fan3FanPre : '-' }}</span></div
-            >
-            <div class="element-item"
-              ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan3m3 ? selectData.Fan3m3 : '-' }}</span></div
-            >
-          </div>
-        </div>
-        <!-- 18模拟反风锁井 -->
-        <div v-if="hasPermission('mainFan:ffsjMonitor_ljh')" class="elementTag" id="fbm">
-          <div class="elementContent elementContent-r fbm-box">
-            <div class="fbm-video">
-              <div class="vent-flex-row-between vent-margin-t-20">
-                <span class="data-title">风门开启状态:</span>
-                <template v-if="explosionDoorData['openGate'] == 1 && explosionDoorData['closeGate'] == 0">
-                  <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>开启</span>
-                </template>
-                <template v-else-if="explosionDoorData['openGate'] == 0 && explosionDoorData['closeGate'] == 1">
-                  <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>关闭</span>
-                </template>
-                <template v-else>
-                  <div class="vent-margin-l-10"><span class="signal-round signal-round-warning vent-margin-r-8"></span>正在运行 或 数据异常</div>
-                </template>
-              </div>
-              <div class="vent-flex-row-between vent-margin-t-10">
-                <span class="data-title">反风锁紧状态:</span>
-                <template
-                  v-if="
-                    explosionDoorData['lock1Open'] == 1 &&
-                    explosionDoorData['lock1Close'] == 0 &&
-                    explosionDoorData['lock2Open'] == 1 &&
-                    explosionDoorData['lock2Close'] == 0
-                  "
-                >
-                  <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>锁1开</span>
-                  <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>锁2开</span>
-                </template>
-                <template
-                  v-else-if="
-                    explosionDoorData['lock1Open'] == 0 &&
-                    explosionDoorData['lock1Close'] == 1 &&
-                    explosionDoorData['lock2Open'] == 0 &&
-                    explosionDoorData['lock2Close'] == 1
-                  "
-                >
-                  <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>锁1关</span>
-                  <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>锁2关</span>
-                </template>
-                <template v-else>
-                  <div class="vent-margin-l-10"><span class="signal-round signal-round-warning vent-margin-r-8"></span>正在运行 或 数据异常</div>
-                </template>
-              </div>
+        <template v-if="!fanSwitchoverSimulation">
+          <div class="elementTag" id="inputBox1" v-if="backMonitorIsShow">
+            <div class="elementContent elementContent-r">
+              <!-- <div class="element-item"><span class="data-title">风机气压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
+              <div class="element-item"
+                ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan2FanPre ? selectData.Fan2FanPre : '-' }}</span></div
+              >
+              <div class="element-item"
+                ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan2m3 ? selectData.Fan2m3 : '-' }}</span></div
+              >
             </div>
-            <div class="fbm-data">
-              <!-- <div class="element-item"
-                ><span class="data-title">井口负压(kPa):</span
-                ><span>{{ selectData.Fan1FanPre ? selectData.Fan1FanPre : selectData.Fan2FanPre ? selectData.Fan2FanPre : '-' }}</span></div
-              > -->
+          </div>
+          <div class="elementTag" id="inputBox" v-if="frontMonitorIsShow">
+            <div class="elementContent elementContent-r">
+              <!-- <div class="element-item"><span class="data-title">风机全压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
               <div class="element-item"
-                ><span class="data-title">井口正压(kPa):</span><span>{{ get(explosionDoorData, 'zhengya') }}</span></div
+                ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan1FanPre ? selectData.Fan1FanPre : '-' }}</span></div
               >
               <div class="element-item"
-                ><span class="data-title">井口温度(℃):</span><span>{{ get(explosionDoorData, 'tempreture') }}</span></div
+                ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan1m3 ? selectData.Fan1m3 : '-' }}</span></div
               >
+            </div>
+          </div>
+          <div class="elementTag" id="inputBox2" v-if="centerMonitorIsShow">
+            <div class="elementContent elementContent-r">
+              <!-- <div class="element-item"><span class="data-title">风机气压(Pa):</span><span>{{ selectData.DataPa ? selectData.DataPa : '-' }}</span></div> -->
               <div class="element-item"
-                ><span class="data-title">甲烷浓度(%):</span><span>{{ get(explosionDoorData, 'ch4') }}</span></div
+                ><span class="data-title">风机负压(Pa):</span><span>{{ selectData.Fan3FanPre ? selectData.Fan3FanPre : '-' }}</span></div
               >
               <div class="element-item"
-                ><span class="data-title">CO浓度(%):</span><span>{{ get(explosionDoorData, 'co') }}</span></div
+                ><span class="data-title">风机风量(m³/s):</span><span>{{ selectData.Fan3m3 ? selectData.Fan3m3 : '-' }}</span></div
               >
+            </div>
+          </div>
+          <!-- 18模拟反风锁井 -->
+          <div v-if="hasPermission('mainFan:ffsjMonitor_ljh')" class="elementTag" id="fbm">
+            <div class="elementContent elementContent-r fbm-box">
+              <div class="fbm-video">
+                <div class="vent-flex-row-between vent-margin-t-20">
+                  <span class="data-title">风门开启状态:</span>
+                  <template v-if="explosionDoorData['openGate'] == 1 && explosionDoorData['closeGate'] == 0">
+                    <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>开启</span>
+                  </template>
+                  <template v-else-if="explosionDoorData['openGate'] == 0 && explosionDoorData['closeGate'] == 1">
+                    <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>关闭</span>
+                  </template>
+                  <template v-else>
+                    <div class="vent-margin-l-10"><span class="signal-round signal-round-warning vent-margin-r-8"></span>正在运行 或 数据异常</div>
+                  </template>
+                </div>
+                <div class="vent-flex-row-between vent-margin-t-10">
+                  <span class="data-title">反风锁紧状态:</span>
+                  <template
+                    v-if="
+                      explosionDoorData['lock1Open'] == 1 &&
+                      explosionDoorData['lock1Close'] == 0 &&
+                      explosionDoorData['lock2Open'] == 1 &&
+                      explosionDoorData['lock2Close'] == 0
+                    "
+                  >
+                    <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>锁1开</span>
+                    <span class="data-title"><span class="signal-round signal-round-blue vent-margin-r-8"></span>锁2开</span>
+                  </template>
+                  <template
+                    v-else-if="
+                      explosionDoorData['lock1Open'] == 0 &&
+                      explosionDoorData['lock1Close'] == 1 &&
+                      explosionDoorData['lock2Open'] == 0 &&
+                      explosionDoorData['lock2Close'] == 1
+                    "
+                  >
+                    <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>锁1关</span>
+                    <span class="data-title"><span class="signal-round signal-round-gry vent-margin-r-8"></span>锁2关</span>
+                  </template>
+                  <template v-else>
+                    <div class="vent-margin-l-10"><span class="signal-round signal-round-warning vent-margin-r-8"></span>正在运行 或 数据异常</div>
+                  </template>
+                </div>
+              </div>
+              <div class="fbm-data">
+                <!-- <div class="element-item"
+                ><span class="data-title">井口负压(kPa):</span
+                ><span>{{ selectData.Fan1FanPre ? selectData.Fan1FanPre : selectData.Fan2FanPre ? selectData.Fan2FanPre : '-' }}</span></div
+              > -->
+                <div class="element-item"
+                  ><span class="data-title">井口正压(kPa):</span><span>{{ get(explosionDoorData, 'zhengya') }}</span></div
+                >
+                <div class="element-item"
+                  ><span class="data-title">井口温度(℃):</span><span>{{ get(explosionDoorData, 'tempreture') }}</span></div
+                >
+                <div class="element-item"
+                  ><span class="data-title">甲烷浓度(%):</span><span>{{ get(explosionDoorData, 'ch4') }}</span></div
+                >
+                <div class="element-item"
+                  ><span class="data-title">CO浓度(%):</span><span>{{ get(explosionDoorData, 'co') }}</span></div
+                >
+                <div class="fbm-data">
+                  <div class="vent-flex-row-between">
+                    <span class="data-title">操作方式:</span>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round signal-round-blue vent-margin-r-8"
+                        :class="{ 'signal-round-blue': get(explosionDoorData, 'isRemote') == 1 }"
+                      ></span
+                      >远程</span
+                    >
+                    <span class="data-title"
+                      ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': get(explosionDoorData, 'isRemote') == 0 }"></span
+                      >就地</span
+                    >
+                  </div>
+                  <div class="vent-flex-row-between">
+                    <span class="data-title">是否检修:</span>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round signal-round-blue vent-margin-r-8"
+                        :class="{ 'signal-round-blue': get(explosionDoorData, 'isrun') == 1 }"
+                      ></span
+                      >正常</span
+                    >
+                    <span class="data-title"
+                      ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': get(explosionDoorData, 'isrun') == 0 }"></span
+                      >检修</span
+                    >
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div v-if="hasPermission('mainFan:ffsjMonitor')">
+            <div class="elementContent elementContent-r fbm-box">
+              <div class="fbm-video">
+                <div class="vent-flex-row-between vent-margin-t-20">
+                  <span class="data-title">风门开启状态:</span>
+                  <template v-if="explosionDoorData['gate_1_kai'] == 1">
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{
+                          'signal-round-blue': explosionDoorData['gate_1_kai'] == 1,
+                          'signal-round-gry': explosionDoorData['gate_1_kai'] == 0,
+                        }"
+                      ></span
+                      >门1开启</span
+                    >
+                  </template>
+                  <template v-else-if="explosionDoorData['gate_2_kai'] == 1">
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{
+                          'signal-round-blue': explosionDoorData['gate_1_kai'] == 1,
+                          'signal-round-gry': explosionDoorData['gate_1_kai'] == 0,
+                        }"
+                      ></span
+                      >门2开启</span
+                    >
+                  </template>
+                  <template v-else>
+                    <div class="vent-margin-l-10"
+                      ><span class="signal-round signal-round-warning vent-margin-r-8"></span>防爆门正在运行 或 数据异常</div
+                    >
+                  </template>
+                </div>
+                <div class="vent-flex-row-between vent-margin-t-10">
+                  <span class="data-title">反风锁紧状态:</span>
+                  <div>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo1_kai'] == 1, 'signal-round-gry': explosionDoorData['suo1_kai'] == 0 }"
+                      ></span
+                      >锁1开</span
+                    >
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo1_guan'] == 1, 'signal-round-gry': explosionDoorData['suo1_guan'] == 0 }"
+                      ></span
+                      >锁1关</span
+                    >
+                  </div>
+                  <div>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo2_kai'] == 1, 'signal-round-gry': explosionDoorData['suo2_kai'] == 0 }"
+                      ></span
+                      >锁1开</span
+                    >
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo2_guan'] == 1, 'signal-round-gry': explosionDoorData['suo2_guan'] == 0 }"
+                      ></span
+                      >锁2关</span
+                    >
+                  </div>
+                  <div>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo3_kai'] == 1, 'signal-round-gry': explosionDoorData['suo3_kai'] == 0 }"
+                      ></span
+                      >锁3开</span
+                    >
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo3_guan'] == 1, 'signal-round-gry': explosionDoorData['suo3_guan'] == 0 }"
+                      ></span
+                      >锁3关</span
+                    >
+                  </div>
+                  <div>
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo4_kai'] == 1, 'signal-round-gry': explosionDoorData['suo4_kai'] == 0 }"
+                      ></span
+                      >锁4开</span
+                    >
+                    <span class="data-title"
+                      ><span
+                        class="signal-round vent-margin-r-8"
+                        :class="{ 'signal-round-blue': explosionDoorData['suo4_guan'] == 1, 'signal-round-gry': explosionDoorData['suo4_guan'] == 0 }"
+                      ></span
+                      >锁4关</span
+                    >
+                  </div>
+                </div>
+              </div>
               <div class="fbm-data">
                 <div class="vent-flex-row-between">
                   <span class="data-title">操作方式:</span>
                   <span class="data-title"
                     ><span
                       class="signal-round signal-round-blue vent-margin-r-8"
-                      :class="{ 'signal-round-blue': get(explosionDoorData, 'isRemote') == 1 }"
+                      :class="{ 'signal-round-blue': explosionDoorData['jd_yc'] == 1 }"
                     ></span
                     >远程</span
                   >
                   <span class="data-title"
-                    ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': get(explosionDoorData, 'isRemote') == 0 }"></span
-                    >就地</span
+                    ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': explosionDoorData['jd_yc'] == 0 }"></span>就地</span
                   >
                 </div>
                 <div class="vent-flex-row-between">
@@ -122,146 +258,24 @@
                   <span class="data-title"
                     ><span
                       class="signal-round signal-round-blue vent-margin-r-8"
-                      :class="{ 'signal-round-blue': get(explosionDoorData, 'isrun') == 1 }"
+                      :class="{ 'signal-round-blue': explosionDoorData['zc_jx'] == 1 }"
                     ></span
                     >正常</span
                   >
                   <span class="data-title"
-                    ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': get(explosionDoorData, 'isrun') == 0 }"></span
-                    >检修</span
+                    ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': explosionDoorData['zc_jx'] == 0 }"></span>检修</span
                   >
                 </div>
               </div>
             </div>
           </div>
-        </div>
-        <div v-if="hasPermission('mainFan:ffsjMonitor')">
-          <div class="elementContent elementContent-r fbm-box">
-            <div class="fbm-video">
-              <div class="vent-flex-row-between vent-margin-t-20">
-                <span class="data-title">风门开启状态:</span>
-                <template v-if="explosionDoorData['gate_1_kai'] == 1">
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['gate_1_kai'] == 1, 'signal-round-gry': explosionDoorData['gate_1_kai'] == 0 }"
-                    ></span
-                    >门1开启</span
-                  >
-                </template>
-                <template v-else-if="explosionDoorData['gate_2_kai'] == 1">
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['gate_1_kai'] == 1, 'signal-round-gry': explosionDoorData['gate_1_kai'] == 0 }"
-                    ></span
-                    >门2开启</span
-                  >
-                </template>
-                <template v-else>
-                  <div class="vent-margin-l-10"
-                    ><span class="signal-round signal-round-warning vent-margin-r-8"></span>防爆门正在运行 或 数据异常</div
-                  >
-                </template>
-              </div>
-              <div class="vent-flex-row-between vent-margin-t-10">
-                <span class="data-title">反风锁紧状态:</span>
-                <div>
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo1_kai'] == 1, 'signal-round-gry': explosionDoorData['suo1_kai'] == 0 }"
-                    ></span
-                    >锁1开</span
-                  >
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo1_guan'] == 1, 'signal-round-gry': explosionDoorData['suo1_guan'] == 0 }"
-                    ></span
-                    >锁1关</span
-                  >
-                </div>
-                <div>
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo2_kai'] == 1, 'signal-round-gry': explosionDoorData['suo2_kai'] == 0 }"
-                    ></span
-                    >锁1开</span
-                  >
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo2_guan'] == 1, 'signal-round-gry': explosionDoorData['suo2_guan'] == 0 }"
-                    ></span
-                    >锁2关</span
-                  >
-                </div>
-                <div>
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo3_kai'] == 1, 'signal-round-gry': explosionDoorData['suo3_kai'] == 0 }"
-                    ></span
-                    >锁3开</span
-                  >
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo3_guan'] == 1, 'signal-round-gry': explosionDoorData['suo3_guan'] == 0 }"
-                    ></span
-                    >锁3关</span
-                  >
-                </div>
-                <div>
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo4_kai'] == 1, 'signal-round-gry': explosionDoorData['suo4_kai'] == 0 }"
-                    ></span
-                    >锁4开</span
-                  >
-                  <span class="data-title"
-                    ><span
-                      class="signal-round vent-margin-r-8"
-                      :class="{ 'signal-round-blue': explosionDoorData['suo4_guan'] == 1, 'signal-round-gry': explosionDoorData['suo4_guan'] == 0 }"
-                    ></span
-                    >锁4关</span
-                  >
-                </div>
-              </div>
-            </div>
-            <div class="fbm-data">
-              <div class="vent-flex-row-between">
-                <span class="data-title">操作方式:</span>
-                <span class="data-title"
-                  ><span
-                    class="signal-round signal-round-blue vent-margin-r-8"
-                    :class="{ 'signal-round-blue': explosionDoorData['jd_yc'] == 1 }"
-                  ></span
-                  >远程</span
-                >
-                <span class="data-title"
-                  ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': explosionDoorData['jd_yc'] == 0 }"></span>就地</span
-                >
-              </div>
-              <div class="vent-flex-row-between">
-                <span class="data-title">是否检修:</span>
-                <span class="data-title"
-                  ><span
-                    class="signal-round signal-round-blue vent-margin-r-8"
-                    :class="{ 'signal-round-blue': explosionDoorData['zc_jx'] == 1 }"
-                  ></span
-                  >正常</span
-                >
-                <span class="data-title"
-                  ><span class="signal-round vent-margin-r-8" :class="{ 'signal-round-blue': explosionDoorData['zc_jx'] == 0 }"></span>检修</span
-                >
-              </div>
-            </div>
+        </template>
+        <template v-else>
+          <!-- 李家壕风机模拟 -->
+          <div class="elementTag">
+            <FanSwitchoverSimPanel :selectData="selectData" @sim-speed-change="emit('simSpeedChange', $event)" @sim-abort="emit('simAbort')" />
           </div>
-        </div>
+        </template>
       </div>
     </div>
     <div id="main3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
@@ -271,6 +285,7 @@
 <script lang="ts" setup>
   import { usePermission } from '/@/hooks/web/usePermission';
   import { get } from '@/utils/ventutil';
+  import FanSwitchoverSimPanel from './FanSwitchoverSimPanel.vue';
 
   defineProps<{
     loading: boolean;
@@ -280,6 +295,14 @@
     frontMonitorIsShow: boolean;
     backMonitorIsShow: boolean;
   }>();
+
+  const emit = defineEmits<{
+    simSpeedChange: [speed: number];
+    simAbort: [];
+  }>();
+
+  const fanSwitchoverSimulation = VENT_PARAM['fanSwitchoverSimulation'];
+
   const { hasPermission } = usePermission();
 </script>
 <style scoped lang="less">

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1046 - 769
src/views/vent/monitorManager/mainFanMonitor/index.vue


+ 87 - 11
src/views/vent/monitorManager/mainFanMonitor/main.data.ts

@@ -383,16 +383,21 @@ export const modalTypeArr = {
       value: '一键启动/停止',
       permission: 'fan:startAndResetFan',
     },
-    {
-      key: 'resetFan',
-      value: '一键复位',
-      permission: 'fan:resetFan',
-    },
+    // {
+    //   key: 'resetFan',
+    //   value: '一键复位',
+    //   permission: 'fan:resetFan',
+    // },
     {
       key: 'changeSmoke',
       value: '不停风倒机',
       permission: 'fan:changeSmoke',
     },
+    {
+      key: 'changeSmoke1',
+      value: '不停风倒机',
+      permission: 'fan:changeSmoke1',
+    },
     {
       key: 'changeDirection',
       value: '一键反风',
@@ -408,11 +413,7 @@ export const modalTypeArr = {
       value: '防爆门控制',
       permission: 'fan:fbm',
     },
-    {
-      key: 'kkjc',
-      value: '工况辅助决策',
-      permission: 'fan:kkjc',
-    },
+
     {
       key: 'zhlk',
       value: '自主联控',
@@ -430,9 +431,24 @@ export const modalTypeArr = {
     },
     {
       key: 'tckz',
-      value: '天窗控',
+      value: '天窗控',
       permission: 'fan:tckz',
     },
+    {
+      key: 'zbmkz',
+      value: '闸板门调控',
+      permission: 'fan:zbmkz',
+    },
+    {
+      key: 'dfkz',
+      value: '蝶阀控制',
+      permission: 'fan:dfkz',
+    },
+    {
+      key: 'kkjc',
+      value: '工况辅助决策',
+      permission: 'fan:kkjc',
+    },
   ],
 };
 
@@ -2472,3 +2488,63 @@ export function getModelComponent(is2DModel: boolean = false, modalTyoe: string
     }
   });
 }
+
+// ==================== 不停风倒机模拟配置 ====================
+
+/** 模拟状态枚举 */
+export enum SimState {
+  IDLE = 'IDLE',
+  INIT = 'INIT',
+  OPEN_SKYLIGHT = 'OPEN_SKYLIGHT',
+  STABILIZE = 'STABILIZE',
+  TRANSFER = 'TRANSFER',
+  REVERSE = 'REVERSE',
+  END = 'END',
+  COMPLETE = 'COMPLETE',
+  ABORTED = 'ABORTED',
+}
+
+/** 模拟步骤名称映射 */
+export const SIM_STEP_NAMES: Record<SimState, string> = {
+  [SimState.IDLE]: '待机',
+  [SimState.INIT]: '初始化',
+  [SimState.OPEN_SKYLIGHT]: '开启目标天窗',
+  [SimState.STABILIZE]: '等待风量稳定',
+  [SimState.TRANSFER]: '逐步切换',
+  [SimState.REVERSE]: '回退调整',
+  [SimState.END]: '完成切换',
+  [SimState.COMPLETE]: '倒机完成',
+  [SimState.ABORTED]: '已终止',
+};
+
+/** 模拟配置参数 */
+export const SIM_CONFIG = {
+  targetSkylightMax: 5.0, // m^2, 天窗最大面积
+  gateMax: 7.0, // m^2, 闸板门最大面积
+  totalSteps: 12, // 切换步数
+  stepDuration: 28, // 每步秒数 (12*28=336s)
+  dS: 0.42, // 面积步长 m^2 (5.00/12)
+  stabilizeWait: 10, // 稳定等待秒数
+  skylightOpenWait: 30, // 天窗开启等待秒数
+  maxDelta: 0.2, // 风量波动阈值 20%
+  tickInterval: 500, // 数据刷新间隔 ms
+  areaMonitorMaxError: 0.008, // 天窗/闸板门到位值最大误差 ±0.8% FS
+};
+
+/** 模拟字段初始值 */
+export const SIM_INIT_FIELDS = {
+  Fan1WindowArea: undefined,
+  Fan1GateArea: undefined,
+  Fan2WindowArea: undefined,
+  Fan2GateArea: undefined,
+  simState: SimState.IDLE,
+  simStep: 0,
+  simTotalSteps: 0,
+  simElapsed: 0,
+  simSpeed: 1,
+  simDirection: '',
+  simAirflowDelta: 0,
+  simStepName: '',
+  simStepLog: [],
+  simStepDetail: '',
+};

+ 78 - 59
src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts

@@ -261,9 +261,12 @@ export const playAnimate = async (selectData, duration?) => {
     mainObj = mainFanLjDtObj;
   }
   if (selectData && mainObj) {
-    if (selectData['Fan1FreqHz'] == undefined || selectData['Fan1FreqHz'] == null || selectData['Fan1FreqHz'] == '') selectData['Fan1FreqHz'] = 50;
-    if (selectData['Fan2FreqHz'] == undefined || selectData['Fan2FreqHz'] == null || selectData['Fan2FreqHz'] == '') selectData['Fan2FreqHz'] = 50;
-    if (selectData['Fan3FreqHz'] == undefined || selectData['Fan3FreqHz'] == null || selectData['Fan3FreqHz'] == '') selectData['Fan3FreqHz'] = 50;
+    if (selectData['Fan1FreqHz'] == undefined || selectData['Fan1FreqHz'] == null || selectData['Fan1FreqHz'] == '')
+      selectData['Fan1FreqHz'] = VENT_PARAM['fanSwitchoverSimulation'] ? 0 : 50;
+    if (selectData['Fan2FreqHz'] == undefined || selectData['Fan2FreqHz'] == null || selectData['Fan2FreqHz'] == '')
+      selectData['Fan2FreqHz'] = VENT_PARAM['fanSwitchoverSimulation'] ? 0 : 50;
+    if (selectData['Fan3FreqHz'] == undefined || selectData['Fan3FreqHz'] == null || selectData['Fan3FreqHz'] == '')
+      selectData['Fan3FreqHz'] = VENT_PARAM['fanSwitchoverSimulation'] ? 0 : 50;
     if (modalType === 'mainWindRect3') {
       mainObj.resetSmokeParam('front', selectData.Fan1FreqHz, duration);
       mainObj.resetSmokeParam('center', selectData.Fan2FreqHz, duration);
@@ -408,11 +411,12 @@ export const playAnimate = async (selectData, duration?) => {
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
           // await mainObj.setSmokeDirection('back', 'tubPositivePath');
-          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('back', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('back', 'tubPositivePath');
-          }
+          // if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          // }
+          await mainObj.setSmokeDirection('back', 'tubPositivePath');
         } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
           // 主风机反转
           if (mainObj['airJin1'] && !mainObj['airJin1'].visible) {
@@ -423,11 +427,12 @@ export const playAnimate = async (selectData, duration?) => {
           }
           mainObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
           // await mainObj.setSmokeDirection('back', 'tubInversePath');
-          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('back', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('back', 'tubInversePath');
-          }
+          // if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('back', 'tubInversePath');
+          // }
+          await mainObj.setSmokeDirection('back', 'tubInversePath');
         } else {
           if (mainObj['airChu1'] && !mainObj['airChu1'].visible) {
             mainObj['airJin1'].visible = false;
@@ -437,11 +442,12 @@ export const playAnimate = async (selectData, duration?) => {
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
           // await mainObj.setSmokeDirection('back', 'tubPositivePath');
-          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('back', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('back', 'tubPositivePath');
-          }
+          // if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          // }
+          await mainObj.setSmokeDirection('back', 'tubPositivePath');
         }
 
         if (!mainObj?.backSmoke?.frameId) mainObj?.backSmoke?.startSmoke(duration);
@@ -452,7 +458,7 @@ export const playAnimate = async (selectData, duration?) => {
     } else {
       mainObj.resetSmokeParam('front', selectData.Fan2FreqHz, duration);
       mainObj.resetSmokeParam('back', selectData.Fan1FreqHz, duration);
-      if (selectData.Fan2StartStatus == 1) {
+      if (selectData.Fan2StartStatus == 1 && selectData['Fan2FreqHz']) {
         // 主风机开启
         mainObj?.lookMotor('front', 'open', duration);
         mainObj?.openOrCloseValve('front', 'open', duration);
@@ -461,53 +467,58 @@ export const playAnimate = async (selectData, duration?) => {
         if (selectData.Fan2FreqForwardRun == 1 && selectData.Fan2FreqReverseRun == 0) {
           // 主风机正转
           if (mainObj['airChu2'] && !mainObj['airChu2'].visible) {
-            mainObj['airJin1'].visible = false;
+            // mainObj['airJin1'].visible = false;
+            // mainObj['airChu1'].visible = false;
             mainObj['airJin2'].visible = false;
-            mainObj['airChu1'].visible = false;
             mainObj['airChu2'].visible = true;
           }
           mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('front', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('front', 'tubPositivePath');
-          }
+          // if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          // }
+          await mainObj.setSmokeDirection('front', 'tubPositivePath');
         } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
           // 主风机反转
           if (mainObj['airJin2'] && !mainObj['airJin2'].visible) {
-            mainObj['airJin1'].visible = false;
+            // mainObj['airJin1'].visible = false;
+            // mainObj['airChu1'].visible = false;
             mainObj['airJin2'].visible = true;
-            mainObj['airChu1'].visible = false;
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('front', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
-          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('front', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('front', 'tubInversePath');
-          }
+          // if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('front', 'tubInversePath');
+          // }
+          await mainObj.setSmokeDirection('front', 'tubInversePath');
         } else {
           // 默认主风机正转
           if (mainObj['airChu2'] && !mainObj['airChu2'].visible) {
-            mainObj['airJin1'].visible = false;
+            // mainObj['airJin1'].visible = false;
+            // mainObj['airChu1'].visible = false;
             mainObj['airJin2'].visible = false;
-            mainObj['airChu1'].visible = false;
             mainObj['airChu2'].visible = true;
           }
           mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
-            await mainObj.setSmokeDirection('front', 'windowPositivePath');
-          } else {
-            await mainObj.setSmokeDirection('front', 'tubPositivePath');
-          }
+          // if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+          //   await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          // } else {
+          //   await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          // }
+          await mainObj.setSmokeDirection('front', 'tubPositivePath');
         }
 
         if (!mainObj?.frontSmoke?.frameId) mainObj?.frontSmoke?.startSmoke(duration);
       } else {
         // 主风机停止
         mainObj.closeDevice('front');
+        mainObj['airJin2'].visible = false;
+        mainObj['airChu2'].visible = false;
       }
-      if (selectData.Fan1StartStatus == 1) {
+      if (selectData.Fan1StartStatus == 1 && selectData['Fan1FreqHz']) {
         // 主风机开启
         mainObj.lookMotor('back', 'open', duration);
         mainObj.openOrCloseValve('back', 'open', duration);
@@ -518,9 +529,9 @@ export const playAnimate = async (selectData, duration?) => {
           // 主风机正转
           if (mainObj['airChu1'] && !mainObj['airChu1'].visible) {
             mainObj['airJin1'].visible = false;
-            mainObj['airJin2'].visible = false;
             mainObj['airChu1'].visible = true;
-            mainObj['airChu2'].visible = false;
+            // mainObj['airJin2'].visible = false;
+            // mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
           // await mainObj.setSmokeDirection('back', 'tubPositivePath');
@@ -533,9 +544,9 @@ export const playAnimate = async (selectData, duration?) => {
           // 主风机反转
           if (mainObj['airJin1'] && !mainObj['airJin1'].visible) {
             mainObj['airJin1'].visible = true;
-            mainObj['airJin2'].visible = false;
             mainObj['airChu1'].visible = false;
-            mainObj['airChu2'].visible = false;
+            // mainObj['airJin2'].visible = false;
+            // mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan1FreqHz, duration);
           // await mainObj.setSmokeDirection('back', 'tubInversePath');
@@ -547,9 +558,9 @@ export const playAnimate = async (selectData, duration?) => {
         } else {
           if (mainObj['airChu1'] && !mainObj['airChu1'].visible) {
             mainObj['airJin1'].visible = false;
-            mainObj['airJin2'].visible = false;
             mainObj['airChu1'].visible = true;
-            mainObj['airChu2'].visible = false;
+            // mainObj['airJin2'].visible = false;
+            // mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
 
@@ -564,21 +575,29 @@ export const playAnimate = async (selectData, duration?) => {
       } else {
         // 主风机停止
         mainObj.closeDevice('back');
+        mainObj['airJin1'].visible = false;
+        mainObj['airChu1'].visible = false;
       }
+
       if (mainObj && modalType.includes('mainWindRect')) {
-        if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
-          // 打开天窗1
-          mainObj.openOrCloseWindow('back', 'openWindow');
-        } else {
-          // 关闭天窗1
-          mainObj.openOrCloseWindow('back', 'closeWindow');
-        }
-        if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
-          // 打开天窗1
-          mainObj.openOrCloseWindow('front', 'openWindow');
+        if (!VENT_PARAM['fanSwitchoverSimulation']) {
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            // 打开天窗1
+            mainObj.openOrCloseWindow('back', 'openWindow');
+          } else {
+            // 关闭天窗1
+            mainObj.openOrCloseWindow('back', 'closeWindow');
+          }
+          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+            // 打开天窗1
+            mainObj.openOrCloseWindow('front', 'openWindow');
+          } else {
+            // 关闭天窗1
+            mainObj.openOrCloseWindow('front', 'closeWindow');
+          }
         } else {
-          // 关闭天窗1
-          mainObj.openOrCloseWindow('front', 'closeWindow');
+          mainObj.openOrCloseWindowSimulation('back', selectData['Fan1WindowArea'] || 0);
+          mainObj.openOrCloseWindowSimulation('front', selectData['Fan2WindowArea'] || 0);
         }
       }
     }

+ 121 - 49
src/views/vent/monitorManager/mainFanMonitor/mainWind.threejs.ts

@@ -49,63 +49,82 @@ class mainWindRect {
   airTexture;
   offset = 0;
   direction = 0; // -1 代表反向,1代表正向
-  arrowMesh;
+  arrowMeshFront: THREE.Mesh | null = null;
+  arrowMeshBack;
   constructor(model, playerVal1) {
     this.model = model;
     this.player1 = playerVal1;
   }
+
   // 添加 cssObject
   addCssText() {
     if (!this.group) {
       return;
     }
     const ztfjGroup = this.group.getObjectByName('ztfj');
-
-    if (!this.group.getObjectByName('monitorText1')) {
-      const worldPosition = new THREE.Vector3();
-      ztfjGroup?.getObjectByName('Cylinder1042')?.getWorldPosition(worldPosition);
-      const element = document.getElementById('inputBox') as HTMLElement;
-      if (element) {
-        const mainCSS3D = new CSS3DObject(element);
-        mainCSS3D.name = 'monitorText1';
-        mainCSS3D.scale.set(0.09, 0.09, 0.09);
-        // mainCSS3D.position.set(23.78, 18.18, -6.85);
-        mainCSS3D.position.set(worldPosition.x + 12, worldPosition.y - 10, worldPosition.z - 20);
-        mainCSS3D.lookAt(worldPosition.x + 12, worldPosition.y - 0, worldPosition.z + 2);
-        this.group.add(mainCSS3D);
+    if (VENT_PARAM['fanSwitchoverSimulation']) {
+      // 李家豪风机模拟
+      if (!this.group.getObjectByName('monitorText1')) {
+        const worldPosition = new THREE.Vector3();
+        ztfjGroup?.getObjectByName('Cylinder1042')?.getWorldPosition(worldPosition);
+        const element = document.getElementById('ljhMonitor') as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'monitorText1';
+          mainCSS3D.scale.set(0.08, 0.08, 0.08);
+          // mainCSS3D.position.set(23.78, 18.18, -6.85);
+          mainCSS3D.position.set(worldPosition.x + 12, worldPosition.y - 10, worldPosition.z - 22);
+          mainCSS3D.lookAt(worldPosition.x + 12, worldPosition.y - 0, worldPosition.z + 2);
+          this.group.add(mainCSS3D);
+        }
       }
-    }
-    if (!this.group.getObjectByName('monitorText2')) {
-      const worldPosition = new THREE.Vector3();
-      ztfjGroup?.getObjectByName('Cylinder396')?.getWorldPosition(worldPosition);
-      const element = document.getElementById('inputBox1') as HTMLElement;
-      if (element) {
-        const mainCSS3D = new CSS3DObject(element);
-        mainCSS3D.name = 'monitorText2';
-        mainCSS3D.scale.set(0.09, 0.09, 0.09);
-        // mainCSS3D.position.set(23.78, 18.18, 16.82);
-        mainCSS3D.position.set(worldPosition.x + 12, worldPosition.y - 10, worldPosition.z - 20);
-        mainCSS3D.lookAt(worldPosition.x + 12, worldPosition.y - 0, worldPosition.z + 2);
-        // mainCSS3D.lookAt(23.78, 20, 20.82);
-        this.group.add(mainCSS3D);
+    } else {
+      if (!this.group.getObjectByName('monitorText1')) {
+        const worldPosition = new THREE.Vector3();
+        ztfjGroup?.getObjectByName('Cylinder1042')?.getWorldPosition(worldPosition);
+        const element = document.getElementById('inputBox') as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'monitorText1';
+          mainCSS3D.scale.set(0.09, 0.09, 0.09);
+          // mainCSS3D.position.set(23.78, 18.18, -6.85);
+          mainCSS3D.position.set(worldPosition.x + 12, worldPosition.y - 10, worldPosition.z - 20);
+          mainCSS3D.lookAt(worldPosition.x + 12, worldPosition.y - 0, worldPosition.z + 2);
+          this.group.add(mainCSS3D);
+        }
       }
-    }
-
-    if (!this.group.getObjectByName('monitorText4')) {
-      const worldPosition = new THREE.Vector3();
-      const fbmGroup = this.group?.getObjectByName('fbm') as THREE.Group;
-      if (fbmGroup) {
-        fbmGroup?.getObjectByName('Box022')?.getWorldPosition(worldPosition);
-        const element = document.getElementById('fbm') as HTMLElement;
+      if (!this.group.getObjectByName('monitorText2')) {
+        const worldPosition = new THREE.Vector3();
+        ztfjGroup?.getObjectByName('Cylinder396')?.getWorldPosition(worldPosition);
+        const element = document.getElementById('inputBox1') as HTMLElement;
         if (element) {
           const mainCSS3D = new CSS3DObject(element);
-          mainCSS3D.name = 'monitorText4';
-          mainCSS3D.scale.set(0.07, 0.07, 0.07);
-          mainCSS3D.position.set(worldPosition.x + 20, worldPosition.y - 8, worldPosition.z - 20);
-          mainCSS3D.lookAt(worldPosition.x + 20, worldPosition.y - 0, worldPosition.z + 2);
+          mainCSS3D.name = 'monitorText2';
+          mainCSS3D.scale.set(0.09, 0.09, 0.09);
+          // mainCSS3D.position.set(23.78, 18.18, 16.82);
+          mainCSS3D.position.set(worldPosition.x + 12, worldPosition.y - 10, worldPosition.z - 20);
+          mainCSS3D.lookAt(worldPosition.x + 12, worldPosition.y - 0, worldPosition.z + 2);
+          // mainCSS3D.lookAt(23.78, 20, 20.82);
           this.group.add(mainCSS3D);
         }
       }
+
+      if (!this.group.getObjectByName('monitorText4')) {
+        const worldPosition = new THREE.Vector3();
+        const fbmGroup = this.group?.getObjectByName('fbm') as THREE.Group;
+        if (fbmGroup) {
+          fbmGroup?.getObjectByName('Box022')?.getWorldPosition(worldPosition);
+          const element = document.getElementById('fbm') as HTMLElement;
+          if (element) {
+            const mainCSS3D = new CSS3DObject(element);
+            mainCSS3D.name = 'monitorText4';
+            mainCSS3D.scale.set(0.07, 0.07, 0.07);
+            mainCSS3D.position.set(worldPosition.x + 20, worldPosition.y - 8, worldPosition.z - 20);
+            mainCSS3D.lookAt(worldPosition.x + 20, worldPosition.y - 0, worldPosition.z + 2);
+            this.group.add(mainCSS3D);
+          }
+        }
+      }
     }
   }
 
@@ -213,6 +232,7 @@ class mainWindRect {
       await this.openOrCloseValve(deviceType, 'open', duration);
       this.startGearAnimation(deviceType, 'open', smokeDirection, frequencyVal, duration);
       smoke.startSmoke(duration);
+      this.openOrCloseAir(deviceType, true);
     }
   }
 
@@ -236,6 +256,7 @@ class mainWindRect {
         await this.lookMotor(deviceType, 'close', 0);
       }
     }
+    this.openOrCloseAir(deviceType, false);
   }
 
   async setSmokeDirection(deviceType, smokeDirection) {
@@ -333,9 +354,15 @@ class mainWindRect {
         spreadDirection: 0, // 1是由小变大,-1是由大变小
       },
     ];
-    const getPathPoint = () => {
-      this.arrowMesh = this.group?.getObjectByName('arrow');
-      if (this.arrowMesh) return;
+    const getPathPoint = (deviceType: string) => {
+      let arrowMesh;
+      if (deviceType === 'front') {
+        arrowMesh = this.group?.getObjectByName('arrowFront');
+      } else {
+        arrowMesh = this.group?.getObjectByName('arrowBack');
+      }
+
+      if (arrowMesh) return arrowMesh;
       pathPoints.push(new THREE.Vector3(16.441, 1.485, 2.614), new THREE.Vector3(35.583, 1.485, 2.614));
       const pathPointList = new PathPointList();
       const up = new THREE.Vector3(0, 0, 1);
@@ -346,9 +373,16 @@ class mainWindRect {
         arrow: false,
       });
 
-      this.arrowMesh = new THREE.Mesh(geometry, this.material);
-      this.arrowMesh.name = 'arrow';
-      this.group?.add(this.arrowMesh);
+      arrowMesh = new THREE.Mesh(geometry, this.material);
+      if (deviceType === 'front') {
+        arrowMesh.name = 'arrowFront';
+        this.arrowMeshFront = arrowMesh;
+      } else {
+        arrowMesh.name = 'arrowBack';
+        this.arrowMeshBack = arrowMesh;
+      }
+      this.group?.add(arrowMesh);
+      return arrowMesh;
     };
 
     if (deviceType === 'front') {
@@ -386,11 +420,19 @@ class mainWindRect {
         }
         break;
     }
-    getPathPoint();
+    const arrowMesh = getPathPoint(deviceType);
     if (deviceType === 'front') {
-      if (this.arrowMesh && this.arrowMesh.position.z !== 2.31) this.arrowMesh.position.set(-2.51, 5.51, 13.25);
+      if (arrowMesh && arrowMesh.position.z !== 2.31) arrowMesh.position.set(-2.51, 5.51, 13.25);
     } else {
-      if (this.arrowMesh && this.arrowMesh.position.z !== -12.99) this.arrowMesh.position.set(-2.2, 5.51, -2.8);
+      if (arrowMesh && arrowMesh.position.z !== -12.99) arrowMesh.position.set(-2.2, 5.51, -2.8);
+    }
+  }
+
+  openOrCloseAir(deviceType, flag) {
+    if (deviceType == 'front' && this.arrowMeshFront) {
+      this.arrowMeshFront.visible = flag;
+    } else if (deviceType == 'back' && this.arrowMeshBack) {
+      this.arrowMeshBack.visible = flag;
     }
   }
 
@@ -731,6 +773,36 @@ class mainWindRect {
     }
   }
 
+  openOrCloseWindowSimulation(deviceType, endAngle, maxAngle = 4) {
+    let windowMeshList;
+
+    if (deviceType === 'front') {
+      windowMeshList = this.frontWindowMeshList;
+    }
+    if (deviceType === 'back') {
+      windowMeshList = this.backWindowMeshList;
+    }
+
+    endAngle = ((endAngle / maxAngle) * Math.PI) / 2;
+
+    if (windowMeshList) {
+      const windowAngle = {
+        value: windowMeshList[0].rotation.y,
+      };
+      const duration = Math.abs(endAngle - windowAngle.value) * 5;
+      gsap.to(windowAngle, {
+        value: endAngle,
+        duration: duration,
+        ease: 'none',
+        onUpdate: function () {
+          windowMeshList.forEach((mesh) => {
+            mesh.rotation.y = windowAngle.value;
+          });
+        },
+      });
+    }
+  }
+
   /** 初始化风窗 */
   initWindow() {
     const ztfjGroup = this.group?.getObjectByName('ztfj') as THREE.Group;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.