hongrunxia преди 1 година
родител
ревизия
4760bda500
променени са 54 файла, в които са добавени 4097 реда и са изтрити 368 реда
  1. 7 1
      public/js/config.js
  2. BIN
      public/model/glft/fc/ddFc_2023-06-02.glb
  3. BIN
      public/model/glft/fc/sdFc_2023-06-02.glb
  4. BIN
      public/model/glft/fc/sdFc_2023-10-19.glb
  5. 0 0
      public/model/glft/fm/quickObfurage-door_2023-06-02.glb
  6. 0 0
      public/model/glft/fm/quickObfurage-wall_2023-06-02.glb
  7. 0 0
      public/model/glft/fm/quickObfurage-wire_2023-06-02.glb
  8. BIN
      public/model/glft/jbfj/jbfj-hd_2023-10-19.glb
  9. 1 1
      src/api/sys/vent.ts
  10. 1 1
      src/design/color.less
  11. 10 1
      src/design/vent/color.less
  12. 1 1
      src/design/vent/index.less
  13. 2 2
      src/design/vent/modal.less
  14. 24 1
      src/layouts/default/content/index.vue
  15. 13 6
      src/layouts/default/sider/bottomSideder.vue
  16. 8 1
      src/main.ts
  17. 9 2
      src/utils/threejs/main.worker.ts
  18. 4 2
      src/utils/threejs/useThree.ts
  19. 1 1
      src/views/vent/deviceManager/comment/pointTabel/WorkFacePointTable.vue
  20. 1 0
      src/views/vent/deviceManager/pointTabel/point.api.ts
  21. 18 7
      src/views/vent/monitorManager/alarmMonitor/alarm.data.ts
  22. 22 7
      src/views/vent/monitorManager/alarmMonitor/index.vue
  23. 148 35
      src/views/vent/monitorManager/comment/HistoryTable.vue
  24. 66 0
      src/views/vent/monitorManager/comment/components/PasswordModal.vue
  25. 1 0
      src/views/vent/monitorManager/compressor/index.vue
  26. 31 12
      src/views/vent/monitorManager/deviceMonitor/components/device/index.vue
  27. 456 0
      src/views/vent/monitorManager/deviceMonitor/components/device/modal/mainPath.vue
  28. 14 4
      src/views/vent/monitorManager/deviceMonitor/index.vue
  29. 4 3
      src/views/vent/monitorManager/fanLocalMonitor/index.vue
  30. 164 0
      src/views/vent/monitorManager/gasPumpMonitor/components/DetailModal.vue
  31. 36 26
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue
  32. 3 1
      src/views/vent/monitorManager/gasPumpMonitor/gasPump.threejs.ts
  33. 15 1
      src/views/vent/monitorManager/gasPumpMonitor/gasPump.threejs.under.ts
  34. 11 15
      src/views/vent/monitorManager/gasPumpMonitor/index.vue
  35. 12 4
      src/views/vent/monitorManager/gateMonitor/index.vue
  36. 1 1
      src/views/vent/monitorManager/mainFanMonitor/index.vue
  37. 338 106
      src/views/vent/monitorManager/nitrogen/components/nitrogenHome.vue
  38. 50 1
      src/views/vent/monitorManager/nitrogen/nitrogen.data.ts
  39. 49 29
      src/views/vent/monitorManager/nitrogen/nitrogen.dishang.threejs.ts
  40. 88 44
      src/views/vent/monitorManager/nitrogen/nitrogen.threejs.ts
  41. 32 0
      src/views/vent/monitorManager/obfurage1Monitor/detail.vue
  42. 68 0
      src/views/vent/monitorManager/obfurage1Monitor/gate.api.ts
  43. 331 0
      src/views/vent/monitorManager/obfurage1Monitor/gate.data.ts
  44. 503 0
      src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.three.ts
  45. 185 0
      src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.ts
  46. 597 0
      src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.two.ts
  47. 642 0
      src/views/vent/monitorManager/obfurage1Monitor/index.vue
  48. 66 0
      src/views/vent/monitorManager/obfurage1Monitor/modal.vue
  49. 25 27
      src/views/vent/monitorManager/windowMonitor/dandaoFc.threejs.ts
  50. 10 4
      src/views/vent/monitorManager/windowMonitor/modal.vue
  51. 8 7
      src/views/vent/monitorManager/windowMonitor/shuangdaoFc.threejs.ts
  52. 11 10
      src/views/vent/monitorManager/windrectMonitor/duishe.threejs.ts
  53. 9 4
      src/views/vent/monitorManager/windrectMonitor/index.vue
  54. 1 0
      src/views/vent/monitorManager/windrectMonitor/windrect.threejs.ts

+ 7 - 1
public/js/config.js

@@ -3,6 +3,12 @@ const VUE_APP_URL = {
   webRtcUrl: '//192.168.183.216:8000' // rtsp服务器IP地址
 }
 const History_Type = {
-  type: 'vent', // iot、gb、vent
+  type: 'remote', // remote、vent
+  // type: 'vent', // remote、vent
   deviceType: []
+}
+
+const VENT_PARAM = {
+  // simulatedPassword: '123456'
+  simulatedPassword: ''
 }

BIN
public/model/glft/fc/ddFc_2023-06-02.glb


BIN
public/model/glft/fc/sdFc_2023-06-02.glb


BIN
public/model/glft/fc/sdFc_2023-10-19.glb


+ 0 - 0
public/model/glft/fm/Fm-door_2023-06-02.glb → public/model/glft/fm/quickObfurage-door_2023-06-02.glb


+ 0 - 0
public/model/glft/fm/Fm-wall_2023-06-02.glb → public/model/glft/fm/quickObfurage-wall_2023-06-02.glb


+ 0 - 0
public/model/glft/fm/Fm-wire_2023-06-02.glb → public/model/glft/fm/quickObfurage-wire_2023-06-02.glb


BIN
public/model/glft/jbfj/jbfj-hd_2023-10-19.glb


+ 1 - 1
src/api/sys/vent.ts

@@ -1,6 +1,6 @@
 import { defHttp } from '/@/utils/http/axios';
 enum Api {
-  allTableHeaderColumns = '/safety/ventanalyShowColum/show_colum',
+  allTableHeaderColumns = '/ventanaly-device/safety/ventanalyShowColum/show_colum',
 }
 
 export const getAllTableHeaderColumnsApi = () => {

+ 1 - 1
src/design/color.less

@@ -14,7 +14,7 @@ html {
 
 // @content-bg: #f4f7f9;
 // @content-bg: #03114c;
-@content-bg: #09172c;
+@content-bg: var(--vent-base-color);
 
 // :export {
 //   name: "less";

+ 10 - 1
src/design/vent/color.less

@@ -1,7 +1,16 @@
+// @vent-transparent: #ffffff00;
+// @vent-font-color: #ffffff;
+// @vent-table-hover: #ffffff22;
+// @vent-table-hover: #0dc3ff22;
+// @vent-table-no-hover: #00bfff10;
+// @vent-form-item-boder: #3ad8ff77;
+// @ventSpace: zxm;
+@vent-base-color: #09172c;
+// @vent-base-color: #003c8f;
 @vent-transparent: #ffffff00;
 @vent-font-color: #ffffff;
 @vent-table-hover: #ffffff22;
 @vent-table-hover: #0dc3ff22;
 @vent-table-no-hover: #00bfff10;
 @vent-form-item-boder: #3ad8ff77;
-@ventSpace: zxm;
+@ventSpace: zxm;

+ 1 - 1
src/design/vent/index.less

@@ -4,7 +4,7 @@
 @import './comment.less';
 
 .vent {
-  background-color: #09172c !important;
+  background-color: @vent-base-color !important;
 }
 
 

+ 2 - 2
src/design/vent/modal.less

@@ -14,7 +14,7 @@
     position: absolute;
     left: 0;
     top: 0;
-    background: #09172c;
+    background: @vent-base-color;
   }
   .elementTag{
     pointer-events: none !important;
@@ -367,7 +367,7 @@
   .bottom-tabs-box {
     position: fixed;
     width: 100%;
-    height: 340px;
+    height: 330px;
     bottom: 5px;
     // padding: 0 10px;
     margin: 0px;

+ 24 - 1
src/layouts/default/content/index.vue

@@ -27,6 +27,7 @@
   import { registerApps } from '/@/qiankun';
   import { useGlobSetting } from '/@/hooks/setting';
   import { getActions } from '/@/qiankun/state';
+  import { nextTick } from 'vue';
 
   export default defineComponent({
     name: 'LayoutContent',
@@ -58,10 +59,31 @@
       }
       useContentViewHeight();
 
+      const resetContent = () => {
+        const contentDom = document.getElementById('content')
+        if(contentDom && contentDom.firstChild){
+          (contentDom.firstChild as HTMLElement).style.height = '100%';
+          (contentDom.firstChild as HTMLElement).style.width = '100%';
+        }else{
+           setTimeout(()=> {
+            resetContent()
+           }, 500)
+        }
+      }
+      
       const getShowFullHeader = computed(() => {
         const route = unref(currentRoute);
+        if(route.path.startsWith('/micro-')){
+          nextTick(()=> {
+            setTimeout(() =>{
+              resetContent()  
+            }, 500)
+          })
+         
+        }
         return getShowFullHeaderRef.value && ((route.path.startsWith('/micro-')));
       });
+
       onBeforeMount(() => {});
       onMounted(() => {
         //注册openQianKun
@@ -87,6 +109,7 @@
   });
 </script>
 <style lang="less" scoped>
+  @import '/@/design/vent/color.less';
   @prefix-cls: ~'@{namespace}-layout-content';
   @ventSpace: zxm;
   
@@ -131,7 +154,7 @@
     background-image: url('/@/assets/images/vent/loading-bg.png');
     background-size: cover;
     background-repeat: no-repeat;
-    background-color: #09172C;
+    background-color: @vent-base-color;
   }
 
   .app-loading .app-loading-wrap {

+ 13 - 6
src/layouts/default/sider/bottomSideder.vue

@@ -25,15 +25,15 @@
               v-if="!programMenu.title.startsWith('首页')"
               class="program-menu"
               :class="{ 'program-menu-active': currentParentRoute == programMenu }"
-              @mouseenter="selectMenu(programMenu)"
+              @click="selectMenu(programMenu)"
               >{{ programMenu.title }}</div
             >
           </template>
         </div>
         <div class="setting-group">
-          <SvgIcon class="icon-style" size="18" name="home" @click="go('/micro-vent-3dModal/dashboard/analysis')" />
+          <SvgIcon class="icon-style" size="18" name="home" @click="geHome" />
           <SvgIcon class="icon-style" size="18" name="fixed" />
-          <SvgIcon class="icon-style" size="18" name="enter" />
+          <SvgIcon class="icon-style" size="18" name="enter" @click="closeMenu"/>
           <!-- <SvgIcon class="icon-style" size="18" name="setting" />  
           <SvgIcon class="icon-style" size="18" name="hidden" /> -->
         </div>
@@ -84,6 +84,8 @@
 
           if(route.path.startsWith('/subSysmodal/')) {
             router.replace('/micro-vent-3dModal' + path.path)
+          }else if(path.path == '/dustMonitor'){
+            go('/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=dusting')
           }else{
             go(path.path);
           }
@@ -91,15 +93,18 @@
         }
         isShowMenu.value = 0;
       }
-
+      function geHome() {
+        isShowMenu.value = 0;
+        go('/micro-vent-3dModal/dashboard/analysis')
+      }
       function closeMenu() {
         isShowMenu.value = 0;
-        window.removeEventListener('click', closeMenu);
+        // window.removeEventListener('click', closeMenu);
       };
 
       function openMenu() {
         isShowMenu.value = -1;
-        window.addEventListener('click', closeMenu, true);
+        // window.addEventListener('click', closeMenu, true);
       }
 
       onMounted(async () => {
@@ -111,8 +116,10 @@
         isShowMenu,
         handleMenuClick,
         openMenu,
+        closeMenu,
         selectMenu,
         go,
+        geHome,
         currentParentRoute,
       };
     },

+ 8 - 1
src/main.ts

@@ -24,13 +24,13 @@ import { registerThirdComp } from '/@/settings/registerThirdComp';
 import { useSso } from '/@/hooks/web/useSso';
 import { registerPackages } from '/@/utils/monorepo/registerPackages';
 import { initModalWorker, initTHREE } from '/@/utils/threejs/main.worker';
+import GlobalConfig from './components/config/GlobalConfig.vue';
 
 // 在本地开发中引入的,以提高浏览器响应速度
 if (import.meta.env.DEV) {
   import('ant-design-vue/dist/antd.less');
 }
 
-
 async function bootstrap() {
   // 创建应用实例
   const app = createApp(App);
@@ -75,6 +75,13 @@ async function bootstrap() {
 
   initTHREE();
 
+  app.component('global-config', GlobalConfig);
+  app.provide('globalConfig', {
+    // 你的全局配置
+    simulatedPassword: VENT_PARAM['simulatedPassword'],
+    History_Type: History_Type['type'],
+  });
+
   // 挂载应用
   app.mount('#app', true);
 }

+ 9 - 2
src/utils/threejs/main.worker.ts

@@ -17,9 +17,16 @@ export function initModalWorker() {
     'fm/Fm-wall_2023-09-26.glb',
     'fm/fmThree_2023-07-25.glb',
     'fm/fmThreeBase_2023-07-25.glb',
+
+    'fm/quickObfurage-door_2023-06-02.glb',
+    'fm/quickObfurage-wire_2023-06-02.glb',
+    'fm/quickObfurage-wall_2023-06-02.glb',
+
     'fc/wall_2023-10-11.glb',
-    'fc/ddFc_2023-10-11.glb',
-    'fc/sdFc_2023-10-19.glb',
+    // 'fc/ddFc_2023-10-11.glb',
+    // 'fc/sdFc_2023-10-19.glb',
+    'fc/ddFc_2023-06-02.glb',
+    'fc/sdFc_2023-06-02.glb',
     'cf/lmcf_2023-06-02.glb',
     'cf/lmcfSide_2023-06-02.glb',
     'cf/zdcf_2023-06-02.glb',

+ 4 - 2
src/utils/threejs/useThree.ts

@@ -428,7 +428,9 @@ class UseThree {
   /* 初始动画 */
   startAnimation() {}
 
-  renderAnimationScene() {}
+  renderAnimationScene() {
+    
+  }
 
   animate() {
     if (this.isRender) {
@@ -436,7 +438,7 @@ class UseThree {
       const T = this.clock?.getDelta() || 0;
       this.timeS = this.timeS + T;
       if (this.timeS > this.renderT) {
-        // this.renderAnimationScene();
+        this.renderAnimationScene();
 
         if (this.renderEnabled) {
           this.render();

+ 1 - 1
src/views/vent/deviceManager/comment/pointTabel/WorkFacePointTable.vue

@@ -213,7 +213,7 @@
               },
               {
                 label: '删除',
-                onClick: workRelevanceDeviceDelete.bind(null, { deviceid: record.deviceID, sysid: props.deviceId }, handleSuccess),
+                onClick: workRelevanceDeviceDelete.bind(null, { deviceid: record.deviceId, sysid: props.deviceId }, handleSuccess),
               },
             ];
           }

+ 1 - 0
src/views/vent/deviceManager/pointTabel/point.api.ts

@@ -12,6 +12,7 @@ enum Api {
   exportXls = '/safety/ventanalyMonitorParams/exportXls',
   queryDevice = '/sys/dict/DeviceKind/query',
 }
+
 /**
  * 导出api
  * @param params

+ 18 - 7
src/views/vent/monitorManager/alarmMonitor/alarm.data.ts

@@ -489,7 +489,7 @@ export const colors = [
   // [{ color: 'rgba(46, 144, 165, 1 )' }, { color: 'rgba(46, 144, 165, 0.8 )' }, { color: 'rgba(46, 144, 165, 0.08 )' }],
   // [{ color: 'rgba(46, 144, 165, 1 )' }, { color: 'rgba(46, 144, 165, 0.8 )' }, { color: 'rgba(46, 144, 165, 0.08 )' }],
   // [{ color: 'rgba(46, 144, 165, 1 )' }, { color: 'rgba(46, 144, 165, 0.8 )' }, { color: 'rgba(46, 144, 165, 0.08 )' }],
-  
+
   [{ color: 'rgba(46,144,165, 1 )' }, { color: 'rgba(46,144,165, 0.8 )' }, { color: 'rgba(46, 144, 165, 0.08 )' }],
   [{ color: 'rgba(254,254,53, 1 )' }, { color: 'rgba(254,254,53, 0.8 )' }, { color: 'rgba(254,254,53, 0.08 )' }],
   [{ color: 'rgba(234,179,105, 1 )' }, { color: 'rgba(234,179,105, 0.8 )' }, { color: 'rgba(234,179,105, 0.08 )' }],
@@ -537,47 +537,58 @@ export const fireMonitor = [
   {
     title: '最高温度(℃)',
     code: '',
+    value: '26.7',
   },
   {
     title: 'CO最大值(ppm)',
     code: '',
+    value: '0.0',
   },
   {
-    title: 'CH4最大值(ppm)',
+    title: 'CH4最大值(%)',
     code: '',
+    value: '0.03',
   },
   {
     title: 'C2H4最大值(ppm)',
     code: '',
+    value: '0.0',
   },
   {
-    title: 'CO2最大值(ppm)',
+    title: 'CO2最大值(%)',
     code: '',
+    value: '0.0',
   },
   {
     title: '上隅角O2最小值(%)',
     code: '',
+    value: '18.3',
   },
 ];
 export const fireMonitor1 = [
   {
     title: '最高温度(℃)',
     code: '',
+    value: '98.7',
   },
   {
     title: '火焰传感器',
     code: '',
+    value: '报警',
   },
   {
     title: '烟雾传感器',
     code: '',
+    value: '报警',
   },
   {
     title: '喷淋装置',
     code: '',
+    value: '未接入',
   },
-  {
-    title: 'CO传感器',
-    code: '',
-  },
+  // {
+  //   title: 'CO传感器',
+  //   code: '',
+  //   value: '0.0',
+  // },
 ];

+ 22 - 7
src/views/vent/monitorManager/alarmMonitor/index.vue

@@ -82,7 +82,7 @@
     <div class="center-box">
       <div class="animation-box">
         <canvas class="rain"></canvas>
-        <div class="bottom bottom2">
+        <div class="bottom bottom5">
           <div class="animation1">
             <div class="ball"></div>
             <svg xmlns="http://www.w3.org/2000/svg" width="375.334" height="77.559" viewBox="0 0 375.334 77.559" style="opacity: 0.5;">
@@ -100,7 +100,7 @@
           </svg>
           </div>
           <div class="text-box">
-            <div class="text1">风险</div>
+            <div class="text1">风险</div>
             <div class="text2">风险分析</div>
           </div>
           <div class="icon-animation"></div>
@@ -113,7 +113,7 @@
             </div>
             <div class="item-monitor-box">
               <span class="title">火灾监测</span>
-              <span class="value">正常</span>
+              <span class="value value1">异常</span>
             </div>
           </div>
           <div class="item item2">
@@ -122,7 +122,7 @@
             </div>
             <div class="item-monitor-box">
               <span class="title">设备监测</span>
-              <span class="value value1">异常</span>
+              <span class="value value">正常</span>
             </div>
           </div>
           <div class="item item3">
@@ -165,14 +165,14 @@
           <div class="item">
             <div class="icon"></div>
             <div class="data-box" v-for="(item, index) in fireMonitor" :key="index">
-              <div class="value"> 0.0 </div>
+              <div class="value"> {{ item.value }} </div>
               <div class="title">{{ item.title }}</div>
             </div>
           </div>
           <div class="item item1">
             <div class="icon"></div>
             <div class="data-box" v-for="(item, index) in fireMonitor1" :key="index">
-              <div class="value"> 0.0 </div>
+              <div class="value" :style="{color: item.value == '报警'? '#ff0000': ''}" >{{ item.value }} </div>
               <div class="title">{{ item.title }}</div>
             </div>
           </div>
@@ -812,8 +812,14 @@
           }
           .text-box{
             .text1{
-              color: #df0f00;
+              color: #ff2313;
+              // animation: color-blink 1s infinite;
             }
+            // @keyframes color-blink {  
+            //   0% { color: red; }  
+            //   50% { color: blue; }  
+            //   100% { color: red; }
+            // }  
           }
         }
         
@@ -904,8 +910,17 @@
                 margin-left: 20px;
               }
               .value1{
+                font-size: 16px;
+                font-weight: 800;
+                margin-top: 6px;
                 color: #ff0000;
+                animation: color-blink 1s infinite; 
               }
+              @keyframes color-blink {  
+                0% { color: red; }  
+                50% { color: rgb(198, 0, 0); }  
+                100% { color: rgb(255, 18, 18); }  
+              }  
             }
           }
           .item1{

+ 148 - 35
src/views/vent/monitorManager/comment/HistoryTable.vue

@@ -1,6 +1,11 @@
 <template>
   <div class="history-table">
-    <BasicTable ref="historyTable" @register="registerTable" >
+    <BasicTable v-if="globalConfig.History_Type == 'vent'" ref="historyTable" @register="registerTable" >
+      <template #bodyCell="{ column, record }">
+        <slot name="filterCell" v-bind="{ column, record }"></slot>
+      </template>
+    </BasicTable>
+    <BasicTable v-else ref="historyTable" @register="registerTable" >
       <template #bodyCell="{ column, record }">
         <slot name="filterCell" v-bind="{ column, record }"></slot>
       </template>
@@ -10,22 +15,23 @@
 
 <script lang="ts" name="system-user" setup>
   //ts语法
-  import { watchEffect, ref, watch, defineExpose } from 'vue';
+  import { watchEffect, ref, watch, defineExpose, inject } from 'vue';
   import { FormSchema } from '/@/components/Form/index';
   import { BasicTable } from '/@/components/Table';
   import { useListPage } from '/@/hooks/system/useListPage';
   import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
   import { defHttp } from '/@/utils/http/axios';
   import dayjs from 'dayjs';
+  const globalConfig = inject('globalConfig');
 
   const historyTable = ref();
+  const dataSource = ref([])
+
   const list = (params) => {
-    if(History_Type.type == 'iot') {
-      return defHttp.post({ url: '/ventanaly-device/history/getDataBySystem', params })
-    } else if (History_Type.type == 'gb') {
-      return defHttp.post({ url: '/ventanaly-device/history/getGbHistoryData', params })
-    } else {
+    if(globalConfig.History_Type == 'vent') {
       return defHttp.get({ url: '/safety/ventanalyMonitorData/list', params })
+    } else {
+      return defHttp.post({ url: '/ventanaly-device/history/getHistoryData', params })
     }
   };
   const emit = defineEmits(['change']);
@@ -101,8 +107,114 @@
   })
 
   // 列表页面公共参数、方法
-  const { tableContext } = useListPage({
-    tableProps: {
+  const { tableContext } = useListPage(
+    globalConfig.History_Type == 'vent' ? {
+      tableProps: {
+        api: list,
+        columns: props.columnsType ? columns : (props.columns as any[]),
+        canResize: true,
+        showTableSetting: false,
+        showActionColumn: false,
+        bordered: false,
+        size: 'small',
+        scroll: tableScroll,
+        formConfig: {
+          labelAlign: 'left',
+          showAdvancedButton: false,
+          // autoAdvancedCol: 2,
+
+          baseColProps: {
+            // offset: 0.5,
+            xs: 24,
+            sm: 24,
+            md: 24,
+            lg: 9,
+            xl: 7,
+            xxl: 4,
+          },
+          schemas: props.formSchemas.length > 0 ? props.formSchemas : [
+            {
+              label: '查询日期',
+              field: 'tData',
+              component: 'DatePicker',
+              defaultValue: dayjs(),
+              componentProps: {
+                valueFormat: 'YYYY-MM-DD',
+              },
+            },
+            {
+              label: '时间区间',
+              field: 'tickectDate',
+              component: 'TimeRangePicker',
+              componentProps: {
+                placeholder: ['开始时间', '结束时间'],
+                valueFormat: 'HH:mm:ss',
+              },
+            },
+            {
+              label: '查询设备',
+              field: 'gdeviceid',
+              component: 'ApiSelect',
+              required: true,
+              componentProps: {
+                api: () => defHttp.get({ url: '/safety/ventanalyManageSystem/linkdevicelist', params: { sysId: props.sysId, deviceType: props.deviceType } }),
+                // resultField: 'result',
+                labelField: 'strinstallpos',
+                valueField: 'id',
+                // numberToString: true,
+              },
+            },
+            {
+              label: '间隔时间',
+              field: 'skip',
+              component: 'Select',
+              componentProps: {
+                options: [
+                  {
+                    label: '5秒',
+                    value: '1',
+                  },
+                  {
+                    label: '10秒',
+                    value: '2',
+                  },
+                  {
+                    label: '1分钟',
+                    value: '3',
+                  },
+                  {
+                    label: '5分钟',
+                    value: '4',
+                  },
+                  {
+                    label: '10分钟',
+                    value: '5',
+                  },
+                ],
+              },
+            },
+          ],
+          fieldMapToTime: [['tickectDate', ['ttime_begin', 'ttime_end'], '']],
+        },
+        fetchSetting: {
+          listField: globalConfig.History_Type == 'iot'? 'records': globalConfig.History_Type == 'records'? '': 'datalist.records',
+          totalField: globalConfig.History_Type == 'iot' ? 'total' : globalConfig.History_Type == 'total' ? 'total' : 'datalist.total',
+        },
+        beforeFetch(params) {
+          params.strtype = props.deviceType + '*';
+          if(props.sysId){
+            params.sysId = props.sysId;
+          }
+        },
+        afterFetch(resultItems) {
+          resultItems.map((item) => {
+            Object.assign(item, item['readData']);
+          });
+          return resultItems;
+        },
+      },
+    }: {
+      tableProps: {
       api: list,
       columns: props.columnsType ? columns : (props.columns as any[]),
       canResize: true,
@@ -132,21 +244,12 @@
             component: 'DatePicker',
             defaultValue: dayjs(),
             componentProps: {
-              valueFormat: 'YYYY-MM-DD',
-            },
-          },
-          {
-            label: '时间区间',
-            field: 'tickectDate',
-            component: 'TimeRangePicker',
-            componentProps: {
-              placeholder: ['开始时间', '结束时间'],
-              valueFormat: 'HH:mm:ss',
+              valueFormat: 'YYYY-MM-DD hh:mm:ss',
             },
           },
           {
             label: '查询设备',
-            field: 'gdeviceid',
+            field: 'deviceId',
             component: 'ApiSelect',
             required: true,
             componentProps: {
@@ -159,8 +262,9 @@
           },
           {
             label: '间隔时间',
-            field: 'skip',
+            field: 'interval',
             component: 'Select',
+            defaultValue: 1,
             componentProps: {
               options: [
                 {
@@ -176,37 +280,46 @@
                   value: '3',
                 },
                 {
-                  label: '5分钟',
+                  label: '10分钟',
                   value: '4',
                 },
                 {
-                  label: '10分钟',
+                  label: '20分钟',
                   value: '5',
                 },
               ],
             },
           },
         ],
-        fieldMapToTime: [['tickectDate', ['ttime_begin', 'ttime_end'], '']],
-      },
-      fetchSetting: {
-        listField: History_Type.type == 'iot'? 'records': History_Type.type == 'records'? '': 'datalist.records',
-        totalField: History_Type.type == 'iot' ? 'total' : History_Type.type == 'total' ? 'total' : 'datalist.total',
+        fieldMapToTime: [['tData', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss']],
       },
+
       beforeFetch(params) {
         params.strtype = props.deviceType + '*';
-        if(props.sysId){
+        if (props.sysId) {
           params.sysId = props.sysId;
         }
+        if(params.interval){
+          params.interval = params.interval+'m'
+        }else{
+          params.interval = '1m'
+        }
       },
-      afterFetch(resultItems) {
-        resultItems.map((item) => {
-          Object.assign(item, item['readData']);
-        });
-        return resultItems;
+      afterFetch(result) {
+        let dataSource = []
+        if(result['records'] && result['records'].length >0){
+           dataSource = result['records'].map((item) => {
+            Object.assign(item, result['deviceId'], result['deviceName'], result['devicePos'] );
+          })
+        }
+        return dataSource;
       },
     },
-  });
+    } 
+  );
+
+
+
   //注册table数据
   const [registerTable, { getDataSource, reload, setLoading, getForm }] = tableContext;
 

+ 66 - 0
src/views/vent/monitorManager/comment/components/PasswordModal.vue

@@ -0,0 +1,66 @@
+<template>
+  <a-modal v-model:visible="visible" :title="title" @ok="handleOk" @cancel="handleCancel">
+    <div class="modal-container">
+      <div class="vent-flex-row">
+        <ExclamationCircleFilled style="color: #ffb700; font-size: 30px" />
+        <div class="warning-text">您是否要进行{{ title }}操作?</div>
+      </div>
+      <div class="vent-flex-row input-box">
+        <div class="label">操作密码:</div>
+        <a-input size="small" type="password" v-model:value="passWord" />
+      </div>
+    </div>
+  </a-modal>
+</template>
+<script setup lang="ts">
+  import { watch, ref } from 'vue';
+  import { ExclamationCircleFilled } from '@ant-design/icons-vue';
+
+  const props = defineProps({
+    modalIsShow: {
+      type: Boolean,
+      default: false,
+    },
+    modalTitle: {
+      type: String,
+      default: '',
+    },
+    modalType: {
+      type: String,
+      default: '',
+    },
+  });
+
+  const emit = defineEmits(['handleOk', 'handleCancel']);
+
+  const visible = ref<Boolean>(false);
+  const title = ref<String>('');
+  const type = ref<String>('');
+  const passWord = ref('');
+
+  watch([() => props.modalIsShow, () => props.modalTitle, () => props.modalType], ([newVal, newModalTitle, newModalType]) => {
+    visible.value = newVal;
+    if (newModalTitle) title.value = newModalTitle;
+    if (newModalType) type.value = newModalType;
+    passWord.value = '';
+  });
+
+  function handleOk() {
+    //
+    emit('handleOk', passWord.value, type.value);
+  }
+  function handleCancel() {
+    //
+    emit('handleCancel');
+  }
+</script>
+<style scoped lang="less">
+  @ventSpace: zxm;
+
+  .label {
+    width: 80px;
+  }
+  .@{ventSpace}-input {
+    width: 150px;
+  }
+</style>

+ 1 - 0
src/views/vent/monitorManager/compressor/index.vue

@@ -113,6 +113,7 @@ function changeModalType(currentData) {
 onMounted(async () => {
   if (currentRoute.value['query'] && currentRoute.value['query']['id']) optionValue.value = currentRoute.value['query']['id']
   await getSysDataSource()
+  getSelectRow(optionValue.value)
 });
 
 onUnmounted(() => {

+ 31 - 12
src/views/vent/monitorManager/deviceMonitor/components/device/index.vue

@@ -82,7 +82,7 @@
                 design-scope="device_monitor" :isShowPagination="false" :isShowActionColumn="true" title="设备监测"
                 :scroll="scroll">
                 <template #action="{ record }">
-                  <TableAction :actions="deviceType !== 'nitrogen' ?[
+                  <TableAction :actions="!deviceType.startsWith('nitrogen') ?[
                     {
                       label: '详情',
                       onClick: goDetail.bind(null, record),
@@ -188,7 +188,7 @@
         <!-- </dv-border-box8> -->
       </div>
     </div>
-
+    <mainPath v-if="deviceType == 'majorpath'" :line-list="dataSource" style="width: 300px; height: 300px; position: absolute; left: 250px;"/>
     <component v-if="modalVisible" :is="currentModal" v-model:visible="modalVisible" :dataSource="dataSource"
       :activeID="activeID" />
   </div>
@@ -213,6 +213,7 @@ import { getActions } from '/@/qiankun/state';
 import { useRouter } from 'vue-router';
 import { setDivHeight } from '/@/utils/event';
 import { majorColumns } from  './device.data'
+import mainPath from './modal/mainPath.vue'
 // import { BorderBox8 as DvBorderBox8 } from '@kjgl77/datav-vue3';
 
 
@@ -381,16 +382,31 @@ async function getDataSource() {
   } else {
     let res = null
     if(systemID.value){
-      res = await list({ devicetype: 'sys', types: deviceType.value})
+      res = await list({ devicetype: 'sys', types: deviceType.value, systemID: systemID.value })
     }else{
       res = await list({ devicetype: deviceType.value, pagetype: 'normal' })
     }
-    if (res && res.msgTxt && res.msgTxt[0]) {
-      dataSource.value = res.msgTxt[0].datalist || [];
-      dataSource.value.filter((data: any) => {
-        const readData = data.readData;
-        return Object.assign(data, readData);
-      });
+    if (res && res.msgTxt) {
+      const result = res.msgTxt;
+      result.forEach(item => {
+        const data = item['datalist'].filter((data: any) => {
+          const readData = data.readData;
+          return Object.assign(data, readData);
+        })
+        if (item.type != 'sys') {
+          if (item.type === 'majorpath') {
+            // deviceArr.unshift({ deviceType: item.type, deviceName: item['typeName'], datalist: item['datalist'][0]['paths'] })
+            dataSource.value = item['datalist'][0]['paths']
+          } else {
+            dataSource.value = data
+          }
+        }
+      })
+      // dataSource.value = res.msgTxt[0].datalist || [];
+      // dataSource.value.filter((data: any) => {
+      //   const readData = data.readData;
+      //   return Object.assign(data, readData);
+      // });
     }
   }
 }
@@ -400,7 +416,6 @@ function goLocation(record) {
 }
 
 function goDetail(record?) {
-
   if (record) {
     activeID.value = record.deviceID
     if (deviceType.value.startsWith('fiber')) {
@@ -439,6 +454,9 @@ function goDetail(record?) {
     } else if (deviceType.value.indexOf("safetymonitor") != -1) {
       const newPage = router.resolve({ path: '/monitorChannel/device-monitor/safetymonitor', query: { id: activeID.value } })
       window.open(newPage.href, '_blank')
+    } else if (deviceType.value.indexOf("pump") != -1) {
+      const newPage = router.resolve({ path: '/monitorChannel/gasPump-home', query: { id: activeID.value } })
+      window.open(newPage.href, '_blank')
     } else {
       message.info('待开发。。。')
     }
@@ -461,7 +479,7 @@ function goDetail(record?) {
     } else if (systemType.value.indexOf("sys_surface_junya") != -1) {
       const newPage = router.resolve({ path: '/monitorChannel/balancePress-home', query: { id: systemID.value } })
       window.open(newPage.href, '_blank')
-    } else if (systemType.value.indexOf("pump_under") != -1 || systemType.value.indexOf("pump_over") != -1) {
+    }  else if (systemType.value.indexOf("sys_nitrogen") != -1) {
       const newPage = router.resolve({ path: '/compressor-home', query: { id: systemID.value } })
       window.open(newPage.href, '_blank')
     } else {
@@ -552,6 +570,7 @@ onMounted(async () => {
   await getDeviceType()
 
   const pageObj = props.pageData
+  if(!pageObj) return 
   if (pageObj.deviceid) {
     findTreeDataValue({ deviceid: pageObj.deviceid })
   } else {
@@ -905,7 +924,7 @@ onUnmounted(() => {
 .bottom-tabs-box {
 
   // position: relative;
-  // height: 330px !important;
+
   .tabs-box {
     width: calc(100% - 12px) !important;
     bottom: 3px !important;

+ 456 - 0
src/views/vent/monitorManager/deviceMonitor/components/device/modal/mainPath.vue

@@ -0,0 +1,456 @@
+<template>
+  <div class="windLine">
+    <!-- <div class="title-top" @click="getDetail">关键路线通风</div>
+    <div class="toggle-search">
+      <i class="icon-search">
+        <SvgIcon class="icon" size="14" name="toggle" />
+      </i>
+      <a-select v-model:value="searchValue" style="width: 180px; margin-right: 10px" :options="lineTypeList"
+        aria-placeholder="请选择" @change="changeSelect" />
+    </div> -->
+    <div class="line-echart">
+      <div class="line" ref="majorpath"></div>
+      <div class="pic">
+        <!-- <img src="../../../../../assets/images/home-container/pie.png" alt="" /> -->
+      </div>
+      <div class="percent">
+        <div class="percent-box">
+          <span>{{ percentF }}</span>
+          <span class="dw">%</span>
+        </div>
+        <div class="percent-box">
+          <span>{{ percentT }}</span>
+          <span class="dw">%</span>
+        </div>
+        <div class="percent-box">
+          <span>{{ percentE }}</span>
+          <span class="dw">%</span>
+        </div>
+      </div>
+    </div>
+    <div class="line-card">
+      <div class="card-item" v-for="(item, index) in tabList" :key="index">
+        <div class="item-s">
+          <div class="item-label">{{ item.name }}</div>
+          <div class="item-val">{{ item.val }}</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted, nextTick, defineProps, watch } from 'vue';
+import { SvgIcon } from '/@/components/Icon';
+import * as echarts from 'echarts';
+const emit = defineEmits(['goDetail'])
+
+let props = defineProps({
+  lineList: Array,
+});
+
+//获取dom节点
+let majorpath = ref<any>();
+let lineData = reactive<any[]>([]);
+let searchValue = ref('');
+const lineTypeList = reactive<any[]>([]);
+
+let echartData = reactive<any[]>([
+  { name: '进风区', value: 0 },
+  { name: '用风区', value: 0 },
+  { name: '回风区', value: 0 },
+]);
+let xData = reactive<any[]>([]);
+let yData = reactive<any[]>([]);
+let percentE = ref<any>(0);
+let percentF = ref<any>(0);
+let percentT = ref<any>(0);
+
+let tabList = reactive<any[]>([
+  { name: '总风量(m³/min)', val: 0 },
+  { name: '总阻力(Pa)', val: 0 },
+  { name: '等积孔(m²)', val: 0 },
+]);
+//跳转详情
+function getDetail() {
+  console.log('跳转详情');
+  emit('goDetail', 'line')
+}
+//选项切换
+function changeSelect(val) {
+  switch (val) {
+    case '1号回风斜井':
+      // echartData[0].value = lineData[0].majorpath.drag_1;
+      // echartData[1].value = lineData[0].majorpath.drag_2;
+      // echartData[2].value = lineData[0].majorpath.drag_3;
+      // tabList[0].val = lineData[0].majorpath.drag_total;
+      // tabList[1].val = lineData[0].majorpath.m3_total;
+      echartData[0].value = Math.floor(Math.random() * (629 - 620 + 1)) + 620;
+      echartData[1].value = Math.floor(Math.random() * (949 - 940 + 1)) + 940;
+      echartData[2].value = Math.floor(Math.random() * (855 - 850 + 1)) + 850;
+      tabList[0].val = Math.floor(Math.random() * (10700 - 10600 + 1)) + 10600;
+      tabList[1].val = Math.floor(Math.random() * (2433 - 2423 + 1)) + 2423;
+      tabList[2].val = 0.56;
+      percentF.value = ((echartData[0].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      percentT.value = ((echartData[1].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      percentE.value = ((echartData[2].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      getOption();
+      break;
+    case '2号回风立井':
+      // echartData[0].value = lineData[1].majorpath.drag_1;
+      // echartData[1].value = lineData[1].majorpath.drag_2;
+      // echartData[2].value = lineData[1].majorpath.drag_3;
+      // tabList[0].val = lineData[1].majorpath.drag_total;
+      // tabList[1].val = lineData[1].majorpath.m3_total;
+      echartData[0].value = Math.floor(Math.random() * (830 - 820 + 1)) + 820;
+      echartData[1].value = Math.floor(Math.random() * (620 - 600 + 1)) + 600;
+      echartData[2].value = Math.floor(Math.random() * (860 - 800 + 1)) + 800;
+      tabList[0].val = Math.floor(Math.random() * (10100 - 10000 + 1)) + 10000;
+      tabList[1].val = Math.floor(Math.random() * (2310 - 2210 + 1)) + 2210;
+      tabList[2].val = 0.78;
+      percentF.value = ((echartData[0].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      percentT.value = ((echartData[1].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      percentE.value = ((echartData[2].value / (echartData[0].value + echartData[1].value + echartData[2].value)) * 100).toFixed(2);
+      getOption();
+      break;
+  }
+}
+
+function getOption() {
+  nextTick(() => {
+    function deepCopy(obj) {
+      if (typeof obj !== 'object') {
+        return obj;
+      }
+      var newobj = {};
+      for (var attr in obj) {
+        newobj[attr] = obj[attr];
+      }
+      return newobj;
+    }
+    echartData.map((a, b) => {
+      xData.push(a.name);
+      yData.push(a.value);
+    });
+    var startColor = ['rgba(255, 224, 28,.6)', 'rgba(31, 248, 251,.6)', 'rgba(154, 255, 168,.6)'];
+    var borderStartColor = ['#ffe01c', '#1ff8fb', '#9affa8'];
+    var RealData = [];
+    var borderData = [];
+    echartData.map((item, index) => {
+      var newobj = deepCopy(item);
+      var newobj1 = deepCopy(item);
+      RealData.push(newobj);
+      borderData.push(newobj1);
+    });
+    RealData.map((item, index) => {
+      item.itemStyle = {
+        normal: {
+          color: startColor[index],
+        },
+      };
+    });
+    borderData.map((item, index) => {
+      item.itemStyle = {
+        normal: {
+          color: borderStartColor[index],
+        },
+      };
+    });
+    const myChart = echarts.init(majorpath.value);
+
+    let option = {
+      legend: [
+        {
+          // orient: 'vertical',
+          x: '50%',
+          y: '12%',
+          itemWidth: 10,
+          itemHeight: 10,
+          align: 'left',
+          textStyle: {
+            fontSize: 14,
+            color: '#b3b8cc',
+          },
+          data: ['进风区'],
+        },
+        {
+          // orient: 'vertical',
+          x: '50%',
+          y: '42%',
+          itemWidth: 10,
+          itemHeight: 10,
+          align: 'left',
+          textStyle: {
+            fontSize: 14,
+            color: '#b3b8cc',
+          },
+          data: ['用风区'],
+        },
+        {
+          // orient: 'vertical',
+          x: '50%',
+          y: '70%',
+          itemWidth: 10,
+          itemHeight: 10,
+          align: 'left',
+          textStyle: {
+            fontSize: 14,
+            color: '#b3b8cc',
+          },
+          data: ['回风区'],
+        },
+      ],
+      tooltip: {
+        formatter: '{b}:{c}',
+      },
+      series: [
+        // 主要展示层的
+        {
+          radius: ['40%', '80%'],
+          center: ['25%', '50%'],
+          type: 'pie',
+          z: 10,
+          label: {
+            normal: {
+              show: false,
+            },
+            emphasis: {
+              show: false,
+            },
+          },
+          labelLine: {
+            normal: {
+              show: false,
+            },
+            emphasis: {
+              show: false,
+            },
+          },
+          itemStyle: {
+            normal: {
+              borderWidth: 5,
+              borderColor: 'rgba(2, 39, 115)',
+            },
+          },
+          data: RealData,
+        },
+        // 边框的设置
+        {
+          radius: ['45%', '52%'],
+          center: ['25%', '50%'],
+          type: 'pie',
+          z: 5,
+          label: {
+            normal: {
+              show: false,
+            },
+            emphasis: {
+              show: false,
+            },
+          },
+          labelLine: {
+            normal: {
+              show: false,
+            },
+            emphasis: {
+              show: false,
+            },
+          },
+
+          animation: false,
+          tooltip: {
+            show: false,
+          },
+          data: borderData,
+        },
+      ],
+    };
+    myChart.setOption(option);
+    window.onresize = function () {
+      myChart.resize();
+    };
+  });
+}
+
+watch(
+  () => props.lineList,
+  (val) => {
+    changeSelect('1号回风斜井');
+  },
+  {
+    deep: true,
+  }
+);
+
+onMounted(() => { });
+</script>
+
+<style lang="less" scoped>
+@font-face {
+  font-family: 'douyuFont';
+  src: url('../../../../../assets/font/douyuFont.otf');
+}
+
+.windLine {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  .title-top {
+    position: absolute;
+    top: 9px;
+    left: 46px;
+    color: #fff;
+    font-size: 16px;
+    font-family: 'douyuFont';
+    cursor: pointer;
+
+    &:hover {
+      color: #66ffff;
+    }
+  }
+
+  .toggle-search {
+    position: absolute;
+    left: 9px;
+    top: 37px;
+    display: flex;
+
+    .icon-search {
+      position: absolute;
+      top: 50%;
+      left: 5px;
+      transform: translate(0%, -50%);
+    }
+  }
+
+  .line-echart {
+    position: absolute;
+    top: 66px;
+    left: 0;
+    width: 100%;
+    height: 120px;
+
+    .line {
+      width: 100%;
+      height: 100%;
+    }
+
+    .pic {
+      height: 100%;
+      position: absolute;
+      left: 45%;
+      top: 0;
+      display: flex;
+      align-items: center;
+
+      img {
+        height: 60%;
+      }
+    }
+
+    .percent {
+      position: absolute;
+      left: 75%;
+      top: 0;
+      width: 45px;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      // justify-content: space-between;
+      align-items: center;
+
+      .percent-box {
+        font-size: 14px;
+
+        // color: #b3b8cc;
+        &:nth-child(1) {
+          position: absolute;
+          top: 12%;
+          color: #ffe01c;
+        }
+
+        &:nth-child(2) {
+          position: absolute;
+          top: 42%;
+          color: #1ff8fb;
+        }
+
+        &:nth-child(3) {
+          position: absolute;
+          top: 70%;
+          color: #9affa8;
+        }
+
+        .dw {
+          color: #b3b8cc;
+          margin-left: 5px;
+        }
+      }
+    }
+  }
+
+  .line-card {
+    position: absolute;
+    top: 186px;
+    left: 0;
+    width: 100%;
+    height: calc(100% - 186px);
+    padding: 0px 15px 15px 15px;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+
+    .card-item {
+      display: flex;
+      flex: 1;
+      justify-content: center;
+      align-items: center;
+      height: 100%;
+
+      .item-s {
+        position: relative;
+        width: 105px;
+        height: 58px;
+        margin-top: 20px;
+        background: url('../../../../../assets/images/home-container/line-val.png') no-repeat;
+        background-size: 100% 90%;
+
+        .item-label {
+          width: 100%;
+          text-align: center;
+          color: #b3b8cc;
+          font-size: 12px;
+        }
+
+        .item-val {
+          position: absolute;
+          left: 50%;
+          top: 26px;
+          font-size: 14px;
+          font-family: 'douyuFont';
+          color: #fff;
+          transform: translate(-50%, 0);
+        }
+      }
+    }
+  }
+}
+
+:deep .zxm-select-selector {
+  width: 100%;
+  height: 30px !important;
+  padding: 0 11px 0px 25px !important;
+  background-color: rgba(8, 148, 255, 0.3) !important;
+  border: 1px solid #1d80da !important;
+}
+
+:deep .zxm-select-selection-item {
+  color: #fff !important;
+  line-height: 28px !important;
+}
+
+:deep .zxm-select-arrow {
+  color: #fff !important;
+}</style>

+ 14 - 4
src/views/vent/monitorManager/deviceMonitor/index.vue

@@ -6,7 +6,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onUnmounted, onMounted } from 'vue';
+import { ref, onUnmounted, onMounted, nextTick } from 'vue';
 import DeviceVue from './components/device/index.vue';
 import Network from './components/network/index.vue'
 import { getActions } from '/@/qiankun/state';
@@ -19,7 +19,7 @@ const DeviceRef = ref(null)
 const NetworkRef = ref(null)
 
 const routerParam = ref('home')
-const pageData = ref(null)
+const pageData = ref({})
 const pageResult = ref({})
 
 const changePageType = (pageType) => {
@@ -29,12 +29,17 @@ const changePageType = (pageType) => {
 }
 
 onMounted(() => {
-  const {type} = route.query
+  const {type, deviceType } = route.query
   if(type === 'network'){
     routerParam.value = 'network'
     actions.setGlobalState({ pageObj: { pageType: 'network' } });
-  }else{
+  }else if(type === 'home'){
     actions.setGlobalState({ pageObj: { pageType: 'home' } });
+  }else if(type === 'tunMonitor') {
+    if(deviceType){
+      pageData.value = { pageType: deviceType }
+      actions.setGlobalState({ pageObj: { pageType: deviceType } });
+    }
   }
   actions.onGlobalStateChange((newState) => {
     for (const key in newState) {
@@ -48,6 +53,11 @@ onMounted(() => {
             pageResult.value = pageObj.timesolution
           }
         }
+      } else if (key === 'isMounted' && newState[key]) {
+        if(deviceType){
+          console.log('qqqqqqqqqqqqqqqqq', newState)
+          // actions.setGlobalState({ pageObj: { pageType: 'tunMonitor', deviceType } });
+        }
       }
     }
   })

+ 4 - 3
src/views/vent/monitorManager/fanLocalMonitor/index.vue

@@ -305,7 +305,7 @@
       </div> -->
       <!-- 启动或停止 -->
       <div class="" v-if="modalType == 'startSmoke'"> </div>
-      <div class="vent-flex-row input-box">
+      <div v-if="!globalConfig?.simulatedPassword" class="vent-flex-row input-box">
         <div class="label">操作密码:</div>
         <a-input size="small" type="password" v-model:value="passWord" />
       </div>
@@ -316,7 +316,7 @@
 
 <script setup lang="ts">
   import { ExclamationCircleFilled, SendOutlined } from '@ant-design/icons-vue';
-  import { onBeforeMount, ref, computed, onMounted, nextTick, toRaw, reactive, onUnmounted } from 'vue';
+  import { onBeforeMount, ref, computed, onMounted, nextTick, toRaw, reactive, onUnmounted, inject } from 'vue';
   import BarSingle from '../../../../components/chart/BarSingle.vue';
   import GroupMonitorTable from '../comment/GroupMonitorTable.vue';
   import HistoryTable from '../comment/HistoryTable.vue';
@@ -336,7 +336,8 @@
   import { SvgIcon } from '/@/components/Icon';
   import { useRouter } from 'vue-router';
   import { useModal } from '/@/components/Modal';
-import { message } from 'ant-design-vue';
+
+  const globalConfig = inject('globalConfig');
 
   const [registerModal, { openModal, closeModal }] = useModal();
   const { currentRoute } = useRouter();

+ 164 - 0
src/views/vent/monitorManager/gasPumpMonitor/components/DetailModal.vue

@@ -0,0 +1,164 @@
+<template>
+  <BasicModal @register="register" title="设备控制" width="700px" v-bind="$attrs" @ok="onSubmit" @cancel="onSubmit">
+    <div>
+      <div class="password-box">
+        <div class="vent-flex-row">
+          <ExclamationCircleFilled style="color: #ffb700; font-size: 30px" />
+          <div class="warning-text">请先输入密码再进行操作!</div>
+        </div>
+        <div class="vent-flex-row input-box">
+          <div class="label">操作密码:</div>
+          <a-input size="small" type="password" v-model:value="passWord" />
+        </div>
+      </div>
+      <div class="">
+         <a-divider class="divider">公共设备控制</a-divider>
+        <div>
+          <div v-for="(pumpType, index) in publicPumpCtrlType" :key="index">
+            <div class="device-group"  v-for="key in 2" :key="key">
+              <div class="title">#{{ key }}{{ pumpType.title }}:</div>
+              <div class="btn-group">
+                <a-button class="btn" v-for="(pump, i) in PumpCtrlItems" :key="i" type="primary" @click="handlerFn(`${pumpType.code}${key}${pump.code}`)">#{{ key }}{{ pumpType.title }}{{ pump.title }}</a-button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="">
+        <a-divider class="divider">单动控制</a-divider>
+        <div class="parameter-title group-parameter-title"><SvgIcon class="icon" size="14" name="pulp-title"/><span>泵站控制</span></div>
+        <div class="vent-margin-b-10">
+          <div v-for="(pumpType, index) in pumpCtrlType" :key="index">
+            <div class="device-group"  v-for="key in 2" :key="key">
+              <div class="title">#{{ key }}{{ pumpType.title }}:</div>
+              <div class="btn-group">
+                <a-button class="btn" v-for="(pump, i) in PumpCtrlItems" :key="i" type="primary" @click="handlerFn(`${pumpType.code}${key}${pump.code}`)">#{{ key }}{{ pumpType.title }}{{ pump.title }}</a-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="parameter-title group-parameter-title"><SvgIcon class="icon" size="14" name="pulp-title"/><span>阀门控制</span></div>
+        <div  v-for="key in 2" :key="key">
+          <div class="device-group" v-for="(valveType, index) in valveCtrlType" :key="index">
+            <div class="title">#{{ key }}{{ valveType.title }}:</div>
+            <div class="btn-group">
+              <a-button class="btn" v-for="(valve, i) in valveCtrl" :key="i" type="primary" @click="handlerFn(`CentrifugalPump${key}_${valveType.code}${valve.code}`)">#{{ key }}{{ valveType.title }}{{ valve.title }}</a-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+
+import { onMounted, ref, defineEmits, onUnmounted } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { pumpCtrlType, valveCtrlType, valveCtrl, PumpCtrlItems, publicPumpCtrlType } from '../gasPump.data'
+import { SvgIcon } from '/@/components/Icon'
+import { message } from 'ant-design-vue';
+import { ExclamationCircleFilled } from '@ant-design/icons-vue';
+import { deviceControlApi } from '/@/api/vent/index';
+
+const emit = defineEmits(['close', 'register'])
+const props = defineProps({
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceId: {
+    type: String,
+    required: true,
+  }
+})
+
+const passWord = ref('')
+// 注册 modal
+const [register, { closeModal }] = useModalInner(() => {
+  passWord.value = ''
+});
+
+async function onSubmit() {
+  emit('close')
+  closeModal();
+}
+
+function handlerFn(paramcode) {
+  if (!passWord.value) {
+    message.warning('请先输入密码!!!');
+    return;
+  }
+  let value = null
+  const data = {
+    deviceid: props.deviceId,
+    devicetype: props.deviceType,
+    paramcode: paramcode,
+    password: passWord.value,
+    value: value,
+  };
+  deviceControlApi(data).then((res) => {
+    message.success('控制成功成功!');
+  }).catch((err) => {
+    // message.success('控制异常');
+  });
+}
+
+onMounted(async () => {
+
+});
+onUnmounted(() => {
+
+});
+</script>
+<style scoped lang="less">
+
+  @import '/@/design/vent/modal.less';
+  @import '../../comment/less/workFace.less';
+   @ventSpace: zxm;
+  
+  
+  .@{ventSpace}-input {
+    width: 150px;
+  }
+  .password-box{
+    .warning-text{
+      font-size: 24px;
+      margin-left: 8px;
+    }
+    .input-box{
+      margin-top: 10px;
+      margin-left: 40px;
+      .label {
+        width: 80px;
+        color: #73e8fe;
+      }
+    }
+    
+  }
+  
+  .btn-group{
+    margin-bottom: 8px;
+    .btn-item{
+      width: calc(50% - 16px);
+      margin: 0 4px;
+    }
+  }
+  .divider{
+    color: #fff;
+    border-color: #73e8fe;
+  }
+  .parameter-title{
+    margin-bottom: 15px;
+  }
+  .device-group{
+    display: flex;
+    align-items: center;
+    .title{
+      width: 100px;
+      color: #73e8fe;
+    }
+    .btn{
+      margin: 0 5px;
+    }
+  }
+</style>

+ 36 - 26
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue

@@ -2,7 +2,11 @@
   <!-- <a-spin tip="Loading..." :spinning="loading">
     
   </a-spin> -->
-
+  <div id="FlowSensor" class="FlowSensor-box" style="position: absolute; z-index: -1;" v-if="selectData.FlowSensor_InputFlux">
+    <div ref="elementContent" class="elementContent" >
+      <p style="color: #50c8fc;"><span class="data-title">抽采泵流量(m³):</span>{{ formatNum(selectData.FlowSensor_InputFlux) }}</p>
+    </div>
+  </div>
   <div class="monitor-container">
     <div class="lr left-box">
       <div class="left-container">
@@ -23,7 +27,7 @@
                       </template>
                       <template v-else>
                         <div class="value">
-                          <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('CentrifugalPump', `CentrifugalPump${key}`)] == '1', 'signal-round-gry': !selectData[item.code.replace('CentrifugalPump', `CentrifugalPump${key}`)] }"></span>
+                          <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('CentrifugalPump', `CentrifugalPump${key}`)] == '1', 'signal-round-gry': selectData[item.code.replace('CentrifugalPump', `CentrifugalPump${key}`)] == '0' }"></span>
                         </div>
                       </template>
                     </template>
@@ -41,15 +45,18 @@
                 <div class="parameter-title group-parameter-title"><SvgIcon class="icon" size="14" name="pulp-title"/><span>{{ key }}#注水泵</span></div>
                 <div class="input-box">
                   <div v-for="(item, index) in waterPumpData" class="input-item" :key="index">
-                    <div class="title">{{ item.title }}:</div>
-                    <template v-if="item.type !== 'sign'">
-                      <div class="value">{{ selectData && selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)] ? formatNum(selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)]) : '-' }}</div>
-                    </template>
-                    <template v-else>
-                      <div class="value">
-                        <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)], 'signal-round-gry': !selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)] }"></span>
-                      </div>
+                    <template v-if="selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)] != undefined">
+                      <div class="title">{{ item.title }}:</div>
+                      <template v-if="item.type !== 'sign'">
+                        <div class="value">{{ selectData && selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)] ? formatNum(selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)]) : '-' }}</div>
+                      </template>
+                      <template v-else>
+                        <div class="value">
+                          <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)], 'signal-round-gry': selectData[item.code.replace('WaterfloodPump', `WaterfloodPump${key}`)]=='0' }"></span>
+                        </div>
+                      </template>
                     </template>
+
                   </div>
                 </div>
               </div>
@@ -64,14 +71,16 @@
                 <div class="parameter-title group-parameter-title"><SvgIcon class="icon" size="14" name="pulp-title"/><span>{{ key }}#排水泵</span></div>
                 <div class="input-box">
                   <div v-for="(item, index) in dewateringPumpData" class="input-item" :key="index">
-                    <div class="title">{{ item.title }}:</div>
-                    <template v-if="item.type !== 'sign'">
-                      <div class="value">{{ selectData && selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)] ? formatNum(selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)]) : '-' }}</div>
-                    </template>
-                    <template v-else>
-                      <div class="value">
-                        <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)], 'signal-round-gry': !selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)] }"></span>
-                      </div>
+                    <template v-if="selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)] != undefined">
+                      <div class="title">{{ item.title }}:</div>
+                      <template v-if="item.type !== 'sign'">
+                        <div class="value">{{ selectData && selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)] ? formatNum(selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)]) : '-' }}</div>
+                      </template>
+                      <template v-else>
+                        <div class="value">
+                          <span :class="{ 'signal-round': true, 'signal-round-run': selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)], 'signal-round-gry': selectData[item.code.replace('DewateringPump', `DewateringPump${key}`)] == '0' }"></span>
+                        </div>
+                      </template>
                     </template>
                   </div>
                 </div>
@@ -100,15 +109,15 @@
                 <div class="control-item">
                   <div class="control-title">控制模式:</div>
                   <a-radio-group v-model:value="selectData['ykjdqh']" @change="changeCtr">
-                    <a-radio :value="1">就地</a-radio>
-                    <a-radio :value="2">远程</a-radio>
+                    <a-radio :value="'0'">就地</a-radio>
+                    <a-radio :value="'1'">远程</a-radio>
                   </a-radio-group>
                 </div>
                 <div class="control-item">
                   <div class="control-title">检修模式:</div>
                   <a-radio-group v-model:value="selectData['jxmsqh']" @change="changeMode">
-                    <a-radio :value="1">开启</a-radio>
-                    <a-radio :value="2">关闭</a-radio>
+                    <a-radio :value="'0'">关闭</a-radio>
+                    <a-radio :value="'1'">开启</a-radio>
                   </a-radio-group>
                 </div>
               </div>
@@ -126,10 +135,10 @@
             <div class="">
               <div v-for="key in 2" :key="key">
                 <div class="device-row" v-for="(valveType, index) in valveCtrlType" :key="index">
-                  <div class="state"  >#{{ key }}{{ valveType.title }}</div>
-                  <div class="state" v-for="(state, i) in valveState" :key="i" >
-                    <span v-if="state.code == '_CtrlMode'">{{ selectData[`CentrifugalPump${key}_${valveType.code}${key}${state.code}`] == '1' ? '控制' : '-' }}</span>
-                    <span v-else :class="{ 'signal-round': true, 'signal-round-run': selectData[`CentrifugalPump${key}_${valveType.code}${key}${state.code}`] == '1', 'signal-round-gry': selectData[`CentrifugalPump${key}_${valveType.code}${key}${state.code}`] == '0' }"></span>
+                  <div class="state"  >#{{ key }}{{ valveType.title }} </div>
+                  <div class="state" v-for="(state, i) in valveState" :key="i" >                    
+                    <span v-if="state.code == '_CtrlMode'">{{ selectData[`CentrifugalPump${key}_${valveType.code}${state.code}`] == '1' ? '控制' : '-' }}</span>
+                    <span v-else :class="{ 'signal-round': true, 'signal-round-run': selectData[`CentrifugalPump${key}_${valveType.code}${state.code}`] == '1', 'signal-round-gry': selectData[`CentrifugalPump${key}_${valveType.code}${state.code}`] == '0' }"></span>
                   </div>
                 </div>
               </div>
@@ -200,6 +209,7 @@ const selectData = reactive({
   outValve4: false,
   jxmsqh: '1',
   ykjdqh: '1',
+  FlowSensor_InputFlux: '-',
 });
 
 const flvURL1 = () => {

+ 3 - 1
src/views/vent/monitorManager/gasPumpMonitor/gasPump.threejs.ts

@@ -52,6 +52,7 @@ export const setModelType = (type) => {
     if (gasPumpType === 'gasPump' && gasPumpBaseObj && gasPumpBaseObj.group) {
       if (model?.scene?.getObjectByName('gasPumpUnder') && gasPumpUnderObj && gasPumpUnderObj.group) {
         model.scene.remove(gasPumpUnderObj.group);
+        gasPumpUnderObj.clearCssText();
       }
       group = gasPumpBaseObj.group;
       const oldCameraPosition = { x: 15.9074, y: 5.40264, z: 27.12551 };
@@ -74,6 +75,7 @@ export const setModelType = (type) => {
         model.scene.remove(gasPumpBaseObj.group);
       }
       group = gasPumpUnderObj.group;
+      gasPumpUnderObj.addCssText();
       const oldCameraPosition = { x: 15.9074, y: 5.40264, z: 27.12551 };
       setTimeout(async () => {
         model?.scene?.add(group);
@@ -94,7 +96,7 @@ export const setModelType = (type) => {
 
 export const mountedThree = () => {
   return new Promise(async (resolve) => {
-    model = new UseThree('#gasPump3D');
+    model = new UseThree('#gasPump3D', '#gas3DCSS');
     model.setEnvMap('test2');
     model.renderer.toneMappingExposure = 1.0;
     // model.renderer.outputEncoding = THREE.sRGBEncoding;

+ 15 - 1
src/views/vent/monitorManager/gasPumpMonitor/gasPump.threejs.under.ts

@@ -1,5 +1,5 @@
 import * as THREE from 'three';
-
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
 // import * as dat from 'dat.gui';
 // const gui = new dat.GUI();
 // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
@@ -34,6 +34,20 @@ class gasPumpUnder {
     // gui.add(pointLight2.position, 'z', -500, 500);
   }
 
+  addCssText = () => {
+    if (!this.group) return;
+    if (!this.group.getObjectByName('text1')) {
+      const element = document.getElementById('FlowSensor') as HTMLElement;
+      const parentElement = document.getElementById('gas3DCSS') as HTMLElement;
+      parentElement.appendChild(element);
+      const fanLocalCSS3D = new CSS3DObject(element);
+      fanLocalCSS3D.name = 'text1';
+      fanLocalCSS3D.scale.set(0.015, 0.015, 0.015);
+      fanLocalCSS3D.position.set(0, 0.92, 0);
+      this.group.add(fanLocalCSS3D);
+    }
+  };
+
   mountedThree() {
     return new Promise((resolve) => {
       this.model.setGLTFModel([this.modelName]).then((gltf) => {

+ 11 - 15
src/views/vent/monitorManager/gasPumpMonitor/index.vue

@@ -2,19 +2,11 @@
   <div class="bg"
     style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
     <a-spin :spinning="loading" />
+    <div id="gas3DCSS" v-show="!loading" style="width: 100%; height: 100%; top:0; left: 0; position: absolute; overflow: hidden;">
+      
+    </div>
     <div id="gasPump3D" v-if="activeKey == 'monitor'" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
-    <!-- <div id="damper3DCSS" v-show="!loading" style="width: 100%; height: 100%; top:0; left: 0; position: absolute; overflow: hidden;">
-      <div>
-        <div ref="elementContent" class="elementContent">
-          <p><span class="data-title">压力(Pa):</span>{{selectData.frontRearDP}}</p>
-          <p><span class="data-title">动力源压力(MPa):</span>{{selectData.sourcePressure}}</p>
-          <p><span class="data-title">故障诊断:</span>
-            <i
-              :class="{'state-icon': true, 'open': selectData.messageBoxStatus, 'close': !selectData.messageBoxStatus}"
-            ></i>{{selectData.fault}}</p>
-        </div>
-      </div>
-    </div> -->
+    
   </div>
   <div class="scene-box">
     <customHeader :fieldNames="{ label: 'strinstallpos', value: 'deviceID', options: 'children' }" :options = 'options' @change="getSelectRow" :optionValue="optionValue">瓦斯抽采泵站监测与管控</customHeader>
@@ -73,7 +65,9 @@ const optionValue = ref('')
 const currentDeviceType = ref('')
 
 // 监测数据
-const selectData = reactive({});
+const selectData = reactive({
+  FlowSensor_InputFlux: 0
+});
 
 function changeActive(activeValue) {
   activeKey.value = activeValue
@@ -101,7 +95,7 @@ async function getDeviceList() {
     }
   })
   deviceList.value = deviceArr
-  deviceActive.value = deviceArr[0].deviceType
+  if(deviceArr[0])deviceActive.value = deviceArr[0].deviceType
 };
 
 async function getSysDataSource () {
@@ -149,8 +143,10 @@ onMounted(async() => {
   if (currentRoute.value['query'] && currentRoute.value['query']['id']) optionValue.value = currentRoute.value['query']['id']
   mountedThree().then(async () => {
     await getSysDataSource()
-    await getDeviceList()  
+    await getDeviceList()
+    getSelectRow(optionValue.value)
   });
+
 });
 
 onUnmounted(() => {

+ 12 - 4
src/views/vent/monitorManager/gateMonitor/index.vue

@@ -25,8 +25,8 @@
         <div class="button-box" @click="playAnimation(2)">关闭前门</div>
         <div class="button-box" @click="playAnimation(3)">打开后门</div>
         <div class="button-box" @click="playAnimation(4)">关闭后门</div>
-        <!-- <div class="button-box" @click="playAnimation(5)">打开前后门</div>
-        <div class="button-box" @click="playAnimation(6)">关闭前后门</div> -->
+        <div class="button-box" @click="playAnimation(5)">打开前后门</div>
+        <div class="button-box" @click="playAnimation(6)">关闭前后门</div>
       </div>
       <div class="top-right row">
         <div class="control-type row">
@@ -130,12 +130,12 @@
   </div>
   <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 15px; width: 300px; height: 280px; margin: auto">
   </div>
-  <HandleModal :modal-is-show="modalIsShow" :modal-title="modalTitle" :modal-type="modalType" @handle-ok="handleOK"
+  <HandleModal v-if="!globalConfig?.simulatedPassword" :modal-is-show="modalIsShow" :modal-title="modalTitle" :modal-type="modalType" @handle-ok="handleOK"
     @handle-cancel="handleCancel" />
 </template>
 
 <script setup lang="ts">
-import { onBeforeUnmount, onUnmounted, onMounted, ref, reactive, nextTick, h, render } from 'vue';
+import { onBeforeUnmount, onUnmounted, onMounted, ref, reactive, nextTick, inject  } from 'vue';
 import DeviceEcharts from '../comment/DeviceEcharts.vue';
 import MonitorTable from '../comment/MonitorTable.vue';
 import HistoryTable from '../comment/HistoryTable.vue';
@@ -153,6 +153,8 @@ import { BorderBox8 as DvBorderBox8 } from '@kjgl77/datav-vue3';
 import { useRouter } from 'vue-router';
 import { deviceCameraInit } from '/@/utils/ventutil.ts'
 
+const globalConfig = inject('globalConfig');
+
 const { currentRoute } = useRouter();
 const MonitorDataTable = ref()
 
@@ -364,6 +366,11 @@ function playAnimation(handlerState) {
       }
       break;
   }
+
+  
+  if(globalConfig?.simulatedPassword){
+    handleOK(globalConfig?.simulatedPassword, handlerState+'')
+  }
 };
 
 
@@ -443,6 +450,7 @@ function handleOK(passWord, handlerState) {
       }
       break;
   }
+  debugger
   if (data.paramcode) {
     deviceControlApi(data)
       .then((res) => {

+ 1 - 1
src/views/vent/monitorManager/mainFanMonitor/index.vue

@@ -246,7 +246,7 @@
       </div>
       <!-- 调频 -->
       <div class="vent-flex-row input-box" v-if="modalType == 'frequency'">
-        <div class="label">运行频率(单位):</div>
+        <div class="label">运行频率(Hz):</div>
         <a-input-number v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
       </div>
       <!-- 启动或停止 -->

+ 338 - 106
src/views/vent/monitorManager/nitrogen/components/nitrogenHome.vue

@@ -1,62 +1,117 @@
 <template>
     <div id="compressor3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"></div>
-    <div id="compressorCss3D" class="threejs-Object-CSS"
+    <div v-show="monitorDataGroupFlag == 1" id="compressorCss3D"  class="threejs-Object-CSS compressorCss3D-box"
         style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 2; top: 0px; left: 0px">
-        <a-spin :spinning="loading" />
-        <div v-for="groupNum in monitorDataGroupNum" :key="groupNum" class="modal-monitor">
+        <!-- <a-spin :spinning="loading" /> -->
+        <div  v-for="(groupNum, index) in monitorDataGroupNum1" :key="index" class="modal-monitor">
           <fourBorderBg :class="`kyj${groupNum}`" :id="`nitrogenMonitor${groupNum}`">
             <div class="title">{{ groupNum }}号空压机 </div>
             <div class="monitor-item">
-              <span class="monitor-title">排气压力:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['compressExhaustPressF1'] ?
-                monitorData[groupNum - 1]['compressExhaustPressF1'] : '-' }}</span><span class="unit">Mpa</span></span>
+              <span class="monitor-title">机头温度:</span>
+              <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_HeadTemp`] ?
+                monitorData[`PRE${groupNum}_CPR_HeadTemp`] : '-' }}</span><span class="unit"></span>℃</span>
             </div>
             <div class="monitor-item">
-              <span class="monitor-title">分离压力:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['compressSeparatePressF1'] ?
-                monitorData[groupNum - 1]['compressSeparatePressF1'] : '-' }}</span><span class="unit">Mpa</span></span>
+              <span class="monitor-title">冷却温度:</span>
+              <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_CoolantTemp`] ?
+                monitorData[`PRE${groupNum}_CPR_CoolantTemp`] : '-' }}</span><span class="unit">℃</span></span>
             </div>
             <div class="monitor-item">
               <span class="monitor-title">排气温度:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['exhaustTemp'] ? monitorData[groupNum
-                - 1]['exhaustTemp'] : '-' }}</span><span class="unit">℃</span></span>
+              <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_ExhaustTemp`] ? monitorData[`PRE${groupNum}_CPR_ExhaustTemp`] : '-' }}</span><span class="unit">℃</span></span>
             </div>
             <div class="signal-item">
               <div class="signal"><span class="monitor-title">运行信号</span><span
-                  :class="{ 'signal-round': true, 'signal-round-run': monitorData[groupNum - 1]['compressRunSigF1'], 'signal-round-gry': !monitorData[groupNum - 1]['compressRunSigF1'] }"></span>
+                  :class="{ 'signal-round': true, 'signal-round-run': monitorData[`PRE${groupNum}_MOT_Running`] == '1', 'signal-round-gry': monitorData[`PRE${groupNum}_MOT_Running`] != '1' }"></span>
               </div>
-              <div class="signal"><span class="monitor-title">加载信号</span><span
-                  :class="{ 'signal-round': true, 'signal-round-run': monitorData[groupNum - 1]['compressLoadSigF1'], 'signal-round-gry': !monitorData[groupNum - 1]['compressLoadSigF1'] }"></span>
+              <div class="signal"><span class="monitor-title">故障信号</span><span
+                  :class="{ 'signal-round': true, 'signal-round-warning': monitorData[`PRE${groupNum}_MOT_Fault`] == '1', 'signal-round-gry': monitorData[`PRE${groupNum}_MOT_Fault`] != '1' }"></span>
               </div>
             </div>
           </fourBorderBg>
           <fourBorderBg :class="`cqg${groupNum}`" :id="`cqgMonitor${groupNum}`">
-            <div class="title">{{ cqgs[groupNum - 1] }}</div>
+            <div class="title">{{ groupNum }}号储气罐 </div>
             <div class="monitor-item">
               <span class="monitor-title">气囊温度:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['airReceiverTemp'] ?
-                monitorData[groupNum - 1]['airReceiverTemp'] : '-' }}</span><span class="unit">℃</span></span>
+              <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_VLS_Temp`] ?
+                monitorData[`PRE${groupNum}_VLS_Temp`] : '-' }}</span><span class="unit">℃</span></span>
             </div>
-            <div class="monitor-item">
+            <!-- <div class="monitor-item">
               <span class="monitor-title">气囊压力<span class="unit"></span>:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['airReceiverPress'] ?
+              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1] && monitorData[groupNum - 1]['airReceiverPress'] ?
                 monitorData[groupNum - 1]['airReceiverPress'] : '-' }}</span><span class="unit">Mpa</span></span>
             </div>
             <div class="monitor-item">
               <span class="monitor-title">气囊流量<span class="unit"></span>:</span>
-              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['airReceiverFlow'] ?
+              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1] && monitorData[groupNum - 1]['airReceiverFlow'] ?
                 monitorData[groupNum - 1]['airReceiverFlow'] : '-' }}</span><span class="unit">m³/k</span></span>
-            </div>
+            </div> -->
           </fourBorderBg>
         </div>
-      </div>
+    </div>
+    <div v-show="monitorDataGroupFlag == 2" id="compressorCss3D1" class="threejs-Object-CSS compressorCss3D-box"
+        style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 3; top: 0px; left: 0px">
+        <div  v-for="(groupNum, index) in monitorDataGroupNum2" :key="index" class="modal-monitor">
+            <fourBorderBg :class="`kyj${groupNum}`" :id="`nitrogenMonitor${groupNum}`">
+              <div class="title">{{ groupNum }}号空压机 </div>
+              <div class="monitor-item">
+                <span class="monitor-title">机头温度:</span>
+                <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_HeadTemp`] ?
+                  monitorData[`PRE${groupNum}_CPR_HeadTemp`] : '-' }}</span><span class="unit"></span>℃</span>
+              </div>
+              <div class="monitor-item">
+                <span class="monitor-title">冷却温度:</span>
+                <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_CoolantTemp`] ?
+                  monitorData[`PRE${groupNum}_CPR_CoolantTemp`] : '-' }}</span><span class="unit">℃</span></span>
+              </div>
+              <div class="monitor-item">
+                <span class="monitor-title">排气温度:</span>
+                <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_CPR_ExhaustTemp`] ? monitorData[`PRE${groupNum}_CPR_ExhaustTemp`] : '-' }}</span><span class="unit">℃</span></span>
+              </div>
+              <div class="signal-item">
+                <div class="signal"><span class="monitor-title">运行信号</span><span
+                    :class="{ 'signal-round': true, 'signal-round-run': monitorData[`PRE${groupNum}_MOT_Running`] == '1', 'signal-round-gry': monitorData[`PRE${groupNum}_MOT_Running`] != '1' }"></span>
+                </div>
+                <div class="signal"><span class="monitor-title">故障信号</span><span
+                    :class="{ 'signal-round': true, 'signal-round-warning': monitorData[`PRE${groupNum}_MOT_Fault`] == '1', 'signal-round-gry': monitorData[`PRE${groupNum}_MOT_Fault`] != '1' }"></span>
+                </div>
+              </div>
+            </fourBorderBg>
+            <fourBorderBg :class="`cqg${groupNum}`" :id="`cqgMonitor${groupNum}`">
+              <div class="title">{{ groupNum }}号储气罐 </div>
+              <div class="monitor-item">
+                <span class="monitor-title">气囊温度:</span>
+                <span class="monitor-val"><span class="val">{{ monitorData[`PRE${groupNum}_VLS_Temp`] ?
+                  monitorData[`PRE${groupNum}_VLS_Temp`] : '-' }}</span><span class="unit">℃</span></span>
+              </div>
+              <!-- <div class="monitor-item">
+              <span class="monitor-title">气囊压力<span class="unit"></span>:</span>
+              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1] && monitorData[groupNum - 1]['airReceiverPress'] ?
+                monitorData[groupNum - 1]['airReceiverPress'] : '-' }}</span><span class="unit">Mpa</span></span>
+            </div>
+            <div class="monitor-item">
+              <span class="monitor-title">气囊流量<span class="unit"></span>:</span>
+              <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1] && monitorData[groupNum - 1]['airReceiverFlow'] ?
+                monitorData[groupNum - 1]['airReceiverFlow'] : '-' }}</span><span class="unit">m³/k</span></span>
+            </div> -->
+            </fourBorderBg>
+          </div>
+    </div>
     <div class="nitrogen-home">
+      <div style="position: absolute; color: #fff; top: 30px; pointer-events: auto; display: flex;">
+        <span class="tab-button-box" :class="{'tab-button-box-active': monitorDataGroupFlag == 1}" @click="setMonitorGroupNum(monitorDataGroupNum1, 1)">压风系统1</span>
+        <span class="tab-button-box" :class="{ 'tab-button-box-active': monitorDataGroupFlag == 2 }" @click="setMonitorGroupNum(monitorDataGroupNum2, 2)">压风系统2</span>
+      </div>
+      <div class="total-data">
+        <div class="item">总流量(m³/min):<span class="val">{{ monitorData[`PreSys_TotalOutPipeFlow${monitorDataGroupFlag}`] ? monitorData[`PreSys_TotalOutPipeFlow${monitorDataGroupFlag}`] : '-' }}</span></div>
+        <div class="item">总压力(bar):<span class="val">{{ monitorData[`PreSys_TotalOutPipePre${monitorDataGroupFlag}`] ? monitorData[`PreSys_TotalOutPipePre${monitorDataGroupFlag}`] : '-' }}</span></div>
+      </div>
       <div class="nitrogen-container">
         <div class="top-box">
           <!-- 左边监测数据 -->
           <div class="lr-box left-box">
             <div class="left-container">
-              <div class="item item-l" v-for="groupNum in monitorDataGroupNum" :key="groupNum">
+              <div class="item item-l" v-for="(groupNum, index) in monitorDataGroupNum" :key="index">
                 <div class="monitor-box">
                   <ventBox1>
                     <template #title>
@@ -66,13 +121,13 @@
                       <div class="state-item" v-for="(data, index) in showMonitorData" :key="index">
                         <div class="item-col">
                           <span class="state-title">{{ Object.values(data)[0] }} :</span>
-                          <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[0]])
-                            >= 0 ? monitorData[groupNum - 1][Object.keys(data)[0]] : '-' }}</span>
+                          <span class="state-val">{{ (monitorData[Object.keys(data)[0].replace('PRE', 'PRE'+ groupNum)])
+                            >= 0 ? Number(monitorData[Object.keys(data)[0].replace('PRE', 'PRE' + groupNum)]) : '-' }}</span>
                         </div>
                         <div class="item-col">
                           <span class="state-title">{{ Object.values(data)[1] }} :</span>
-                          <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[1]])
-                            >= 0 ? monitorData[groupNum - 1][Object.keys(data)[1]] : '-' }}</span>
+                          <span class="state-val">{{ (monitorData[Object.keys(data)[0].replace('PRE', 'PRE' + groupNum)])
+                            >= 0 ? Number(monitorData[Object.keys(data)[0].replace('PRE', 'PRE' + groupNum)]) : '-' }}</span>
                         </div>
                       </div>
                     </template>
@@ -82,7 +137,7 @@
             </div>
           </div>
           <!-- 右边控制状态 -->
-          <div class="lr-box right-box">
+          <div class="lr-box right-box" style="display: none;">
             <ventBox1>
               <template #title>
                 <div>远程控制</div>
@@ -151,71 +206,17 @@
 <script lang="ts" setup name="nitrogenHome">
 import { onMounted, onUnmounted, ref, watch } from 'vue'
 import fourBorderBg from '../../../comment/components/fourBorderBg.vue'
-import { mountedThree, destroy } from '../nitrogen.threejs'
+import { mountedThree, destroy, setModelType, clearCssText } from '../nitrogen.threejs'
 import { list } from '../nitrogen.api'
 import ventBox1 from '/@/components/vent/ventBox1.vue'
+import { monitorDataGroupNum1, monitorDataGroupNum2, airCompressorState, showMonitorData, monitorData } from '../nitrogen.data'
 
 const loading = ref(true)
-
+const monitorDataGroupNum = ref(monitorDataGroupNum1)
+const monitorDataGroupFlag = ref(1)
 const kyjs = ['1号空压机', '2号空压机', '3号空压机', '4号空压机'];
 const cqgs = ['1号储气罐', '2号储气罐', '3号储气罐', '4号储气罐'];
 
-const monitorDataGroupNum = ref(3);
-
-const airCompressorState = ref([
-  {
-    id: '',
-    compressRunSigF1: false,
-    controlModel: false
-  },
-  {
-    id: '',
-    compressRunSigF1: false,
-    controlModel: false
-  },
-  {
-    id: '',
-    compressRunSigF1: false,
-    controlModel: false
-  },
-  {
-    id: '',
-    compressRunSigF1: false,
-    controlModel: false
-  }
-]);
-const showMonitorData = [
-  {
-    supVolt1: '电流A(A)',
-    supVolt2: '电流B(A)',
-  },
-  {
-    supVolt3: '电流C(A)',
-    current: '电源电压(V)',
-  },
-  {
-    shock1: '震动X(mm/s)',
-    shock2: '震动Y(mm/s)',
-  },
-  {
-    shock3: '震动Z(mm/s)',
-    noise: '噪声',
-  },
-  {
-    ambTempCol: '环境温度(℃)',
-  },
-];
-const monitorData = ref(new Array(4).fill({
-  strName: '空压机',
-  compressGroupName: '',
-  compressExhaustPressF1: '-',
-  compressSeparatePressF1: '-',
-  compressHostTempF1: '-',
-  compressCrewTempF1: '-',
-  compressRunTimeF1: '-',
-  controlModel: 'LOC'
-}));
-
 // https获取监测数据
 let timer: null | NodeJS.Timeout = null;
 async function getMonitor(flag?) {
@@ -231,21 +232,166 @@ async function getMonitor(flag?) {
 };
 
 async function getDataSource() {
-  const res = await list({ devicetype: 'pressurefan', pagetype: 'normal' });
-  const dataSource = res.msgTxt[0].datalist || [];
-  dataSource.forEach((data, index) => {
-    const item = data.readData;
-    Object.assign(item, data);
-    item.compressRunSigF1 = item.compressRunSigF1 ? true : false
-    airCompressorState.value[index].id = item.id
-    airCompressorState.value[index].compressRunSigF1 = item.compressRunSigF1
-    airCompressorState.value[index].controlModel = item.controlModel === 'LOC' ? true : false
-    monitorData.value[index] = item
-  });
-  monitorDataGroupNum.value = monitorData.value.length
+  const res = await list({ devicetype: 'forcFan', pagetype: 'normal' });
+  let dataSource = res.msgTxt[0].datalist[0];
+  // dataSource =  
+  //   {
+  //     "msgType": null,
+  //     "deviceID": "1705212847586627592",
+  //     "strname": "压风机系统",
+  //     "strinstallpos": "压风机系统",
+  //     "fsectarea": "null",
+  //     "stationname": "压风机系统分站",
+  //     "deviceType": "forcFan",
+  //     "typeName": null,
+  //     "netStatus": 1,
+  //     "warnFlag": 0,
+  //     "warnLevel": null,
+  //     "warnLevel_str": null,
+  //     "warnTime": null,
+  //     "readTime": "2023-10-24 08:47:27",
+  //     "warnDes": "",
+  //     "frontGateOpenCtrl": null,
+  //     "rearGateOpenCtrl": null,
+  //     "readData": {
+  //       "PRE1_MOT_PhaseATemp": "526",
+  //       "PRE3_CPR_CoolantTemp": "12",
+  //       "PRE4_CPR_HeadTemp": "13",
+  //       "PRE2_MOT_PhaseATempAlarm": "0",
+  //       "PRE5_MOT_Fault": "0",
+  //       "PRE4_MOT_PhaseATempStop": "0",
+  //       "PRE4_CPR_LoadPre": "65",
+  //       "PRE5_CPR_LoadPre": "62",
+  //       "PRE4_MOT_CtrlMode": "1",
+  //       "PRE2_CPR_LoadorUnload": "1",
+  //       "PRE3_MOT_PhaseBTemp": "133",
+  //       "PRE1_CPR_ExhaustPre": "66",
+  //       "PRE1_MOT_PhaseATempAlarm": "0",
+  //       "PRE5_CPR_UnLoadPre": "69",
+  //       "PRE4_MOT_PhaseATemp": "129",
+  //       "PRE5_MOT_PhaseCTemp": "685",
+  //       "PRE5_MOT_PhaseATemp": "681",
+  //       "PRE5_VLS_Temp": "590",
+  //       "PRE1_CPR_LoadTime": "8344",
+  //       "PRE2_CPR_LoadTime": "5553",
+  //       "PRE4_CPR_LoadorUnload": "0",
+  //       "PRE5_MOT_PhaseATempAlarm": "0",
+  //       "PRE3_CPR_LoadTime": "4511",
+  //       "PRE5_CPR_LoadTime": "6032",
+  //       "PRE1_MOT_PhaseATempStop": "0",
+  //       "PRE2_MOT_CompProtFault": "0",
+  //       "PRE5_MOT_PhaseATempStop": "0",
+  //       "PRE3_MOT_PhaseATempStop": "0",
+  //       "PRE1_VLS_Temp": "436",
+  //       "PRE2_CPR_ExhaustPre": "71",
+  //       "PRE5_MOT_CtrlMode": "1",
+  //       "PRE3_CPR_ExhaustTemp": "10",
+  //       "PRE3_MOT_TotalRunTime": "5342",
+  //       "P RE2_MOT_PhaseATemp": "541",
+  //       "PRE4_MOT_PhaseCTemp": "130",
+  //       "PRE4_MOT_PhaseATempAlarm": "0",
+  //       "timestamp": "1698108447720",
+  //       "PRE3_VLS_Temp": "219",
+  //       "PRE1_HostorLoc": "0",
+  //       "PRE2_MOT_Running": "1",
+  //       "PRE1_CPR_LoadPre": "65",
+  //       "PRE1_MOT_Running": "1",
+  //       "PRE4_MOT_Fault": "0",
+  //       "PRE2_CPR_LoadPre": "65",
+  //       "PRE3_MOT_Running": "0",
+  //       "PRE4_MOT_Running": "0",
+  //       "PRE3_CPR_LoadPre": "65",
+  //       "PRE1_MOT_CtrlMode": "1",
+  //       "PRE3_MOT_CtrlMode": "1",
+  //       "PRE3_CPR_LoadorUnload": "0",
+  //       "PRE2_MOT_PhaseCTemp": "550",
+  //       "PRE1_CPR_CoolantTemp": "71",
+  //       "PRE1_MOT_PhaseBTemp": "539",
+  //       "PRE3_MOT_PhaseATempAlarm": "0",
+  //       "PRE5_MOT_Running": "1",
+  //       "PRE1_MOT_Fault": "0",
+  //       "PRE4_CPR_ExhaustPre": "66",
+  //       "PRE4_CPR_CoolantTemp": "12",
+  //       "PRE5_CPR_ExhaustTemp": "76",
+  //       "PRE2_CPR_HeadTemp": "89",
+  //       "PRE3_MOT_PhaseCTemp": "135",
+  //       "PRE4_CPR_LoadTime": "5084",
+  //       "sign": "0",
+  //       "PRE1_CPR_UnLoadPre": "72",
+  //       "PRE4_HostorLoc": "0",
+  //       "PRE4_MOT_CompProtFault": "0",
+  //       "PRE1_CPR_LoadorUnload": "1",
+  //       "PRE3_CPR_ExhaustPre": "68",
+  //       "PRE2_CPR_ExhaustTemp": "77",
+  //       "PRE2_MOT_PhaseATempStop": "0",
+  //       "PRE5_MOT_CompProtFault": "0",
+  //       "PRE2_MOT_Fault": "0",
+  //       "PRE5_MOT_PhaseBTemp": "676",
+  //       "PRE3_MOT_PhaseATemp": "134",
+  //       "PRE4_MOT_PhaseBTemp": "130",
+  //       "PRE2_CPR_CoolantTemp": "66",
+  //       "PRE3_HostorLoc": "0",
+  //       "PRE4_MOT_TotalRunTime": "5104",
+  //       "PRE1_MOT_TotalRunTime": "8416",
+  //       "PRE3_MOT_CompProtFault": "0",
+  //       "PRE3_MOT_Fault": "0",
+  //       "PRE4_CPR_UnLoadPre": "72",
+  //       "PRE1_CPR_HeadTemp": "97",
+  //       "PRE2_HostorLoc": "0",
+  //       "PRE2_MOT_PhaseBTemp": "562",
+  //       "PRE3_CPR_HeadTemp": "13",
+  //       "PRE2_MOT_TotalRunTime": "5586",
+  //       "PRE5_CPR_HeadTemp": "95",
+  //       "PRE3_CPR_UnLoadPre": "72",
+  //       "PRE4_VLS_Temp": "166",
+  //       "PRE5_CPR_CoolantTemp": "70",
+  //       "PRE1_MOT_CompProtFault": "0",
+  //       "PRE5_MOT_TotalRunTime": "7825",
+  //       "PRE2_MOT_CtrlMode": "1",
+  //       "PRE5_CPR_ExhaustPre": "68",
+  //       "PRE1_MOT_PhaseCTemp": "544",
+  //       "PRE5_CPR_LoadorUnload": "1",
+  //       "PRE2_CPR_UnLoadPre": "72",
+  //       "PRE4_CPR_ExhaustTemp": "11",
+  //       "PRE2_VLS_Temp": "445",
+  //       "isRun": "-2",
+  //       "PRE5_HostorLoc": "0",
+  //       "PRE1_CPR_ExhaustTemp": "68"
+  //     },
+  //     "readDataDes": null,
+  //     "summaryHour": [],
+  //     "summaryDay": [],
+  //     "history": [],
+  //     "totalInfo": null,
+  //     "sign": null,
+  //     "cameras": [],
+  //     "links": [],
+  //     "other1": null,
+  //     "other2": null,
+  //     "other3": null
+  //   }
+  if(dataSource){
+    monitorData.value = Object.assign(dataSource, dataSource.readData);
+  }
+  // dataSource.forEach((data, index) => {
+  //   const item = data.readData;
+  //   Object.assign(item, data);
+  //   item.compressRunSigF1 = item.compressRunSigF1 ? true : false
+  //   airCompressorState.value[index].id = item.id
+  //   airCompressorState.value[index].compressRunSigF1 = item.compressRunSigF1
+  //   airCompressorState.value[index].controlModel = item.controlModel === 'LOC' ? true : false
+  //   monitorData.value[index] = item
+  // });
+  // monitorDataGroupNum.value = monitorData.value.length
   loading.value = false
 };
 
+function setMonitorGroupNum(num, flag){
+  
+  monitorDataGroupNum.value = num
+  monitorDataGroupFlag.value = flag
+}
+
 function handlerDevice(data) {
   // if (data.length < 1) return
   // handleAirCompressor({ id: data.id, compressRunF1: data.compressRunSigF1 }).then(res => {
@@ -264,14 +410,21 @@ function handlerControlModel(data) {
 
 }
 
-watch(monitorDataGroupNum, (newVal) => {
-  if (newVal) {
-    destroy()
-    mountedThree(newVal)
+watch(monitorDataGroupFlag, (newVal) => {
+
+  if(newVal == 1){
+    setModelType('compressor1')
+  }
+  if (newVal == 2) {
+    setModelType('compressor2')
   }
 })
+
 onMounted(async () => {
-  await getMonitor(true)
+  mountedThree(monitorDataGroupNum1, monitorDataGroupNum2).then(async() => {
+    await getMonitor(true)
+    setModelType('compressor1')
+  })
 })
 
 onUnmounted(() => {
@@ -292,9 +445,26 @@ onUnmounted(() => {
   height: 100%;
   display: flex;
   justify-content: center;
+  position: relative;
+  .total-data{
+    position: absolute;
+    color: #e4cd00;
+    z-index: 9;
+    top: 50px;
+    right: 30px;
+    display: flex;
+    font-size: 18px;
+    .item{
+      margin-left: 30px;
+      .val{
+        color: #00d8ff;
+      }
+    }
+  }
+
 }
 
-#compressorCss3D {
+.compressorCss3D-box {
   .modal-monitor {
     position: absolute;
     left: 0px;
@@ -415,7 +585,7 @@ onUnmounted(() => {
   justify-content: center;
   align-items: center;
   pointer-events: none;
-  top: 100px;
+  top: 60px;
   .nitrogen-container {
     width: 100%;
     height: calc(100%);
@@ -786,4 +956,66 @@ onUnmounted(() => {
       }
     }
   }
-}</style>
+}
+.tab-button-box{
+  display: inline-block;
+  position: relative;
+  padding: 5px;
+  // border: 1px transparent solid;
+  border-radius: 5px;
+  margin-left: 8px;
+  margin-right: 8px;
+  width: auto;
+  // height: 40px;
+  // border: 1px solid #65dbea;
+  height: 35px !important;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  padding: 0 15px 5px 15px;
+  cursor: pointer;
+  &:hover {
+    background: linear-gradient(#2cd1ff55, #1eb0ff55);
+  }
+  &::before {
+    width: calc(100% - 6px);
+    height: 27px;
+    content: '';
+    position: absolute;
+    top: 3px;
+    right: 0;
+    left: 3px;
+    bottom: 0;
+    z-index: -1;
+    border-radius: inherit; /*important*/
+    background: linear-gradient(#1fa6cb, #127cb5);
+  }
+  &::after {
+    width: calc(100% + 32px);
+    height: 10px;
+    content: '';
+    position: absolute;
+    top: 28px;
+    right: 0;
+    left: -16px;
+    bottom: 0;
+    z-index: -1;
+    border-radius: inherit; /*important*/
+    background: url('/@/assets/images/vent/short-light.png') no-repeat;
+    background-position: center;
+    background-size: 100%;
+    z-index: 999;
+  }
+}
+
+.tab-button-box-active {
+  border: 1px solid #66989e !important;
+  &:hover {
+    background: none !important;
+  }
+  &::before {
+    background: linear-gradient(#1fa6cbcc, #127cb5cc) !important;
+  }
+}
+</style>

+ 50 - 1
src/views/vent/monitorManager/nitrogen/nitrogen.data.ts

@@ -31,7 +31,56 @@ export const bottomBtnList = ref([
     isHover: false,
   },
 ]);
-
+export const monitorDataGroupNum1 = [1, 2, 3];
+export const monitorDataGroupNum2 = [4, 5];
+export const monitorDataGroupNum = ref(3)
+export const airCompressorState = ref([
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false,
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false,
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false,
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false,
+  },
+]);
+export const showMonitorData = [
+  {
+    PRE_CPR_ExhaustPre: '排气压力(bar)',
+    PRE_CPR_LoadPre: '加载压力(bar)',
+  },
+  {
+    PRE_CPR_UnLoadPre: '卸载压力(bar)',
+    PRE_CPR_LoadTime: '加载时间(h)',
+  },
+  {
+    PRE_MOT_TotalRunTime: '总运行时间(h)',
+  },
+];
+  export const monitorData = ref(
+  new Array(4).fill({
+    strName: '空压机',
+    compressGroupName: '',
+    compressExhaustPressF1: '-',
+    compressSeparatePressF1: '-',
+    compressHostTempF1: '-',
+    compressCrewTempF1: '-',
+    compressRunTimeF1: '-',
+    controlModel: 'LOC',
+  })
+  );
 
       // dataInfo: {
       //   controlModel: false

+ 49 - 29
src/views/vent/monitorManager/nitrogen/nitrogen.dishang.threejs.ts

@@ -1,6 +1,7 @@
 import * as THREE from 'three';
 import { setModalCenter } from '/@/utils/threejs/util';
 import { CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
 // import * as dat from 'dat.gui';
 // const gui = new dat.GUI();
 // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
@@ -16,6 +17,10 @@ class Nitrogen {
   playerStartClickTime2 = new Date().getTime();
   deviceRunState = '';
   nitrogenNum = 0;
+  singGltf: THREE.Object3D | undefined = undefined;
+  CSSCanvasContainer;
+  css3dRender;
+  canvasContainer;
 
   constructor(model) {
     this.model = model;
@@ -125,12 +130,13 @@ class Nitrogen {
     model.material = transparentMaterial;
   };
 
-  addCssText = () => {
+  addCssText = (monitorDataGroup) => {
     if (this.nitrogenNum > 0) {
       for (let i = 0; i < this.nitrogenNum; i++) {
+        const n = monitorDataGroup[i];
         const compressorModal = this.group.getObjectByName('compressorModal' + i) as THREE.Object3D;
         if (!compressorModal.getObjectByName('monitorNitrogenText')) {
-          const element = document.getElementById('nitrogenMonitor' + (i + 1)) as HTMLElement;
+          const element = document.getElementById('nitrogenMonitor' + n) as HTMLElement;
           if (element) {
             element.style.top = '0px';
             element.style.left = '0px';
@@ -145,7 +151,7 @@ class Nitrogen {
           }
         }
         if (!compressorModal.getObjectByName('cqgMonitorText')) {
-          const element = document.getElementById('cqgMonitor' + (i + 1)) as HTMLElement;
+          const element = document.getElementById('cqgMonitor' + n) as HTMLElement;
           if (element) {
             element.style.top = '0px';
             element.style.left = '0px';
@@ -201,36 +207,50 @@ class Nitrogen {
     withVolume.push(transparentWrap);
   };
 
-  mountedThree(nitrogenNum) {
-    this.nitrogenNum = nitrogenNum;
-    return new Promise((resolve) => {
-      if (nitrogenNum < 1) {
-        resolve(null);
-        return;
-      }
-      this.model.setGLTFModel([this.modelName]).then(async (gltf) => {
-        const nitrogenGroup = new THREE.Object3D();
-        const flag = nitrogenNum % 2 == 0 ? 0 : 1;
-        for (let i = 0; i < nitrogenNum; i++) {
-          const compressorModal = gltf[0].clone();
-          compressorModal.name = 'compressorModal' + i;
-          const c = Math.floor(nitrogenNum / 2);
-          if (flag) {
-            compressorModal.position.set(0, 0, 1.29 * (c - i));
-          } else {
-            compressorModal.position.set(0, 0, (c - i - 0.5) * 1.29);
-          }
-          nitrogenGroup.add(compressorModal);
+  setModal(nitrogenNum) {
+    if (nitrogenNum < 1) {
+      return;
+    }
+    if (this.singGltf) {
+      this.nitrogenNum = nitrogenNum;
+      const nitrogenGroup = new THREE.Object3D();
+      const flag = nitrogenNum % 2 == 0 ? 0 : 1;
+      for (let i = 0; i < nitrogenNum; i++) {
+        const compressorModal = this.singGltf?.clone();
+        compressorModal.name = 'compressorModal' + i;
+        const c = Math.floor(nitrogenNum / 2);
+        if (flag) {
+          compressorModal.position.set(0, 0, 1.29 * (c - i));
+        } else {
+          compressorModal.position.set(0, 0, (c - i - 0.5) * 1.29);
         }
+        nitrogenGroup.add(compressorModal);
+      }
 
-        this.group = nitrogenGroup;
+      this.group = nitrogenGroup;
 
-        this.group.name = this.modelName;
-        setModalCenter(this.group);
-        this.addCssText();
-        this.setModalPosition();
-        this.addLight();
+      this.group.name = this.modelName;
+      setModalCenter(this.group);
+      this.setModalPosition();
+    }
+  }
+
+  mountedThree(monitorDataGroup, dom) {
+    return new Promise((resolve) => {
+      this.CSSCanvasContainer = document.querySelector(dom);
+      if (this.CSSCanvasContainer) {
+        this.css3dRender = new CSS3DRenderer() as CSS3DRenderer;
+        this.css3dRender.setSize(this.model.canvasContainer.clientWidth, this.model.canvasContainer.clientHeight);
+        this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement);
+        this.css3dRender.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
+      }
+
+      this.model.setGLTFModel([this.modelName]).then(async (gltf) => {
+        this.singGltf = gltf[0];
+        this.setModal(monitorDataGroup.length);
+        this.addCssText(monitorDataGroup);
         resolve(null);
+        this.addLight();
       });
     });
   }

+ 88 - 44
src/views/vent/monitorManager/nitrogen/nitrogen.threejs.ts

@@ -11,7 +11,8 @@ import Nitrogen from './nitrogen.dishang.threejs';
 // 模型对象、 文字对象
 let model: UseThree | undefined,
   group: THREE.Object3D | undefined,
-  compressorObj: Nitrogen | undefined,
+  compressorObj1: Nitrogen | undefined,
+  compressorObj2: Nitrogen | undefined,
   modalType = 'compressor';
 
 const { mouseDownFn, mousemoveFn, mouseUpFn } = useEvent();
@@ -22,8 +23,11 @@ const mouseEvent = (event) => {
   if (event.button == 0 && model && group) {
     model.canvasContainer?.addEventListener('mousemove', mousemove);
     mouseDownFn(model, group, event, (intersects) => {
-      if (modalType === 'compressor') {
-        compressorObj?.mousedownModel.call(compressorObj, intersects);
+      if (modalType === 'compressor1') {
+        compressorObj1?.mousedownModel.call(compressorObj1, intersects);
+      }
+      if (modalType === 'compressor2') {
+        compressorObj2?.mousedownModel.call(compressorObj2, intersects);
       }
     });
   }
@@ -51,74 +55,114 @@ const render = () => {
 
 /* 添加监控数据 */
 export const addText = () => {
-  if (!compressorObj) return;
-  if (modalType === 'compressor') {
-    return compressorObj.addCssText.call(compressorObj);
+  if (!compressorObj1) return;
+  if (modalType === 'compressor1') {
+    return compressorObj1.addCssText.call(compressorObj1);
+  } else if (modalType === 'compressor2') {
+    return compressorObj2.addCssText.call(compressorObj2);
   }
 };
 
 export const play = () => {
-  if (modalType === 'compressor' && compressorObj) {
-    return compressorObj.play.call(compressorObj);
+  if (modalType === 'compressor1' && compressorObj1) {
+    return compressorObj1.play.call(compressorObj1);
+  }
+  if (modalType === 'compressor2' && compressorObj2) {
+    return compressorObj2.play.call(compressorObj2);
   }
 };
 
 // 切换风窗类型
 export const setModelType = (type) => {
   modalType = type;
-  model?.camera?.position.set(-1000, 100, 500);
+
+  if (group) {
+    // compressorObj1?.clearCssText();
+    model?.scene?.remove(group);
+    // model?.initCSS3Renderer('#compressorCss3D');
+  }
 
   return new Promise((resolve) => {
     // 显示双道风窗
-    if (modalType === 'compressor' && compressorObj && compressorObj.group) {
-      group = compressorObj.group;
-
-      const oldCameraPosition = { x: -1000, y: 100, z: 500 };
-      const oldControlsPosition = { x: 0, y: 0, z: 0 };
-      model?.scene?.add(compressorObj.group);
-
-      let newCameraPosition = { x: 0, y: 0, z: 0 },
-        newControlsPosition = { x: 0, y: 0, z: 0 };
-      if (group.children.length == 6) {
-        newCameraPosition = { x: -1.7434040517387115, y: 46.41089142470955, z: 114.33388865672832 };
-        newControlsPosition = { x: -1.6994314417499141, y: -12.38981035037152, z: 10.110013648346193 };
-      } else if (group.children.length == 5) {
-        newCameraPosition = { x: -1.2741159124866441, y: 46.04318455197148, z: 95.25814325750986 };
-        newControlsPosition = { x: -1.699431, y: -12.38981, z: -1.699431 };
-      } else if (group.children.length == 4) {
-        newCameraPosition = { x: -1.7434040517387115, y: 46.41089142470955, z: 114.33388865672832 };
-        newControlsPosition = { x: -1.6994314417499141, y: -12.38981035037152, z: 10.110013648346193 };
-      } else if (group.children.length == 3) {
-        newCameraPosition = { x: -1.2741159124866441, y: 46.04318455197148, z: 70.25814325750986 };
-        newControlsPosition = { x: -1.699431, y: -12.38981, z: -1.699431 };
-      }
 
-      setTimeout(async () => {
-        resolve(null);
-        await animateCamera(oldCameraPosition, oldControlsPosition, newCameraPosition, newControlsPosition, model, 0.8);
-      }, 300);
+    if (modalType === 'compressor1' && compressorObj1 && model) {
+      group = compressorObj1.group;
+      model.renderAnimationScene = () => {
+        compressorObj1?.css3dRender?.render(model?.scene as THREE.Scene, model?.camera as THREE.PerspectiveCamera);
+      };
+      // if (model?.scene?.getObjectByName('compressor2')) {
+      //   if (compressorObj2) model?.scene?.remove(compressorObj2.group);
+      // }
+    }
+    if (modalType === 'compressor2' && compressorObj2 && model) {
+      group = compressorObj2.group;
+      model.renderAnimationScene = () => {
+        compressorObj2?.css3dRender?.render(model?.scene as THREE.Scene, model?.camera as THREE.PerspectiveCamera);
+      };
+      // if (model?.scene?.getObjectByName('compressor1')) {
+      //   if (compressorObj1) model?.scene?.remove(compressorObj1.group);
+      // }
+    }
+    model?.camera?.position.set(-1000, 100, 500);
+    const oldCameraPosition = { x: -1000, y: 100, z: 500 };
+    const oldControlsPosition = { x: 0, y: 0, z: 0 };
+    let newCameraPosition = { x: 0, y: 0, z: 0 },
+      newControlsPosition = { x: 0, y: 0, z: 0 };
+    if (group?.children.length == 6) {
+      newCameraPosition = { x: -1.7434040517387115, y: 46.41089142470955, z: 114.33388865672832 };
+      newControlsPosition = { x: -1.6994314417499141, y: -12.38981035037152, z: 10.110013648346193 };
+    } else if (group?.children.length == 5) {
+      newCameraPosition = { x: -1.2741159124866441, y: 46.04318455197148, z: 95.25814325750986 };
+      newControlsPosition = { x: -1.699431, y: -12.38981, z: -1.699431 };
+    } else if (group?.children.length == 4) {
+      newCameraPosition = { x: -2.0540155219473597, y: 40.869414434737195, z: 97.8637568969557 };
+      newControlsPosition = { x: -2.01631417107236, y: -9.544836585137801, z: 8.50481206883075 };
+    } else if (group?.children.length == 3) {
+      newCameraPosition = { x: -0.8949900855095378, y: 44.03213312648178, z: 78.19288289339097 };
+      newControlsPosition = { x: -0.4945087067301434, y: -9.810777468925817, z: -3.179982369050871 };
+    } else if (group?.children.length == 2) {
+      newCameraPosition = { x: -1.2741159124866441, y: 46.04318455197148, z: 70.25814325750986 };
+      newControlsPosition = { x: -1.699431, y: -12.38981, z: -1.699431 };
+    } else if (group?.children.length == 1) {
+      newCameraPosition = { x: -1.2741159124866441, y: 46.04318455197148, z: 70.25814325750986 };
+      newControlsPosition = { x: -1.699431, y: -12.38981, z: -1.699431 };
     }
+
+    setTimeout(async () => {
+      resolve(null);
+      if (group) model?.scene?.add(group);
+      await animateCamera(oldCameraPosition, oldControlsPosition, newCameraPosition, newControlsPosition, model, 0.8);
+    }, 300);
   });
 };
 
-export const mountedThree = (compressorNum) => {
+export const clearCssText = () => {
+  if (modalType === 'compressor' && compressorObj1) {
+    return compressorObj1.clearCssText.call(compressorObj1);
+  }
+};
+
+export const mountedThree = (monitorDataGroupNum1, monitorDataGroupNum2) => {
   return new Promise(async (resolve) => {
-    model = new UseThree('#compressor3D', '#compressorCss3D');
+    model = new UseThree('#compressor3D');
     model.setEnvMap('test1');
     model.renderer.toneMappingExposure = 1.0;
     model.canvasContainer?.addEventListener('mousedown', mouseEvent);
     model.canvasContainer?.addEventListener('pointerup', mouseUp);
-    compressorObj = new Nitrogen(model);
-    await compressorObj.mountedThree(compressorNum);
-    setModelType('compressor');
-    resolve(null);
+    compressorObj1 = new Nitrogen(model);
+    await compressorObj1.mountedThree(monitorDataGroupNum1, '#compressorCss3D');
+    compressorObj1.modelName = 'compressor1';
 
+    compressorObj2 = new Nitrogen(model);
+    await compressorObj2.mountedThree(monitorDataGroupNum2, '#compressorCss3D1');
+    compressorObj2.modelName = 'compressor2';
+    resolve(null);
+    // render();
+    model.animate();
     // 添加摄像机辅助
 
     // const helper = new THREE.CameraHelper(model.camera as THREE.Camera);
     // model?.scene?.add(helper);
-
-    render();
   });
 };
 
@@ -126,7 +170,7 @@ export const destroy = () => {
   if (model) {
     model.isRender = false;
     console.log('场景销毁前信息----------->', model.renderer?.info);
-    compressorObj?.destroy();
+    compressorObj1?.destroy();
     group = undefined;
     model.destroy();
     model = undefined;

+ 32 - 0
src/views/vent/monitorManager/obfurage1Monitor/detail.vue

@@ -0,0 +1,32 @@
+<template>
+  <div style="width: 100%; height: calc(100vh - 200px); display: flex; justify-content: center; align-items: center">
+    <a-spin :spinning="loading" />
+    <div id="fengmen3D" v-show="!loading"> </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, onUnmounted } from 'vue';
+  import UseThree from '../../../../utils/threejs/useThree';
+
+  const loading = ref(false);
+  let model;
+
+  onMounted(() => {
+    model = new UseThree('#fengmen3D');
+    // model.setEnvMap('test');
+    //   model.setCustomMaterial = setCustomMaterial
+    loading.value = true;
+    model.setGLTFModel('9f-processed').then(() => {
+      // 模型加载成功
+      loading.value = false;
+    });
+  });
+
+  onUnmounted(() => {
+    if (model) {
+      model.destroy();
+    }
+  });
+</script>
+<style scoped lang="less"></style>

+ 68 - 0
src/views/vent/monitorManager/obfurage1Monitor/gate.api.ts

@@ -0,0 +1,68 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/monitor/device',
+  save = '/safety/ventanalyGate/add',
+  edit = '/safety/ventanalyGate/edit',
+  deleteById = '/safety/ventanalyGate/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+  baseList = '/safety/ventanalyGate/list',
+  cameraList = '/safety/ventanalyCamera/list',
+  cameraAddrList = '/ventanaly-device/camera/info',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.post({ url: Api.list, params });
+
+export const cameraAddrList = (params) => defHttp.post({ url: Api.cameraAddrList, params });
+
+export const cameraList = (params) => defHttp.get({ url: Api.cameraList, params });
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.put({ url: url, params });
+};
+
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });

+ 331 - 0
src/views/vent/monitorManager/obfurage1Monitor/gate.data.ts

@@ -0,0 +1,331 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { rules } from '/@/utils/helper/validator';
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+    customRender: ({ text }) => {
+      if (text) {
+        return text.replace('风门', '快速密闭');
+      }
+      return '-';
+    },
+  },
+  {
+    title: '密闭门类型',
+    dataIndex: 'typeName',
+    customRender: ({ text }) => {
+      if (text) {
+        return text.replace('风门', '密闭门');
+      }
+      return '-';
+    },
+    width: 100,
+  },
+  {
+    title: '密闭门道数',
+    dataIndex: 'ndoorcount',
+    width: 100,
+    customRender: ({ text }) => {
+      if (!text && text != 0) {
+        return '-';
+      }
+    },
+    // customRender: render.renderAvatar,
+  },
+  {
+    title: '压差(Pa)',
+    dataIndex: 'frontRearDP',
+    width: 80,
+    customRender: ({ text }) => {
+      if (!text && text != 0) {
+        return '-';
+      }
+    },
+  },
+  {
+    title: '气源(MPa)',
+    dataIndex: 'sourcePressure',
+    width: 100,
+    customRender: ({ text }) => {
+      if (!text && text != 0) {
+        return '-';
+      }
+    },
+  },
+  {
+    title: '前门状态',
+    width: 150,
+    dataIndex: 'frontGateOpen',
+  },
+  {
+    title: '后门状态',
+    width: 100,
+    dataIndex: 'rearGateOpen',
+  },
+  {
+    title: '是否报警',
+    dataIndex: 'warnFlag',
+    width: 100,
+  },
+  {
+    title: '通信状态',
+    dataIndex: 'netStatus',
+    width: 100,
+  },
+  {
+    title: '时间',
+    dataIndex: 'readTime',
+    width: 100,
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+  },
+  {
+    title: '是否为常闭型',
+    dataIndex: 'bnormalclose',
+    width: 100,
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '是否为常闭型',
+    field: 'bnormalclose',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+    colProps: { span: 6 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+  },
+  {
+    label: '是否为常闭型',
+    field: 'bnormalclose',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+  {
+    label: '净宽',
+    field: 'fclearwidth',
+    component: 'Input',
+  },
+  {
+    label: '净高',
+    field: 'fclearheight',
+    component: 'Input',
+  },
+  {
+    label: '风门道数',
+    field: 'ndoorcount',
+    component: 'Input',
+  },
+  {
+    label: '所属分站',
+    field: 'stationname',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '点表',
+    field: 'strtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '监测类型',
+    field: 'monitorflag',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '是否模拟数据',
+    field: 'testflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+];
+
+export const formPasswordSchema: FormSchema[] = [
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    componentProps: { readOnly: true },
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    componentProps: {
+      placeholder: '请输入登录密码',
+    },
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+];
+
+export const formAgentSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'userName',
+    label: '用户名',
+    component: 'Input',
+    componentProps: {
+      readOnly: true,
+      allowClear: false,
+    },
+  },
+  {
+    field: 'agentUserName',
+    label: '代理人用户名',
+    required: true,
+    component: 'JSelectUser',
+    componentProps: {
+      rowKey: 'username',
+      labelKey: 'realname',
+      maxSelectCount: 10,
+    },
+  },
+  {
+    field: 'startTime',
+    label: '代理开始时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理开始时间',
+    },
+  },
+  {
+    field: 'endTime',
+    label: '代理结束时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理结束时间',
+    },
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'JDictSelectTag',
+    defaultValue: '1',
+    componentProps: {
+      dictCode: 'valid_status',
+      type: 'radioButton',
+    },
+  },
+];
+
+export const chartsColumns = [
+  {
+    legend: '压差',
+    seriesName: '(Pa)',
+    ymax: 100,
+    yname: 'Pa',
+    linetype: 'bar',
+    yaxispos: 'left',
+    color: '#37BCF2',
+    sort: 1,
+    xRotate: 0,
+    dataIndex: 'frontRearDP',
+  },
+  {
+    legend: '气源压力',
+    seriesName: '(MPa)',
+    ymax: 50,
+    yname: 'MPa',
+    linetype: 'line',
+    yaxispos: 'right',
+    color: '#FC4327',
+    sort: 2,
+    xRotate: 0,
+    dataIndex: 'sourcePressure',
+  },
+];

+ 503 - 0
src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.three.ts

@@ -0,0 +1,503 @@
+import * as THREE from 'three';
+import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import UseThree from '../../../../utils/threejs/useThree';
+import { drawHot } from '/@/utils/threejs/util';
+
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:10px;right:10px;z-index:99999999999999';
+
+class Fm2 {
+  modelName = 'fm2';
+  model; //
+  group;
+  isLRAnimation = true; // 是否开启左右摇摆动画
+  direction = 1; // 摇摆方向
+  animationTimer: NodeJS.Timeout | null = null; // 摇摆开启定时器
+  deviceDetailCSS3D;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  fmClock = new THREE.Clock();
+  mixers: THREE.AnimationMixer | undefined;
+
+  clipActionArr = {
+    frontDoor: null as unknown as THREE.AnimationAction,
+    centerDoor: null as unknown as THREE.AnimationAction,
+    backDoor: null as unknown as THREE.AnimationAction,
+  };
+
+  constructor(model) {
+    this.model = model;
+  }
+
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+    directionalLight.position.set(-0.8, 23, 3.9);
+    this.group?.add(directionalLight);
+    directionalLight.target = this.group as THREE.Object3D;
+
+    // const pointLight2 = new THREE.PointLight(0xffeeee, 1, 310);
+    // pointLight2.position.set(-1.5, 2, -0.9);
+    // pointLight2.shadow.bias = 0.05;
+    // this.group?.add(pointLight2);
+
+    // const pointLight3 = new THREE.PointLight(0xffeeee, 1, 310);
+    // pointLight3.position.set(1.3, 2, -0.9);
+    // pointLight3.shadow.bias = 0.05;
+    // this.group?.add(pointLight3);
+
+    // const pointLight4 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight4.position.set(4.3, 1, -0.9);
+    // pointLight4.shadow.bias = 0.05;
+    // this.group?.add(pointLight4);
+
+    // const pointLight5 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight5.position.set(4.3, 1, -0.9);
+    // pointLight5.shadow.bias = 0.05;
+    // this.group?.add(pointLight5);
+
+    // const pointLight6 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight6.position.set(-4.4, 1, -0.9);
+    // pointLight6.shadow.bias = 0.05;
+    // this.group?.add(pointLight6);
+  }
+  // 重置摄像头
+  resetCamera() {
+    this.model.camera.far = 274;
+    this.model.orbitControls?.update();
+    this.model.camera.updateProjectionMatrix();
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-20, 20, 9);
+  }
+
+  /* 添加监控数据 */
+  addMonitorText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `远程控制自动风门`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 120,
+        y: 100,
+      },
+      {
+        text: `压力(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 155,
+      },
+      {
+        text: `${selectData.frontRearDP}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 290,
+        y: 155,
+      },
+      {
+        text: `动力源压力(MPa): `,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 215,
+      },
+      {
+        text: ` ${selectData.sourcePressure}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 280,
+        y: 215,
+      },
+      {
+        text: `故障诊断:`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 275,
+      },
+      {
+        text: `${selectData.fault}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 280,
+        y: 275,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 20,
+        y: 325,
+      },
+    ];
+
+    //
+    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      textMap.colorSpace = THREE.SRGBColorSpace;
+      const textMaterial = new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.FrontSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.002, 0.002, 0.002);
+        planeMesh.position.set(4.155, 0.09, -0.39);
+        this.group.add(planeMesh);
+      }
+    });
+  }
+
+  /** 添加热点 */
+  drawHots() {
+    const hotPositions = [
+      { x: -0.37, y: 0.26, z: -0.32 },
+      { x: 0.28, y: -0.2, z: -0.43 },
+      { x: 0.55, y: -0.22, z: -0.38 },
+    ];
+    for (let i = 0; i < 3; i++) {
+      const hotPoint = drawHot(0.1);
+      const position = hotPositions[i];
+      // hotPoint.scale.set(0.3, 0.3, 0.3);
+      hotPoint.position.set(position.x, position.y, position.z);
+      hotPoint.name = 'hotPoint' + i;
+      this.group?.add(hotPoint);
+    }
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00002 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00002 * 30 * this.direction;
+      }
+    }
+
+    // 风门开关动画
+    if (this.mixers && this.fmClock.running) this.mixers.update(2);
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' })
+          // 双击,视频放大
+          // if (this.player1) {
+          //   this.player1.requestFullscreen();
+          // }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      } else if (mesh.name === 'player2') {
+        if (new Date().getTime() - this.playerStartClickTime2 < 400) {
+          // model.orbitControls?.dispatchEvent.call(model.orbitControls, { type: 'end' })
+          // 双击,视频放大
+          // if (this.player2) {
+          //   this.player2.requestFullscreen();
+          // }
+        }
+        this.playerStartClickTime2 = new Date().getTime();
+        return true;
+      } else if (mesh.name.startsWith('hotPoint')) {
+        // if (this.deviceDetailCSS3D) {
+        //   this.deviceDetailCSS3D.position.set(mesh.position.x + 0.035, mesh.position.y + 0.66, mesh.position.z + 0.02);
+        //   console.log('[ deviceDetailCSS3D.position ] >', this.deviceDetailCSS3D.position);
+        //   this.deviceDetailCSS3D.visible = true;
+        //   return true;
+        // }
+      } else {
+        // this.deviceDetailCSS3D.visible = false;
+        console.log('[ 点击事件 ] >');
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    const fmGroup = this.group?.getObjectByName('fmThree');
+    if (fmGroup) {
+      const tracks = fmGroup.animations[0].tracks;
+      const fontTracks: any[] = [],
+        centerTracks: any[] = [],
+        backTracks: any[] = [];
+      for (let i = 0; i < tracks.length; i++) {
+        const track = tracks[i];
+        if (track.name.startsWith('door01')) {
+          fontTracks.push(track);
+        } else if (track.name.startsWith('door02')) {
+          centerTracks.push(track);
+        } else if (track.name.startsWith('door03')) {
+          backTracks.push(track);
+        }
+      }
+      this.mixers = new THREE.AnimationMixer(fmGroup);
+
+      const frontDoor = new THREE.AnimationClip('frontDoor', 4, fontTracks);
+      const frontClipAction = this.mixers.clipAction(frontDoor, fmGroup);
+      frontClipAction.clampWhenFinished = true;
+      frontClipAction.loop = THREE.LoopOnce;
+      this.clipActionArr.frontDoor = frontClipAction;
+
+      const backDoor = new THREE.AnimationClip('backDoor', 4, backTracks);
+
+      const backClipAction = this.mixers.clipAction(backDoor, fmGroup);
+      backClipAction.clampWhenFinished = true;
+      backClipAction.loop = THREE.LoopOnce;
+      this.clipActionArr.backDoor = backClipAction;
+
+      const centerDoor = new THREE.AnimationClip('centerDoor', 4, centerTracks);
+      const centerClipAction = this.mixers.clipAction(centerDoor, fmGroup);
+      centerClipAction.clampWhenFinished = true;
+      centerClipAction.loop = THREE.LoopOnce;
+      this.clipActionArr.centerDoor = centerClipAction;
+    }
+  }
+
+  deviceDetailCard(position = { x: 0, y: 0, z: 0 }) {
+    const element = document.getElementById('deviceCard') as HTMLElement;
+    this.deviceDetailCSS3D = new CSS2DObject(element);
+    this.deviceDetailCSS3D.name = 'deviceCard';
+    this.deviceDetailCSS3D.position.set(position.x, position.y, position.z);
+    this.deviceDetailCSS3D.visible = false;
+    // this.model.scene.add(this.deviceDetailCSS3D);
+    this.group.add(this.deviceDetailCSS3D);
+  }
+
+  // 播放动画
+  play(handlerState, timeScale = 0.01) {
+    let handler = () => {};
+    switch (handlerState) {
+      case 1: // 打开前门
+        handler = () => {
+          this.clipActionArr.frontDoor.paused = true;
+          this.clipActionArr.frontDoor.reset();
+          this.clipActionArr.frontDoor.time = 1.2;
+          this.clipActionArr.frontDoor.timeScale = timeScale;
+          this.clipActionArr.frontDoor.clampWhenFinished = true;
+          this.clipActionArr.frontDoor.play();
+          this.fmClock.start();
+        };
+        break;
+      case 2: // 关闭前门
+        handler = () => {
+          this.clipActionArr.frontDoor.paused = true;
+          this.clipActionArr.frontDoor.reset(); //
+          this.clipActionArr.frontDoor.time = 4;
+          this.clipActionArr.frontDoor.timeScale = -timeScale;
+          this.clipActionArr.frontDoor.clampWhenFinished = true;
+          this.clipActionArr.frontDoor.play();
+          this.fmClock.start();
+        };
+        break;
+      case 3: // 打开后门
+        handler = () => {
+          this.clipActionArr.backDoor.paused = true;
+          this.clipActionArr.backDoor.reset();
+          this.clipActionArr.backDoor.time = 1.2;
+          this.clipActionArr.backDoor.timeScale = timeScale;
+          this.clipActionArr.backDoor.clampWhenFinished = true;
+          this.clipActionArr.backDoor.play();
+          this.fmClock.start();
+        };
+        break;
+      case 4: // 关闭后门
+        handler = () => {
+          this.clipActionArr.backDoor.paused = true;
+          this.clipActionArr.backDoor.reset();
+          this.clipActionArr.backDoor.time = 4;
+          this.clipActionArr.backDoor.timeScale = -timeScale;
+          this.clipActionArr.backDoor.clampWhenFinished = true;
+          this.clipActionArr.backDoor.play();
+          this.fmClock.start();
+        };
+        break;
+      // case 5: // 打开前后门
+      //   handler = () => {
+      //     this.clipActionArr.backDoor.paused = true;
+      //     this.clipActionArr.frontDoor.paused = true;
+
+      //     this.clipActionArr.frontDoor.reset();
+      //     this.clipActionArr.frontDoor.time = 0;
+      //     this.clipActionArr.frontDoor.timeScale = 0.01;
+      //     this.clipActionArr.frontDoor.clampWhenFinished = true;
+      //     this.clipActionArr.frontDoor.play();
+
+      //     this.clipActionArr.backDoor.reset();
+      //     this.clipActionArr.backDoor.time = 0;
+      //     this.clipActionArr.backDoor.timeScale = 0.01;
+      //     this.clipActionArr.backDoor.clampWhenFinished = true;
+      //     this.clipActionArr.backDoor.play();
+      //     this.fmClock.start();
+      //   };
+      //   break;
+      // case 6: // 关闭前后门
+      //   handler = () => {
+      //     this.clipActionArr.backDoor.paused = true;
+      //     this.clipActionArr.frontDoor.paused = true;
+
+      //     this.clipActionArr.frontDoor.reset();
+      //     this.clipActionArr.frontDoor.time = 4;
+      //     this.clipActionArr.frontDoor.timeScale = -0.01;
+      //     this.clipActionArr.frontDoor.clampWhenFinished = true;
+      //     this.clipActionArr.frontDoor.play();
+      //     this.clipActionArr.backDoor.reset();
+      //     this.clipActionArr.backDoor.time = 4;
+      //     this.clipActionArr.backDoor.timeScale = -0.01;
+      //     this.clipActionArr.backDoor.clampWhenFinished = true;
+      //     this.clipActionArr.backDoor.play();
+      //     this.fmClock.start();
+      //   };
+      //   break;
+      default:
+    }
+
+    handler();
+    // model.clock.start();
+    // const honglvdeng = group.getObjectByName('honglvdeng');
+    // const material = honglvdeng.material;
+    // setTimeout(() => {
+    //   if (handlerState === 2 || handlerState === 4 || handlerState === 6) {
+    //     material.color = new THREE.Color(0x00ff00);
+    //   } else {
+    //     material.color = new THREE.Color(0xff0000);
+    //   }
+    // }, 1000);
+  }
+
+  async initCamera(dom1?) {
+    const videoPlayer1 = dom1;
+    let monitorPlane: THREE.Mesh | null = null;
+    const canvas = await getTextCanvas(320, 180, '', 'noSinge.png');
+    const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+    const textMaterial = new THREE.MeshBasicMaterial({
+      map: textMap, // 设置纹理贴图
+      transparent: true,
+      side: THREE.DoubleSide, // 这里是双面渲染的意思
+    });
+    textMaterial.blending = THREE.CustomBlending;
+    monitorPlane = this.group?.getObjectByName('noPlayer');
+    if (monitorPlane) {
+      monitorPlane.material = textMaterial;
+    } else {
+      const planeGeometry = new THREE.PlaneGeometry(100, 100); // 平面3维几何体PlaneGeometry
+      monitorPlane = new THREE.Mesh(planeGeometry, textMaterial);
+      textMaterial.dispose();
+      planeGeometry.dispose();
+    }
+    const videoPlayer = this.group.getObjectByName('player1');
+    if (videoPlayer) {
+      this.model.clearMesh(videoPlayer);
+      this.group.remove(videoPlayer);
+    }
+    const noPlayer1 = this.group.getObjectByName('noPlayer1');
+    if (noPlayer1) {
+      this.model.clearMesh(noPlayer1);
+      this.group.remove(noPlayer1);
+    }
+    if (!videoPlayer1 && videoPlayer1 === null) {
+      monitorPlane.name = 'noPlayer1';
+      monitorPlane.scale.set(0.0085, 0.0056, 0.012);
+      monitorPlane.position.set(-4.23, 0.02, -0.39);
+      this.group?.add(monitorPlane);
+    } else if (videoPlayer1) {
+      const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+      if (mesh) {
+        mesh?.scale.set(-0.028, 0.0285, 1);
+        mesh?.position.set(-4.238, 0.02, -0.4);
+        mesh.rotation.y = -Math.PI;
+        this.group.add(mesh);
+      }
+    }
+  }
+
+  mountedThree() {
+    this.group = new THREE.Object3D();
+    this.group.name = this.modelName;
+    return new Promise((resolve) => {
+      this.model.setGLTFModel(['fmThree', 'fmThreeBase'], this.group).then(() => {
+        this.group.name = 'fm2';
+        this.setModalPosition();
+        // 初始化左右摇摆动画;
+        this.initAnimation();
+        // this.drawHots();
+        this.addLight();
+        // this.deviceDetailCard();
+        this.model.animate();
+        if (this.model.camera && this.model.camera.layers.mask == -1) this.model.camera.layers.toggle(1);
+        this.initCamera();
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    if (this.model) {
+      if (this.mixers) {
+        const fmGroup = this.group?.getObjectByName('fmThree');
+        this.mixers.uncacheClip(this.clipActionArr.frontDoor.getClip());
+        this.mixers.uncacheClip(this.clipActionArr.backDoor.getClip());
+        this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), fmGroup);
+        this.mixers.uncacheAction(this.clipActionArr.backDoor.getClip(), fmGroup);
+        this.mixers.uncacheRoot(fmGroup);
+
+        if (this.model.animations[0]) this.model.animations[0].tracks = [];
+      }
+      this.model.clearGroup(this.group);
+      this.clipActionArr.backDoor = undefined;
+      this.clipActionArr.frontDoor = undefined;
+      this.mixers = undefined;
+    }
+  }
+}
+export default Fm2;

+ 185 - 0
src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.ts

@@ -0,0 +1,185 @@
+import * as THREE from 'three';
+import UseThree from '../../../../utils/threejs/useThree';
+import Fm1 from './gate.threejs.two';
+import Fm2 from './gate.threejs.three';
+import { animateCamera } from '/@/utils/threejs/util';
+import useEvent from '../../../../utils/threejs/useEvent';
+
+// 模型对象、 文字对象
+let model,
+  fm1: Fm1,
+  fm2: Fm2,
+  group: THREE.Object3D,
+  fmType = '';
+
+const { mouseDownFn } = useEvent();
+
+// 初始化左右摇摆动画
+const startAnimation = () => {
+  // 定义鼠标点击事件
+  model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+  model.canvasContainer?.addEventListener('pointerup', (event) => {
+    event.stopPropagation();
+    // 单道、 双道
+    if (fmType === 'fm1') {
+      fm1.mouseUpModel.call(fm1);
+    } else if (fmType === 'fm2') {
+      fm2.mouseUpModel.call(fm2);
+    }
+  });
+};
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  if (event.button == 0) {
+    mouseDownFn(model, group, event, (intersects) => {
+      if (fmType === 'fm1' && fm1) {
+        fm1.mousedownModel.call(fm1, intersects);
+      } else if (fmType === 'fm2' && fm2) {
+        fm2.mousedownModel.call(fm2, intersects);
+      }
+    });
+    // console.log('摄像头控制信息', model.orbitControls, model.camera);
+  }
+};
+
+export const addMonitorText = (selectData) => {
+  if (fmType === 'fm1' && fm1) {
+    return fm1.addMonitorText.call(fm1, selectData);
+  } else if (fmType === 'fm2' && fm2) {
+    return fm2.addMonitorText.call(fm2, selectData);
+  }
+};
+
+export const deviceDetailCard = () => {
+  if (fmType === 'fm1') {
+    return fm1.deviceDetailCard.call(fm1);
+  } else {
+    // return fm2.addMonitorText.call(fm2);
+  }
+};
+
+export const play = (handlerState, flag?) => {
+  if (fmType === 'fm1' && fm1) {
+    return fm1.play.call(fm1, handlerState, flag);
+  } else if (fmType === 'fm2' && fm2) {
+    return fm2.play.call(fm2, handlerState, flag);
+  }
+};
+
+// 切换风门类型
+export const setModelType = (type) => {
+  fmType = type;
+  return new Promise((resolve) => {
+    // 暂停风门1动画
+
+    if (fmType === 'fm1' && fm1 && fm1.group) {
+      fm1.clipActionArr.frontDoor.reset();
+      fm1.clipActionArr.frontDoor.time = 0.5;
+      fm1.clipActionArr.backDoor.reset();
+      fm1.clipActionArr.backDoor.time = 0.5;
+      fm1.clipActionArr.frontDoor.stop();
+      fm1.clipActionArr.backDoor.stop();
+
+      if (fm1.frontDamperOpenMesh) fm1.frontDamperOpenMesh.visible = false;
+      if (fm1.frontDamperClosedMesh) fm1.frontDamperClosedMesh.visible = true;
+      if (fm1.backDamperOpenMesh) fm1.backDamperOpenMesh.visible = false;
+      if (fm1.backDamperClosedMesh) fm1.backDamperClosedMesh.visible = true;
+
+      model.startAnimation = fm1.render.bind(fm1);
+      group = fm1.group;
+      group.rotation.y = 0;
+      if (model.scene.getObjectByName('fm2')) {
+        model.scene.remove(fm2.group);
+      }
+      const oldCameraPosition = { x: -1000, y: 100, z: 500 };
+      setTimeout(async () => {
+        resolve(null);
+        model.scene.add(fm1.group);
+        await animateCamera(
+          oldCameraPosition,
+          { x: 0, y: 0, z: 0 },
+          { x: 50.99, y: 69.32, z: 93.61 },
+          { x: -10.04, y: -14.38, z: -31.4 },
+          model,
+          0.8
+        );
+      }, 300);
+    } else if (fmType === 'fm2' && fm2 && fm2.group) {
+      
+      fm2.clipActionArr.frontDoor.reset();
+      fm2.clipActionArr.frontDoor.time = 0.5;
+      fm2.clipActionArr.backDoor.reset();
+      fm2.clipActionArr.backDoor.time = 0.5;
+      fm2.clipActionArr.centerDoor.reset();
+      fm2.clipActionArr.centerDoor.time = 0.5;
+      fm2.clipActionArr.frontDoor.stop();
+      fm2.clipActionArr.backDoor.stop();
+      fm2.clipActionArr.centerDoor.stop();
+
+      // 显示单道风窗
+      model.startAnimation = fm2.render.bind(fm2);
+      group = fm2.group;
+      if (model.scene.getObjectByName('fm1')) {
+        model.scene.remove(fm1.group);
+      }
+      const oldCameraPosition = { x: -761, y: 569, z: 871 };
+      setTimeout(async () => {
+        resolve(null);
+        model.scene.add(fm2.group);
+        const position = { x: -2.28, y: -0.91, z: -5.68 };
+        await animateCamera(
+          oldCameraPosition,
+          { x: -2.27, y: -0.91, z: -5.67 },
+          { x: 66.257, y: 57.539, z: 94.313 },
+          { x: position.x, y: position.y, z: position.z },
+          model,
+          0.6
+        );
+      }, 300);
+    }
+  });
+};
+
+export const initCameraCanvas = async (playerVal1?, playerVal2?) => {
+  if (fmType === 'fm1' && fm1) {
+    return await fm1.initCamera.call(fm1, playerVal1, playerVal2);
+  } else if (fmType === 'fm2' && fm2) {
+    console.log('fm2-------',playerVal1);
+    return fm2.initCamera.call(fm2, playerVal1);
+  }
+};
+
+export const mountedThree = () => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#damper3D', '', '#deviceDetail');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 1.0;
+    model.camera.position.set(100, 0, 1000);
+    // 单道、 双道
+    fm1 = new Fm1(model);
+    await fm1.mountedThree();
+    fm2 = new Fm2(model);
+    await fm2.mountedThree();
+
+    model.animate();
+    resolve(null);
+    startAnimation();
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    model.isRender = false;
+
+    console.log('场景销毁前信息----------->', model.renderer?.info);
+    if (fm1) fm1.destroy();
+    if (fm2) fm2.destroy();
+    fm1 = null;
+    fm2 = null;
+    group = null;
+    model.mixers = [];
+    model.destroy();
+  }
+  model = null;
+};

+ 597 - 0
src/views/vent/monitorManager/obfurage1Monitor/gate.threejs.two.ts

@@ -0,0 +1,597 @@
+import * as THREE from 'three';
+import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+import UseThree from '../../../../utils/threejs/useThree';
+import { drawHot } from '/@/utils/threejs/util';
+import { useAppStore } from '/@/store/modules/app';
+
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class Fm1 {
+  modelName = 'fm1';
+  model; //
+  group;
+  isLRAnimation = true; // 是否开启左右摇摆动画
+  direction = 1; // 摇摆方向
+  animationTimer: NodeJS.Timeout | null = null; // 摇摆开启定时器
+  player1;
+  player2;
+  deviceDetailCSS3D;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  fmClock = new THREE.Clock();
+  mixers: THREE.AnimationMixer | undefined;
+  appStore = useAppStore();
+
+  backDamperOpenMesh;
+  backDamperClosedMesh;
+  frontDamperOpenMesh;
+  frontDamperClosedMesh;
+
+  clipActionArr = {
+    frontDoor: null as unknown as THREE.AnimationAction,
+    backDoor: null as unknown as THREE.AnimationAction,
+  };
+
+  constructor(model) {
+    this.model = model;
+  }
+
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
+    directionalLight.position.set(344, 690, 344);
+    this.group?.add(directionalLight);
+    directionalLight.target = this.group as THREE.Object3D;
+
+    const pointLight2 = new THREE.PointLight(0xffeeee, 1, 300);
+    pointLight2.position.set(-4, 10, 1.8);
+    pointLight2.shadow.bias = 0.05;
+    this.group?.add(pointLight2);
+
+    const pointLight3 = new THREE.PointLight(0xffeeee, 1, 200);
+    pointLight3.position.set(-0.5, -0.5, 0.75);
+    pointLight3.shadow.bias = 0.05;
+    this.group?.add(pointLight3);
+
+    // const pointLight4 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight4.position.set(4.3, 1, -0.9);
+    // pointLight4.shadow.bias = 0.05;
+    // this.group?.add(pointLight4);
+
+    // const pointLight5 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight5.position.set(4.3, 1, -0.9);
+    // pointLight5.shadow.bias = 0.05;
+    // this.group?.add(pointLight5);
+
+    // const pointLight6 = new THREE.PointLight(0xffeeee, 1, 150);
+    // pointLight6.position.set(-4.4, 1, -0.9);
+    // pointLight6.shadow.bias = 0.05;
+    // this.group?.add(pointLight6);
+
+    // const pointLightHelper2 = new THREE.PointLightHelper(pointLight2, 1);
+    // this.model.scene?.add(pointLightHelper2);
+
+    // gui.add(pointLight2.position, 'x', -300, 300);
+    // gui.add(pointLight2.position, 'y', -300, 300);
+    // gui.add(pointLight2.position, 'z', -300, 300);
+
+    // gui.add(pointLight3.position, 'x', -300, 300);
+    // gui.add(pointLight3.position, 'y', -300, 300);
+    // gui.add(pointLight3.position, 'z', -300, 300);
+  }
+  // 重置摄像头
+  resetCamera() {
+    this.model.camera.far = 274;
+    this.model.orbitControls?.update();
+    this.model.camera.updateProjectionMatrix();
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-20, 20, 9);
+  }
+
+  /* 添加监控数据 */
+  addMonitorText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `远程控制快速密闭门`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 120,
+        y: 100,
+      },
+      {
+        text: `压力(Pa):`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 155,
+      },
+      {
+        text: `${selectData.frontRearDP}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 290,
+        y: 155,
+      },
+      {
+        text: `动力源压力(MPa): `,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 215,
+      },
+      {
+        text: ` ${selectData.sourcePressure}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 280,
+        y: 215,
+      },
+      {
+        text: `故障诊断:`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 275,
+      },
+      {
+        text: `${selectData.fault}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 280,
+        y: 275,
+      },
+      {
+        text: `煤炭科学技术研究院有限公司研制`,
+        font: 'normal 28px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 20,
+        y: 325,
+      },
+    ];
+
+    //
+    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      textMap.colorSpace = THREE.SRGBColorSpace;
+      const textMaterial = new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.FrontSide, // 这里是双面渲染的意思
+      });
+      textMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group.getObjectByName('monitorText');
+      if (monitorPlane) {
+        monitorPlane.material = textMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
+        planeMesh.name = 'monitorText';
+        planeMesh.scale.set(0.002, 0.002, 0.002);
+        planeMesh.position.set(-1.255, 0.09, -0.41);
+        this.group.add(planeMesh);
+      }
+      textMap.dispose();
+    });
+  }
+
+  /** 添加热点 */
+  drawHots() {
+    const hotPositions = [
+      { x: -0.37, y: 0.26, z: -0.32 },
+      { x: 0.28, y: -0.2, z: -0.43 },
+      { x: 0.55, y: -0.22, z: -0.38 },
+    ];
+    for (let i = 0; i < 3; i++) {
+      const hotPoint = drawHot(0.1);
+      const position = hotPositions[i];
+      hotPoint.scale.set(0.1, 0.1, 0.1);
+      hotPoint.position.set(position.x, position.y, position.z);
+      hotPoint.name = 'hotPoint' + i;
+      this.group?.add(hotPoint);
+    }
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.isLRAnimation && this.group) {
+      // 左右摇摆动画
+      if (Math.abs(this.group.rotation.y) >= 0.2) {
+        this.direction = -this.direction;
+        this.group.rotation.y += 0.00002 * 30 * this.direction;
+      } else {
+        this.group.rotation.y += 0.00002 * 30 * this.direction;
+      }
+    }
+
+    if (this.mixers && this.fmClock.running) {
+      this.mixers.update(2);
+    }
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      // if (mesh.name === 'player1') {
+      //   if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+      //     // 双击,视频放大
+      //     if (this.player1) {
+      //       this.player1.requestPictureInPicture();
+      //     }
+      //   }
+      //   this.playerStartClickTime1 = new Date().getTime();
+      //   return true;
+      // } else if (mesh.name === 'player2') {
+      //   if (new Date().getTime() - this.playerStartClickTime2 < 400) {
+      //     // 双击,视频放大
+      //     if (this.player2) {
+      //       this.player2.requestPictureInPicture();
+      //     }
+      //   }
+      //   this.playerStartClickTime2 = new Date().getTime();
+      //   return true;
+      // } else if (mesh.name.startsWith('hotPoint')) {
+      //   if (this.deviceDetailCSS3D) {
+      //     this.deviceDetailCSS3D.position.set(mesh.position.x + 0.035, mesh.position.y + 0.68, mesh.position.z + 0.02);
+      //     console.log('[ deviceDetailCSS3D.position ] >', this.deviceDetailCSS3D.position);
+      //     this.deviceDetailCSS3D.visible = true;
+      //     return true;
+      //   }
+      // } else {
+      //   if (this.deviceDetailCSS3D) this.deviceDetailCSS3D.visible = false;
+      //   console.log('[ 点击事件 ] >');
+      // }
+      return false;
+    });
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {
+    debugger
+    const fmGroup = this.group?.getObjectByName('quickObfurage-door');
+    if (fmGroup) {
+      const tracks = fmGroup.animations[0].tracks;
+      const fontTracks: any[] = [],
+        backTracks: any[] = [];
+      for (let i = 0; i < tracks.length; i++) {
+        const track = tracks[i];
+        if (track.name.startsWith('qianmen')) {
+          fontTracks.push(track);
+        } else if (track.name.startsWith('houmen')) {
+          backTracks.push(track);
+        }
+      }
+
+      this.mixers = new THREE.AnimationMixer(fmGroup);
+
+      const frontDoor = new THREE.AnimationClip('frontDoor', 4, fontTracks);
+      const frontClipAction = this.mixers.clipAction(frontDoor, fmGroup);
+      frontClipAction.clampWhenFinished = true;
+      frontClipAction.loop = THREE.LoopOnce;
+      this.clipActionArr.frontDoor = frontClipAction;
+
+      const backDoor = new THREE.AnimationClip('backDoor', 4, backTracks);
+      const backClipAction = this.mixers.clipAction(backDoor, fmGroup);
+      backClipAction.clampWhenFinished = true;
+      backClipAction.loop = THREE.LoopOnce;
+      this.clipActionArr.backDoor = backClipAction;
+    }
+  }
+
+  deviceDetailCard(position = { x: 0, y: 0, z: 0 }) {
+    const element = document.getElementById('deviceCard') as HTMLElement;
+    if (element) {
+      this.deviceDetailCSS3D = new CSS2DObject(element);
+      this.deviceDetailCSS3D.name = 'deviceCard';
+      this.deviceDetailCSS3D.position.set(position.x, position.y, position.z);
+      this.deviceDetailCSS3D.visible = false;
+      // this.model.scene.add(this.deviceDetailCSS3D);
+      this.group.add(this.deviceDetailCSS3D);
+    }
+  }
+
+  // 播放动画
+  play(handlerState, timeScale = 0.01) {
+    let handler = () => {};
+    switch (handlerState) {
+      case 1: // 打开前门
+        handler = () => {
+          this.clipActionArr.frontDoor.paused = true;
+          this.clipActionArr.frontDoor.reset();
+          this.clipActionArr.frontDoor.time = 1.2;
+          this.clipActionArr.frontDoor.timeScale = timeScale;
+          // this.clipActionArr.frontDoor.clampWhenFinished = true;
+          this.clipActionArr.frontDoor.play();
+          this.fmClock.start();
+
+          // 显示打开前门文字
+          if (this.frontDamperOpenMesh) this.frontDamperOpenMesh.visible = true;
+          if (this.frontDamperClosedMesh) this.frontDamperClosedMesh.visible = false;
+        };
+        break;
+      case 2: // 关闭前门
+        handler = () => {
+          this.clipActionArr.frontDoor.paused = true;
+          this.clipActionArr.frontDoor.reset(); //
+          this.clipActionArr.frontDoor.time = 4;
+          this.clipActionArr.frontDoor.timeScale = -timeScale;
+          // this.clipActionArr.frontDoor.clampWhenFinished = true;
+          this.clipActionArr.frontDoor.play();
+          this.fmClock.start();
+
+          if (this.frontDamperOpenMesh) this.frontDamperOpenMesh.visible = false;
+          if (this.frontDamperClosedMesh) this.frontDamperClosedMesh.visible = true;
+        };
+        break;
+      case 3: // 打开后门
+        handler = () => {
+          this.clipActionArr.backDoor.paused = true;
+          this.clipActionArr.backDoor.reset();
+          this.clipActionArr.backDoor.time = 1.2;
+          this.clipActionArr.backDoor.timeScale = timeScale;
+          // this.clipActionArr.backDoor.clampWhenFinished = true;
+          this.clipActionArr.backDoor.play();
+          this.fmClock.start();
+
+          if (this.backDamperOpenMesh) this.backDamperOpenMesh.visible = true;
+          if (this.backDamperClosedMesh) this.backDamperClosedMesh.visible = false;
+        };
+        break;
+      case 4: // 关闭后门
+        handler = () => {
+          this.clipActionArr.backDoor.paused = true;
+          this.clipActionArr.backDoor.reset();
+          this.clipActionArr.backDoor.time = 4;
+          this.clipActionArr.backDoor.timeScale = -timeScale;
+          // this.clipActionArr.backDoor.clampWhenFinished = true;
+          this.clipActionArr.backDoor.play();
+          this.fmClock.start();
+
+          if (this.backDamperOpenMesh) this.backDamperOpenMesh.visible = false;
+          if (this.backDamperClosedMesh) this.backDamperClosedMesh.visible = true;
+        };
+        break;
+      // case 5: // 打开前后门
+      //   handler = () => {
+      //     this.clipActionArr.backDoor.paused = true;
+      //     this.clipActionArr.frontDoor.paused = true;
+
+      //     this.clipActionArr.frontDoor.reset();
+      //     this.clipActionArr.frontDoor.time = 0;
+      //     this.clipActionArr.frontDoor.timeScale = 0.01;
+      //     this.clipActionArr.frontDoor.clampWhenFinished = true;
+      //     this.clipActionArr.frontDoor.play();
+
+      //     this.clipActionArr.backDoor.reset();
+      //     this.clipActionArr.backDoor.time = 0;
+      //     this.clipActionArr.backDoor.timeScale = 0.01;
+      //     this.clipActionArr.backDoor.clampWhenFinished = true;
+      //     this.clipActionArr.backDoor.play();
+      //     this.frontClock.start();
+      //     this.backClock.start();
+      //   };
+      //   break;
+      // case 6: // 关闭前后门
+      //   handler = () => {
+      //     debugger;
+      //     this.clipActionArr.backDoor.paused = true;
+      //     this.clipActionArr.frontDoor.paused = true;
+
+      //     this.clipActionArr.frontDoor.reset();
+      //     this.clipActionArr.frontDoor.time = 4;
+      //     this.clipActionArr.frontDoor.timeScale = -0.01;
+      //     this.clipActionArr.frontDoor.clampWhenFinished = true;
+      //     this.clipActionArr.frontDoor.play();
+      //     this.clipActionArr.backDoor.reset();
+      //     this.clipActionArr.backDoor.time = 4;
+      //     this.clipActionArr.backDoor.timeScale = -0.01;
+      //     this.clipActionArr.backDoor.clampWhenFinished = true;
+      //     this.clipActionArr.backDoor.play();
+      //     this.frontClock.start();
+      //     this.backClock.start();
+      //   };
+      //   break;
+      default:
+    }
+
+    handler();
+    // model.clock.start();
+    // const honglvdeng = group.getObjectByName('honglvdeng');
+    // const material = honglvdeng.material;
+    // setTimeout(() => {
+    //   if (handlerState === 2 || handlerState === 4 || handlerState === 6) {
+    //     material.color = new THREE.Color(0x00ff00);
+    //   } else {
+    //     material.color = new THREE.Color(0xff0000);
+    //   }
+    // }, 1000);
+  }
+
+  async initCamera(dom1?, dom2?) {
+    const videoPlayer1 = dom1;
+    const videoPlayer2 = dom2;
+    this.player1 = dom1;
+    this.player2 = dom2;
+    let monitorPlane: THREE.Mesh | null = null;
+    if (!videoPlayer1 || !videoPlayer2) {
+      const textArr = [
+        {
+          text: `无信号输入`,
+          font: 'normal 40px Arial',
+          color: '#009900',
+          strokeStyle: '#002200',
+          x: 170,
+          y: 40,
+        },
+      ];
+      const canvas = await getTextCanvas(320, 180, '', 'noSinge.png');
+
+      let textMaterial: THREE.MeshBasicMaterial | null = null;
+      if (canvas) {
+        const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+        textMaterial = new THREE.MeshBasicMaterial({
+          map: textMap, // 设置纹理贴图
+          transparent: true,
+          side: THREE.DoubleSide, // 这里是双面渲染的意思
+        });
+        textMaterial.blending = THREE.CustomBlending;
+
+        const planeGeometry = new THREE.PlaneGeometry(100, 100); // 平面3维几何体PlaneGeometry
+        monitorPlane = new THREE.Mesh(planeGeometry, textMaterial);
+
+        textMaterial.dispose();
+        planeGeometry.dispose();
+        textMap.dispose();
+      }
+    }
+    const player1 = this.group.getObjectByName('player1');
+    if (player1) {
+      this.model.clearMesh(player1);
+      this.group.remove(player1);
+    }
+    const noPlayer1 = this.group.getObjectByName('noPlayer1');
+    if (noPlayer1) {
+      this.model.clearMesh(noPlayer1);
+      this.group.remove(noPlayer1);
+    }
+    if (!videoPlayer1 && videoPlayer1 === null) {
+      if (monitorPlane && !this.group.getObjectByName('noPlayer1')) {
+        const planeMesh = monitorPlane.clone();
+        planeMesh.name = 'noPlayer1';
+        planeMesh.scale.set(0.0085, 0.0056, 0.012);
+        planeMesh.position.set(-4.23, 0.02, -0.39);
+        this.group?.add(planeMesh.clone());
+      }
+    } else if (videoPlayer1) {
+      try {
+        const mesh = renderVideo(this.group, videoPlayer1, 'player1');
+        if (mesh) {
+          mesh?.scale.set(-0.028, 0.0285, 1);
+          mesh?.position.set(-4.262, 0.02, -0.4);
+          mesh.rotation.y = -Math.PI;
+          this.group.add(mesh);
+        }
+      } catch (error) {
+        console.log('视频信号异常');
+      }
+    }
+    const player2 = this.group.getObjectByName('player2');
+    if (player2) {
+      this.model.clearMesh(player2);
+      this.group.remove(player2);
+    }
+    const noPlayer2 = this.group.getObjectByName('noPlayer2');
+    if (noPlayer2) {
+      this.model.clearMesh(noPlayer2);
+      this.group.remove(noPlayer2);
+    }
+    if (!videoPlayer2 && videoPlayer2 === null) {
+      if (monitorPlane && !this.group.getObjectByName('noPlayer2')) {
+        const planeMesh = monitorPlane.clone();
+        planeMesh.name = 'noPlayer2';
+        planeMesh.scale.set(0.0085, 0.0056, 0.012);
+        planeMesh.position.set(4.29, 0.02, -0.41);
+        this.group?.add(planeMesh.clone());
+      }
+    } else if (videoPlayer2) {
+      try {
+        const mesh = renderVideo(this.group, videoPlayer2, 'player2');
+        if (mesh) {
+          mesh?.scale.set(-0.028, 0.0285, 1);
+          mesh?.position.set(4.298, 0.02, -0.4);
+          mesh.rotation.y = -Math.PI;
+          this.group.add(mesh);
+        }
+      } catch (error) {
+        console.log('视频信号异常');
+      }
+    }
+  }
+
+  mountedThree() {
+    this.group = new THREE.Object3D();
+    this.group.name = this.modelName;
+    return new Promise((resolve) => {
+      this.model.setGLTFModel(['quickObfurage-door', 'quickObfurage-wire', 'quickObfurage-wall'], this.group).then(() => {
+        this.setModalPosition();
+        // 初始化左右摇摆动画;
+        this.initAnimation();
+        // this.drawHots();
+        this.addLight();
+        // this.deviceDetailCard();
+        this.model.animate();
+
+        this.backDamperOpenMesh = this.group.getObjectByName('Dampler_open_1');
+        if (this.backDamperOpenMesh) this.backDamperOpenMesh.visible = false;
+        this.backDamperClosedMesh = this.group.getObjectByName('Damper_Closed_1');
+        if (this.backDamperClosedMesh) this.backDamperClosedMesh.visible = true;
+
+        this.frontDamperOpenMesh = this.group.getObjectByName('Damper_Open_2');
+        if (this.frontDamperOpenMesh) this.frontDamperOpenMesh.visible = false;
+        this.frontDamperClosedMesh = this.group.getObjectByName('Damper_Closed_2');
+        if (this.frontDamperClosedMesh) this.frontDamperClosedMesh.visible = true;
+
+        resolve(this.model);
+      });
+    });
+  }
+
+  destroy() {
+    if (this.model) {
+      if (this.mixers) {
+        this.mixers.uncacheClip(this.clipActionArr.frontDoor.getClip());
+        this.mixers.uncacheClip(this.clipActionArr.backDoor.getClip());
+        this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), this.group);
+        this.mixers.uncacheAction(this.clipActionArr.backDoor.getClip(), this.group);
+        this.mixers.uncacheRoot(this.group);
+
+        if (this.model.animations[0]) this.model.animations[0].tracks = [];
+      }
+      this.model.clearGroup(this.group);
+      this.clipActionArr.backDoor = undefined;
+      this.clipActionArr.frontDoor = undefined;
+
+      this.mixers = undefined;
+
+      // document.getElementById('damper3D').parentElement.remove(document.getElementById('damper3D'))
+    }
+  }
+}
+export default Fm1;

+ 642 - 0
src/views/vent/monitorManager/obfurage1Monitor/index.vue

@@ -0,0 +1,642 @@
+<template>
+  <div class="bg"
+    style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
+    <a-spin :spinning="loading" />
+    <div id="deviceDetail" class="device-detail">
+      <div id="deviceCard" class="device-card" style="z-index: -1; position: absolute">
+        <div class="title">KJ-980-F矿用本安型监控分站</div>
+        <div class="detail-box">
+          <div class="left-box"></div>
+          <div class="right-box">
+            <div><span class="detail-title">规格型号:</span> <span>KJ-980-F</span></div>
+            <div><span class="detail-title">技术参数:</span>
+              <span>380V,电机功率22kW,50Hz,B级绝缘,额定电流42.2A,效率90.5%,能效等级3,接法角型,2940r/min,轴承6311/CM 6211/CM,功率因数0.89</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div id="damper3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"></div>
+  </div>
+  <div class="scene-box">
+    <div class="top-box">
+      <div class="top-center row">
+        <div class="button-box" @click="playAnimation(1)">打开前门</div>
+        <div class="button-box" @click="playAnimation(2)">关闭前门</div>
+        <div class="button-box" @click="playAnimation(3)">打开后门</div>
+        <div class="button-box" @click="playAnimation(4)">关闭后门</div>
+        <div class="button-box" @click="playAnimation(5)">打开前后门</div>
+        <div class="button-box" @click="playAnimation(6)">关闭前后门</div>
+      </div>
+      <div class="top-right row">
+        <div class="control-type row">
+          <div class="control-title">控制模式:</div>
+          <a-radio-group v-model:value="selectData.autoRoManual" @change="changeType">
+            <a-radio :value="`0`">就地</a-radio>
+            <a-radio :value="`1`">远程</a-radio>
+          </a-radio-group>
+        </div>
+        <!-- <div class="run-type row">
+          <div class="control-title">运行状态:</div>
+          <a-radio-group v-model:value="selectData.runRoRecondition">
+            <a-radio :value="`0`">检修</a-radio>
+            <a-radio :value="`1`">运行</a-radio>
+          </a-radio-group>
+        </div> -->
+      </div>
+    </div>
+    <div class="title-text">
+      {{ selectData.strname.replace('风门', '快速密闭') }}
+    </div>
+    <div class="bottom-tabs-box" @mousedown="setDivHeight($event, 250, scroll)">
+      <dv-border-box8 :dur="5" :style="`padding: 5px; height: ${scroll.y + 100}px`">
+        <a-tabs class="tabs-box" v-model:activeKey="activeKey" @change="tabChange">
+          <a-tab-pane key="1" tab="实时监测">
+            <MonitorTable v-if="activeKey === '1'" ref="MonitorDataTable" class="monitor-table" :columns="columns"
+              :dataSource="dataSource" design-scope="gate-monitor" @selectRow="getSelectRow" :scroll="scroll" title="风门监测"
+              :isShowPagination="false">
+              <template #filterCell="{ column, record }">
+                <template v-if="record.frontGateOpenCtrl == 1 || record.frontGateOpenCtrl === true">
+                  <a-tag
+                    v-if="column.dataIndex === 'frontGateOpen' && record.frontGateOpen == 0 && record.frontGateClose == 0"
+                    color="red">正在打开</a-tag>
+                  <a-tag v-else-if="column.dataIndex === 'frontGateOpen'" color="processing">打开</a-tag>
+                </template>
+                <template v-else-if="record.frontGateOpenCtrl == 0 || record.frontGateOpenCtrl === false">
+                  <a-tag
+                    v-if="column.dataIndex === 'frontGateOpen' && record.frontGateOpen == 0 && record.frontGateClose == 0"
+                    color="red">正在关闭</a-tag>
+                  <a-tag
+                    v-else-if="column.dataIndex === 'frontGateOpen' && record.frontGateOpen == 0 && record.frontGateClose == 1"
+                    color="default">关闭</a-tag>
+                  <a-tag
+                    v-else-if="column.dataIndex === 'frontGateOpen' && record.frontGateOpen == 1 && record.frontGateClose == 0"
+                    color="default">打开</a-tag>
+                </template>
+                <template v-if="record.rearGateOpenCtrl == 1 || record.rearGateOpenCtrl === true">
+                  <a-tag
+                    v-if="column.dataIndex === 'rearGateOpen' && record.rearGateOpen == 0 && record.rearGateClose == 0"
+                    color="red">正在打开</a-tag>
+                  <a-tag v-else-if="column.dataIndex === 'rearGateOpen'" color="processing">打开</a-tag>
+                </template>
+                <template v-else-if="record.rearGateOpenCtrl == 0 || record.rearGateOpenCtrl === false">
+                  <a-tag
+                    v-if="column.dataIndex === 'rearGateOpen' && record.rearGateOpen == 0 && record.rearGateClose == 0"
+                    color="red">正在关闭</a-tag>
+                  <a-tag
+                    v-else-if="column.dataIndex === 'rearGateOpen' && record.rearGateOpen == 0 && record.rearGateClose == 1"
+                    color="default">关闭</a-tag>
+                  <a-tag
+                    v-else-if="column.dataIndex === 'rearGateOpen' && record.rearGateOpen == 1 && record.rearGateClose == 0"
+                    color="default">打开</a-tag>
+                </template>
+
+                <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == 0 ? 'green' : 'red'">{{
+                  record.warnFlag == 0 ? '正常' : '报警'
+                }}</a-tag>
+                <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == 0 ? 'default' : 'green'">{{
+                  record.netStatus == 0 ? '断开' : '连接'
+                }}</a-tag>
+              </template>
+            </MonitorTable>
+          </a-tab-pane>
+          <a-tab-pane key="2" tab="实时曲线图" force-render>
+            <div class="tab-item" v-if="activeKey === '2'">
+              <DeviceEcharts chartsColumnsType="gate_chart" xAxisPropType="strname" :dataSource="dataSource" height="100%"
+                :chartsColumns="chartsColumns" :device-list-api="list" device-type="gate" />
+            </div>
+          </a-tab-pane>
+          <a-tab-pane key="3" tab="历史数据">
+            <div class="tab-item" v-if="activeKey === '3'">
+              <HistoryTable columns-type="gate" device-type="gate" :device-list-api="getTableList"
+                designScope="gate-history" :scroll="scroll" />
+            </div>
+          </a-tab-pane>
+          <a-tab-pane key="4" tab="报警历史">
+            <div class="tab-item" v-if="activeKey === '4'">
+              <AlarmHistoryTable columns-type="alarm" device-type="gate" :device-list-api="getTableList"
+                designScope="alarm-history" :scroll="scroll" />
+            </div>
+          </a-tab-pane>
+          <a-tab-pane key="5" tab="操作历史">
+            <div class="tab-item" v-if="activeKey === '5'">
+              <HandlerHistoryTable columns-type="operatorhistory" device-type="gate" :device-list-api="getTableList"
+                designScope="alarm-history" :scroll="scroll" />
+            </div>
+          </a-tab-pane>
+        </a-tabs>
+      </dv-border-box8>
+    </div>
+  </div>
+  <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 15px; width: 300px; height: 280px; margin: auto">
+  </div>
+  <HandleModal v-if="!globalConfig?.simulatedPassword" :modal-is-show="modalIsShow" :modal-title="modalTitle" :modal-type="modalType" @handle-ok="handleOK"
+    @handle-cancel="handleCancel" />
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, onUnmounted, onMounted, ref, reactive, nextTick, inject } from 'vue';
+import DeviceEcharts from '../comment/DeviceEcharts.vue';
+import MonitorTable from '../comment/MonitorTable.vue';
+import HistoryTable from '../comment/HistoryTable.vue';
+import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
+import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
+import HandleModal from './modal.vue';
+import { mountedThree, addMonitorText, play, destroy, setModelType, initCameraCanvas } from './gate.threejs';
+import { deviceControlApi } from '/@/api/vent/index';
+import { message } from 'ant-design-vue';
+import { list, getTableList, cameraList, cameraAddrList } from './gate.api';
+import { chartsColumns, columns, echartsOption, echartsOption1 } from './gate.data';
+import lodash from 'lodash';
+import { setDivHeight } from '/@/utils/event';
+import { BorderBox8 as DvBorderBox8 } from '@kjgl77/datav-vue3';
+import { useRouter } from 'vue-router';
+import { deviceCameraInit } from '/@/utils/ventutil.ts'
+const globalConfig = inject('globalConfig');
+
+const { currentRoute } = useRouter();
+const MonitorDataTable = ref()
+
+const playerRef = ref();
+
+
+const activeKey = ref('1'); // tab
+const loading = ref(false);
+
+const scroll = reactive({
+  y: 240
+})
+
+const frontDoorIsOpen = ref(false); //前门是否开启
+const backDoorIsOpen = ref(false); //后门是否开启
+
+const modalIsShow = ref<boolean>(false); // 是否显示模态框
+const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
+const modalType = ref(''); // 模态框内容显示类型,设备操作类型
+
+const selectRowIndex = ref(-1); // 选中行
+const dataSource = ref([]);
+
+const deviceBaseList = ref([]); // 设备基本信息
+let webRtcServer: any[] = []
+
+const tabChange = (activeKeyVal) => {
+  activeKey.value = activeKeyVal;
+  if (activeKeyVal == 1) {
+    nextTick(() => {
+      MonitorDataTable.value.setSelectedRowKeys([selectData.deviceID])
+    })
+  }
+};
+
+const initData = {
+  deviceID: '',
+  deviceType: '',
+  strname: '',
+  frontRearDP: '-', //压差
+  sourcePressure: '-', //气源压力
+  runRoRecondition: null,
+  autoRoManual: null,
+  netStatus: '0', //通信状态
+  frontGateOpen: '0',
+  frontGateClose: '1',
+  rearGateOpen: '0',
+  rearGateClose: '1',
+  fault: '气源压力超限',
+  masterComputer: 0,
+  frontGateOpenCtrl: false,
+  rearGateOpenCtrl: false,
+  cameras: []
+};
+
+// 监测数据
+const selectData = reactive(lodash.cloneDeep(initData));
+
+// 获取设备基本信息列表
+function getDeviceBaseList() {
+  getTableList({ pageSize: 1000 }).then((res) => {
+    deviceBaseList.value = res.records;
+  });
+};
+
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+async function getMonitor(flag?) {
+  if (Object.prototype.toString.call(timer) === '[object Null]') {
+    timer = await setTimeout(async () => {
+      const res = await list({ devicetype: 'gate', pagetype: 'normal' })
+      if (res.msgTxt && res.msgTxt[0]) {
+        dataSource.value = res.msgTxt[0].datalist || [];
+        dataSource.value.forEach((data: any) => {
+          const readData = data.readData;
+          data = Object.assign(data, readData);
+        });
+        if (dataSource.value.length > 0 && selectRowIndex.value == -1) {
+          // 初始打开页面
+          if (currentRoute.value['query'] && currentRoute.value['query']['id']) {
+            MonitorDataTable.value.setSelectedRowKeys([currentRoute.value['query']['id']])
+          } else {
+            MonitorDataTable.value.setSelectedRowKeys([dataSource.value[0]['deviceID']])
+          }
+        }
+        Object.assign(selectData, dataSource.value[selectRowIndex.value]);
+        addMonitorText(selectData);
+        monitorAnimation(selectData)
+        if (timer) {
+          timer = null;
+        }
+        getMonitor();
+      }
+    }, flag ? 0 : 1000);
+  }
+};
+
+async function getCamera() {
+  const res = await cameraList({ deviceid: selectData['deviceID'] })
+  const cameras: [] = res.records || []
+  let cameraAddrs:any[] = [], cameraNames:string[] = [];
+  if (cameras.length > 0){
+    cameras.forEach(item => {
+      if (item['devicekind'] == 'toRtsp' || item['devicekind'] == 'toHLS') {
+        cameraNames.push(item['name'])
+      } else {
+        cameraAddrs.push({name: item['name'], addr: item['addr'] })
+      }
+    })
+  }
+  if (cameraNames.length > 0) {
+    // 请求接口从装备院拿数据
+    const addrs: string[] = await cameraAddrList({ cameraNameList: cameraNames })
+    for(let i=0; i < addrs.length; i++){
+      cameraAddrs.push({name: '摄像头'+i, addr: addrs[i]})
+    }
+  }
+  const obj = await deviceCameraInit(cameraAddrs, playerRef.value, webRtcServer)
+  webRtcServer = obj.webRtcServerList
+  const playerDoms = obj.playerDoms
+  // 注意前后门适应需要对应 //[0] 后门 [1]前门
+  await initCameraCanvas(...playerDoms)
+}
+
+// 切换检测数据
+async function getSelectRow(selectRow, index) {
+  if (!selectRow) return;
+  loading.value = true;
+  selectRowIndex.value = index;
+
+  const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
+  Object.assign(selectData, initData, selectRow, baseData);
+  isFrontOpenRunning = false //开关门动作是否在进行
+  isFrontCloseRunning = false //开关门动作是否在进行
+  isRearOpenRunning = false //开关门动作是否在进行
+  isRearCloseRunning = false //开关门动作是否在进行
+  frontDeviceState = 0 //记录设备状态,为了与下一次监测数据做比较
+  rearDeviceState = 0 //记录设备状态,为了与下一次监测数据做比较
+  // const type = selectData.nwindownum == 1 ? 'singleWindow' : 'doubleWindow';
+  let type;
+  if (selectData.deviceType == 'gate_ss') {
+    type = 'fm2'
+  } else {
+    type = 'fm1'
+  }
+  setModelType(type).then(async() => {
+    addMonitorText(selectData);
+    loading.value = false;
+    
+  });
+  await getCamera()
+};
+
+// 播放动画
+function playAnimation(handlerState) {
+  switch (handlerState) {
+    case 1: // 打开前门
+      if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1') {
+        modalTitle.value = '打开前门';
+        modalType.value = '1';
+        modalIsShow.value = true;
+      } else {
+        message.warning('前门已经打开或正在打开,请勿重新操作')
+      }
+      break;
+    case 2: // 关闭前门
+      if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0') {
+        modalTitle.value = '关闭前门';
+        modalType.value = '2';
+        modalIsShow.value = true;
+      } else {
+        message.warning('前门已经关闭或正在关闭,请勿重新操作')
+      }
+      break;
+    case 3: // 打开后门
+      if (selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
+        modalTitle.value = '打开后门';
+        modalType.value = '3';
+        modalIsShow.value = true;
+      } else {
+        message.warning('后门已经打开或正在打开,请勿重新操作')
+      }
+      break;
+    case 4: // 关闭后门
+      if (selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
+        modalTitle.value = '关闭后门';
+        modalType.value = '4';
+        modalIsShow.value = true;
+      } else {
+        message.warning('后门已经关闭或正在关闭,请勿重新操作')
+      }
+      break;
+    case 5: // 打开前后门
+      if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1' && selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
+        modalTitle.value = '打开前后门';
+        modalType.value = '5';
+        modalIsShow.value = true;
+      } else {
+        message.warning('前后门已经打开或正在打开,请勿重新操作')
+      }
+      break;
+    case 6: // 关闭前后门
+      if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0' && selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
+        modalTitle.value = '关闭前后门';
+        modalType.value = '6';
+        modalIsShow.value = true;
+      } else {
+        message.warning('前后门已经关闭或正在关闭,请勿重新操作')
+      }
+      break;
+  }
+
+  if (globalConfig?.simulatedPassword) {
+    handleOK(globalConfig?.simulatedPassword, handlerState + '')
+  }
+};
+
+
+function handleOK(passWord, handlerState) {
+  // if (passWord !== '123456') {
+  //   message.warning('密码不正确,请重新输入');
+  //   return;
+  // }
+
+  if ((isFrontOpenRunning || isFrontCloseRunning) && (handlerState == 2 || handlerState == 1 || handlerState == 5 || handlerState == 6)) {
+    return
+  }
+
+  if ((isRearOpenRunning || isRearCloseRunning) && (handlerState == 3 || handlerState == 4 || handlerState == 5 || handlerState == 6)) {
+    return
+  }
+
+  const data = {
+    deviceid: selectData.deviceID,
+    devicetype: selectData.deviceType,
+    paramcode: '',
+    value: null,
+    password: passWord,
+    masterComputer: selectData.masterComputer,
+  };
+  let handler = () => { };
+
+  switch (handlerState) {
+    case '1': // 打开前门
+      if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1') {
+        handler = () => {
+          frontDoorIsOpen.value = true;
+        };
+        data.paramcode = 'frontGateOpen_S';
+      }
+      break;
+    case '2': // 关闭前门
+      if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0') {
+        handler = () => {
+          frontDoorIsOpen.value = false;
+        };
+        data.paramcode = 'frontGateClose_S';
+      }
+      break;
+    case '3': // 打开后门
+      if (selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
+        handler = () => {
+          backDoorIsOpen.value = true;
+        };
+        data.paramcode = 'rearGateOpen_S';
+      }
+      break;
+    case '4': // 关闭后门
+      if (selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
+        handler = () => {
+          backDoorIsOpen.value = false;
+        };
+        data.paramcode = 'rearGateClose_S';
+      }
+      break;
+    case '5': // 打开前后门
+      if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1' && selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
+        handler = () => {
+          frontDoorIsOpen.value = true;
+          backDoorIsOpen.value = true;
+        };
+        data.paramcode = 'sameTimeOpen';
+      }
+      break;
+    case '6': // 关闭前后门
+      if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0' && selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
+        handler = () => {
+          frontDoorIsOpen.value = false;
+          backDoorIsOpen.value = false;
+        };
+        data.paramcode = 'sameTimeClose';
+      }
+      break;
+  }
+  if (data.paramcode) {
+    deviceControlApi(data)
+      .then((res) => {
+        // 模拟时开启
+        if (res.success) {
+          modalIsShow.value = false;
+          message.success('操作成功!')
+        }
+      })
+  }
+};
+
+/** 开关门动画调用 */
+let isFrontOpenRunning = false //开关门动作是否在进行
+let isFrontCloseRunning = false //开关门动作是否在进行
+let isRearOpenRunning = false //开关门动作是否在进行
+let isRearCloseRunning = false //开关门动作是否在进行
+let frontDeviceState = 0 //记录设备状态,为了与下一次监测数据做比较
+let rearDeviceState = 0 //记录设备状态,为了与下一次监测数据做比较  
+function monitorAnimation(selectData) {
+  const timeScale = 0.003
+  if (selectData.frontGateOpenCtrl == 1 || selectData.frontGateOpenCtrl === true) {
+    isFrontCloseRunning = false
+    if (selectData.frontGateOpen == 0 && selectData.frontGateClose == 0) {
+      //打开前门1
+
+      if (!isFrontOpenRunning) {
+        frontDoorIsOpen.value = true
+        backDoorIsOpen.value = true
+        isFrontOpenRunning = true
+        play(1, timeScale)
+        frontDeviceState = 1
+      }
+
+    }
+    if (selectData.frontGateOpen == 1 && selectData.frontGateClose == 0) {
+      isFrontOpenRunning = false
+      if (frontDeviceState != 1) {
+        import.meta.env.VITE_GLOB_IS_SIMULATE ? play(1, timeScale) : play(1)
+        frontDeviceState = 1
+        frontDoorIsOpen.value = false
+        backDoorIsOpen.value = true
+      }
+    }
+  } else {
+    if (selectData.frontGateOpen == 0 && selectData.frontGateClose == 0) {
+      //关闭前门
+      isFrontOpenRunning = false
+      if (!isFrontCloseRunning) {
+        isFrontCloseRunning = true
+        play(2, timeScale)
+        frontDeviceState = 2
+        frontDoorIsOpen.value = true
+        backDoorIsOpen.value = true
+      }
+    }
+    if (selectData.frontGateClose == 1 && selectData.frontGateOpen == 0) {
+      isFrontCloseRunning = false
+      if (frontDeviceState == 1) {
+        import.meta.env.VITE_GLOB_IS_SIMULATE ? play(2, timeScale) : play(2)
+        frontDeviceState = 2
+        frontDoorIsOpen.value = false
+        // backDoorIsOpen.value = false
+      }
+    }
+  }
+
+  if (selectData.rearGateOpenCtrl == 1 || selectData.rearGateOpenCtrl === true) {
+    isRearCloseRunning = false
+    if (selectData.rearGateOpen == 0 && selectData.rearGateClose == 0) {
+      //打开后门
+      if (!isRearOpenRunning) {
+        isRearOpenRunning = true
+        play(3, timeScale)
+        rearDeviceState = 3
+        frontDoorIsOpen.value = true
+        backDoorIsOpen.value = true
+      }
+
+    }
+    if (selectData.rearGateOpen == 1 && selectData.rearGateClose == 0) {
+      isRearOpenRunning = false
+
+      if (rearDeviceState != 3) {
+        rearDeviceState = 3
+        import.meta.env.VITE_GLOB_IS_SIMULATE ? play(3, timeScale) : play(3)
+        backDoorIsOpen.value = false
+        frontDoorIsOpen.value = true
+      }
+    }
+  } else {
+    if (selectData.rearGateOpen == 0 && selectData.rearGateClose == 0) {
+      //关闭后门
+      isRearOpenRunning = false
+      if (!isRearCloseRunning) {
+        isRearCloseRunning = true
+        play(4, timeScale)
+        rearDeviceState = 4
+        frontDoorIsOpen.value = true
+        backDoorIsOpen.value = true
+      }
+    }
+    if (selectData.rearGateClose == 1 && selectData.rearGateOpen == 0) {
+      isRearCloseRunning = false
+      if (rearDeviceState == 3) {
+        rearDeviceState = 4
+        import.meta.env.VITE_GLOB_IS_SIMULATE ? play(4, timeScale) : play(4)
+        backDoorIsOpen.value = false
+      }
+    }
+  }
+
+  // console.log('frontGateOpen:', selectData.frontGateOpen, '  frontGateClose:', selectData.frontGateClose, ' rearGateOpen:', selectData.rearGateOpen, '  rearGateClose:', selectData.rearGateClose, '  frontGateOpenCtrl:', selectData.frontGateOpenCtrl, '  rearGateOpenCtrl:', selectData.rearGateOpenCtrl)
+
+}
+
+function handleCancel() {
+  modalIsShow.value = false;
+  modalTitle.value = '';
+  modalType.value = '';
+};
+
+// 远程、就地切换
+function changeType() {
+  const data = {
+    deviceid: selectData.deviceID,
+    devicetype: selectData.deviceType,
+    paramcode: 'autoRoManualControl',
+    value: selectData.autoRoManual,
+  };
+  deviceControlApi(data).then(() => {
+    message.success('状态切换成功!');
+  });
+};
+
+onMounted(async () => {
+  loading.value = true;
+  mountedThree().then(async () => {
+    await getMonitor(true);
+    loading.value = false;
+  });
+});
+
+onBeforeUnmount(() => {
+  getDeviceBaseList();
+});
+
+onUnmounted(() => {
+  if (timer) {
+    clearTimeout(timer);
+    timer = undefined;
+  }
+  destroy()
+  if(webRtcServer.length > 0){
+    webRtcServer.forEach(item => {
+      item.disconnect()
+    })
+  }
+});
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/vent/modal.less';
+
+.button-box {
+  border: none !important;
+  height: 34px !important;
+
+  &:hover {
+    background: linear-gradient(#2cd1ff55, #1eb0ff55) !important;
+  }
+
+  &::before {
+    height: 27px !important;
+    background: linear-gradient(#1fa6cb, #127cb5) !important;
+  }
+
+  &::after {
+    top: 35px !important;
+  }
+}
+
+:deep(.@{ventSpace}-tabs-tabpane-active) {
+  height: 100%;
+}
+
+::-webkit-scrollbar-thumb {
+  -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+  background: #4288A444;
+}
+
+</style>

+ 66 - 0
src/views/vent/monitorManager/obfurage1Monitor/modal.vue

@@ -0,0 +1,66 @@
+<template>
+  <a-modal v-model:visible="visible" :title="title" @ok="handleOk" @cancel="handleCancel">
+    <div class="modal-container">
+      <div class="vent-flex-row">
+        <ExclamationCircleFilled style="color: #ffb700; font-size: 30px" />
+        <div class="warning-text">您是否要进行{{ title }}操作?</div>
+      </div>
+      <div class="vent-flex-row input-box">
+        <div class="label">操作密码:</div>
+        <a-input size="small" type="password" v-model:value="passWord" />
+      </div>
+    </div>
+  </a-modal>
+</template>
+<script setup lang="ts">
+  import { watch, ref } from 'vue';
+  import { ExclamationCircleFilled } from '@ant-design/icons-vue';
+
+  const props = defineProps({
+    modalIsShow: {
+      type: Boolean,
+      default: false,
+    },
+    modalTitle: {
+      type: String,
+      default: '',
+    },
+    modalType: {
+      type: String,
+      default: '',
+    },
+  });
+
+  const emit = defineEmits(['handleOk', 'handleCancel']);
+
+  const visible = ref<Boolean>(false);
+  const title = ref<String>('');
+  const type = ref<String>('');
+  const passWord = ref('');
+
+  watch([() => props.modalIsShow, () => props.modalTitle, () => props.modalType], ([newVal, newModalTitle, newModalType]) => {
+    visible.value = newVal;
+    if (newModalTitle) title.value = newModalTitle;
+    if (newModalType) type.value = newModalType;
+    passWord.value = '';
+  });
+
+  function handleOk() {
+    //
+    emit('handleOk', passWord.value, type.value);
+  }
+  function handleCancel() {
+    //
+    emit('handleCancel');
+  }
+</script>
+<style scoped lang="less">
+  @ventSpace: zxm;
+
+  .label {
+    width: 80px;
+  }
+  .@{ventSpace}-input {
+    width: 150px;
+  }
+</style>

+ 25 - 27
src/views/vent/monitorManager/windowMonitor/dandaoFc.threejs.ts

@@ -45,7 +45,7 @@ class singleWindow {
         color: '#009900',
         strokeStyle: '#002200',
         x: 110,
-        y: 94,
+        y: 90,
       },
       {
         text: `过风量(m3/min):`,
@@ -53,15 +53,15 @@ class singleWindow {
         color: '#009900',
         strokeStyle: '#002200',
         x: 5,
-        y: 150,
+        y: 145,
       },
       {
-        text: `${selectData.forntm3}`,
+        text: Number(`${selectData.forntm3}`).toFixed(0),
         font: 'normal 30px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 235,
-        y: 150,
+        x: 225,
+        y: 145,
       },
       {
         text: `过风面积(m2): `,
@@ -69,7 +69,7 @@ class singleWindow {
         color: '#009900',
         strokeStyle: '#002200',
         x: 5,
-        y: 205,
+        y: 200,
       },
       {
         text: `${selectData.forntArea}`,
@@ -77,7 +77,7 @@ class singleWindow {
         color: '#009900',
         strokeStyle: '#002200',
         x: 200,
-        y: 205,
+        y: 200,
       },
       {
         text: `风窗压差(Pa):`,
@@ -100,32 +100,32 @@ class singleWindow {
         font: 'normal 30px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 320,
-        y: 150,
+        x: 330,
+        y: 145,
       },
       {
         text: `1% FS`,
         font: 'normal 30px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 460,
-        y: 150,
+        x: 440,
+        y: 145,
       },
       {
         text: `调节范围:`,
         font: 'normal 30px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 320,
-        y: 205,
+        x: 330,
+        y: 200,
       },
       {
         text: `0~6`,
         font: 'normal 30px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 460,
-        y: 205,
+        x: 470,
+        y: 200,
       },
       {
         text: `煤炭科学技术研究院有限公司研制`,
@@ -137,7 +137,7 @@ class singleWindow {
       },
     ];
 
-    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+    getTextCanvas(726, 546, textArr, '').then((canvas: HTMLCanvasElement) => {
       const textMap = new THREE.CanvasTexture(canvas); // 关键一步
       const textMaterial = new THREE.MeshBasicMaterial({
         // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
@@ -154,8 +154,8 @@ class singleWindow {
         const planeGeometry = new THREE.PlaneGeometry(526, 346); // 平面3维几何体PlaneGeometry
         const planeMesh = new THREE.Mesh(planeGeometry, textMaterial);
         planeMesh.name = 'monitorText';
-        planeMesh.scale.set(0.002, 0.002, 0.002);
-        planeMesh.position.set(1.97, 0.158, -0.23);
+        planeMesh.scale.set(0.0026, 0.003, 0.002);
+        planeMesh.position.set(3.76, -0.042, -0.23);
         this.group?.add(planeMesh);
       }
     });
@@ -165,7 +165,7 @@ class singleWindow {
   initAnimation() {
     const meshArr01: THREE.Object3D[] = [];
     this.group?.children.forEach((obj) => {
-      if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('FCshanye')) {
+      if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('shanye')) {
         obj.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0);
         meshArr01.push(obj);
       }
@@ -277,15 +277,14 @@ class singleWindow {
     }
     if (!videoPlayer1 && videoPlayer1 === null) {
       monitorPlane.name = 'noPlayer1';
-      monitorPlane.scale.set(0.015, 0.007, 0.011);
-      monitorPlane.position.set(4.04, 0.02, -0.46);
+      monitorPlane.scale.set(0.011, 0.0055, 0.011);
+      monitorPlane.position.set(-2.01, 0.15, -0.23);
       this.group?.add(monitorPlane);
     } else if (videoPlayer1) {
       const mesh = renderVideo(this.group, videoPlayer1, 'player1');
       if (mesh) {
-        mesh?.scale.set(-0.038, 0.029, 1);
-        mesh?.position.set(-4.302, 0.15, -0.23);
-        mesh.rotation.y = -Math.PI;
+        mesh?.scale.set(0.0382, 0.028, 0.022);
+        mesh?.position.set(-2.008, 0.148, -0.22);
         this.group.add(mesh);
       }
     }
@@ -293,9 +292,8 @@ class singleWindow {
 
   mountedThree() {
     return new Promise((resolve) => {
-      this.model.setGLTFModel(['ddFc', 'wall'], this.group).then(() => {
-        // this.group = gltf[0];
-
+      this.model.setGLTFModel(['ddFc']).then((gltf) => {
+        this.group = gltf[0];
         this.setModalPosition();
         this.initAnimation();
         resolve(null);

+ 10 - 4
src/views/vent/monitorManager/windowMonitor/modal.vue

@@ -9,7 +9,7 @@
         <div class="label">风窗面积:</div>
         <a-input-number size="small" placeholder="0" :min="0" :max="90" :step="1" v-model:value="area" />
       </div>
-      <div class="vent-flex-row input-box">
+      <div  v-if="!globalConfig?.simulatedPassword" class="vent-flex-row input-box">
         <div class="label">操作密码:</div>
         <a-input size="small" type="password" v-model:value="passWord" />
       </div>
@@ -17,9 +17,11 @@
   </a-modal>
 </template>
 <script setup lang="ts">
-  import { watch, ref } from 'vue';
+  import { watch, ref, inject } from 'vue';
   import { ExclamationCircleFilled } from '@ant-design/icons-vue';
 
+  const globalConfig = inject('globalConfig');
+
   const props = defineProps({
     modalIsShow: {
       type: Boolean,
@@ -52,8 +54,12 @@
   });
 
   function handleOk() {
-    //
-    emit('handleOk', passWord.value, type.value, area.value);
+
+    if(globalConfig?.simulatedPassword){
+      emit('handleOk', globalConfig?.simulatedPassword, type.value, area.value);
+    }else{
+      emit('handleOk', passWord.value, type.value, area.value);
+    }
   }
   function handleCancel() {
     //

+ 8 - 7
src/views/vent/monitorManager/windowMonitor/shuangdaoFc.threejs.ts

@@ -50,11 +50,11 @@ class doubleWindow {
         y: 150,
       },
       {
-        text: `${selectData.forntm3}`,
+        text: Number(`${selectData.forntm3}`).toFixed(0),
         font: 'normal 28px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 220,
+        x: 215,
         y: 150,
       },
       {
@@ -94,7 +94,7 @@ class doubleWindow {
         font: 'normal 28px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 320,
+        x: 330,
         y: 150,
       },
       {
@@ -110,7 +110,7 @@ class doubleWindow {
         font: 'normal 28px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 320,
+        x: 330,
         y: 205,
       },
       {
@@ -118,7 +118,7 @@ class doubleWindow {
         font: 'normal 28px Arial',
         color: '#009900',
         strokeStyle: '#002200',
-        x: 460,
+        x: 500,
         y: 205,
       },
       {
@@ -131,7 +131,7 @@ class doubleWindow {
       },
     ];
 
-    getTextCanvas(526, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
+    getTextCanvas(550, 346, textArr, '').then((canvas: HTMLCanvasElement) => {
       const textMap = new THREE.CanvasTexture(canvas); // 关键一步
       const textMaterial = new THREE.MeshBasicMaterial({
         // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
@@ -349,7 +349,8 @@ class doubleWindow {
 
   mountedThree() {
     return new Promise((resolve) => {
-      this.model.setGLTFModel(['sdFc', 'wall'], this.group).then(() => {
+      this.model.setGLTFModel(['sdFc']).then((gltf) => {
+        this.group = gltf[0];
         this.setModalPosition();
         this.initAnimation();
         resolve(null);

+ 11 - 10
src/views/vent/monitorManager/windrectMonitor/duishe.threejs.ts

@@ -276,18 +276,19 @@ class dsWindRect {
     if (flag == 'moni') {
       gsap.to(dsTanTou['position'], {
         y: -0.32,
-        duration: Math.abs(dsTanTou['position']['y'] - 0.32) * 21,
+        duration: Math.abs(dsTanTou['position']['y'] + 0.32) * 21,
         overwrite: true,
         onComplete: function () {
-          setTimeout(() => {
-            gsap.to(dsTanTou['position'], {
-              y: 0.45,
-              duration: 0.77 * 21,
-              overwrite: true,
-            });
-          }, 5000);
+          gsap.to(dsTanTou['position'], {
+            y: 0.45,
+            duration: 0.77 * 21,
+            overwrite: true,
+          });
         },
       });
+      // setTimeout(() => {
+
+      // }, Math.abs(dsTanTou['position']['y'] - 0.32) * 21);
     } else {
       if (!isDirect) {
         if (this.isRun) return;
@@ -295,13 +296,13 @@ class dsWindRect {
         this.isRun = true;
         gsap.to(dsTanTou['position'], {
           y: -0.32,
-          duration: Math.abs(dsTanTou['position']['y'] - 0.32) * 21,
+          duration: Math.abs(dsTanTou['position']['y'] + 0.32) * 31,
           overwrite: true,
           onComplete: function () {
             setTimeout(() => {
               gsap.to(dsTanTou['position'], {
                 y: 0.45,
-                duration: 0.77 * 21,
+                duration: 0.77 * 31,
                 overwrite: true,
                 // onComplete: function () {
                 //   _this.isRun = false;

+ 9 - 4
src/views/vent/monitorManager/windrectMonitor/index.vue

@@ -138,13 +138,13 @@
       <ModalTable ref="modalTable" deviceType="windrect_list" />
     </div>
   </BasicModal>
-  <HandleModal :modal-is-show="modalIsShow" modal-title="启动测风" :modal-type="modalType"  @handle-ok="controlDevice"
+  <HandleModal v-if="!globalConfig?.simulatedPassword" :modal-is-show="modalIsShow" modal-title="启动测风" :modal-type="modalType"  @handle-ok="controlDevice"
       @handle-cancel="handleCancelControl" />
 </template>
 
 <script setup lang="ts">
   import DeviceEcharts from '../comment/DeviceEcharts.vue';
-  import { onBeforeMount,ref, onMounted, onUnmounted, reactive, toRaw, nextTick, watch } from 'vue';
+  import { onBeforeMount,ref, onMounted, onUnmounted, reactive, toRaw, nextTick, inject } from 'vue';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   import MonitorTable from '../comment/MonitorTable.vue';
   import ModalTable from './components/modalTable.vue';
@@ -164,6 +164,8 @@
   import { useRouter } from 'vue-router';
   import { deviceCameraInit } from '/@/utils/ventutil.ts'
 
+  const globalConfig = inject('globalConfig');
+
   const { currentRoute } = useRouter();
 
   const MonitorDataTable = ref()
@@ -227,9 +229,11 @@
   // https获取监测数据
   let timer: null | NodeJS.Timeout = null;
   function getMonitor(flag?) {
-    if (Object.prototype.toString.call(timer) === '[object Null]') {
+    debugger
+    if (timer == null) {
       timer = setTimeout(() => {
         list({ devicetype: 'windrect', pagetype: 'normal' }).then((res) => {
+          debugger
           dataSource.value = res.msgTxt[0].datalist || [];
           if(dataSource.value.length > 0){
             dataSource.value.forEach((data: any) => {
@@ -246,6 +250,7 @@
             }
             const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
             Object.assign(selectData, data);
+            debugger
             addMonitorText(selectData);
             
             palyAnimation(selectData)
@@ -699,8 +704,8 @@
 
   onMounted(() => {
     loading.value = true;
-    
     mountedThree().then(async () => {
+      debugger
       getMonitor(true);
       // loading.value = false;
     });

+ 1 - 0
src/views/vent/monitorManager/windrectMonitor/windrect.threejs.ts

@@ -67,6 +67,7 @@ const mouseEvent = (event) => {
 
 /* 添加监控数据 */
 export const addMonitorText = (selectData) => {
+  debugger
   if (windRectType === 'lmWindRect' && lmWindRectObj) {
     return lmWindRectObj.addMonitorText.call(lmWindRectObj, selectData);
   } else if (windRectType === 'zdWindRect' && zdWindRectObj) {