hongrunxia 11 ヶ月 前
コミット
41c57497ba

+ 583 - 0
src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue

@@ -0,0 +1,583 @@
+<template>
+  <BasicModal v-bind="$attrs" destroyOnClose @register="register" :title="`喷雾监测详情    ${currentTime}`" width="1200px">
+    <div class="fiber-modal">
+      <div class="modal-left">
+        <div
+          v-for="device in deviceList"
+          class="link-item"
+          :class="{ 'active-device-title': device.deviceID === activeDeviceID }"
+          :key="device.deviceID"
+        >
+          <span class="" @click="selectDevice(device.deviceID)">{{ device.strinstallpos }}</span>
+        </div>
+      </div>
+      <div class="modal-right">
+        <div class="base-info">
+          <span class="base-title">设备基本情况</span>
+          <div class="base-box">
+            <div class="base-item">
+              <span class="title">设备编号:</span>
+              <span class="value">{{ atomizingMonitor['devNum'] ? atomizingMonitor['devNum'] : '-' }}</span>
+            </div>
+            <!-- <div class="base-item">
+              <span class="title">应用场景:</span>
+              <span class="value">{{ atomizingMonitor['areaName'] ? atomizingMonitor['areaName'] : '-' }}</span>
+            </div> -->
+            <div class="base-item flex">
+              <span class="title">工作模式:</span>
+              <!-- <span class="value">{{ atomizingMonitor['workMode'] ? atomizingMonitor['workMode'] : '-' }}</span> -->
+              <div class="flex">
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['workMode'] === '0000' }"></span
+                  ><span class="pl-10px">自动</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['workMode'] === '0001' }"></span
+                  ><span class="pl-10px">闭锁</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['workMode'] === '0002' }"></span
+                  ><span class="pl-10px">手动</span></div
+                >
+              </div>
+            </div>
+            <div class="base-item flex">
+              <span class="title">喷雾状态:</span>
+              <div class="flex">
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['waterStatus'] === 'FF00' }"></span
+                  ><span class="pl-10px">开启</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['waterStatus'] === '0000' }"></span
+                  ><span class="pl-10px">关闭</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['waterStatus'] === '0001' }"></span
+                  ><span class="pl-10px">停止</span></div
+                >
+              </div>
+            </div>
+            <div class="base-item flex">
+              <span class="title">排污状态:</span>
+              <div class="flex">
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === 'FF00' }"></span
+                  ><span class="pl-10px">开启</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === '0000' }"></span
+                  ><span class="pl-10px">关闭</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === '0001' }"></span
+                  ><span class="pl-10px">停止</span></div
+                >
+              </div>
+            </div>
+            <div class="base-item flex">
+              <span class="title">连接状态:</span>
+              <div class="flex">
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['connStatus'] === '0000' }"></span
+                  ><span class="pl-10px">未连接</span></div
+                >
+                <div class="pr-10px"
+                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['connStatus'] === '0001' }"></span
+                  ><span class="pl-10px">已连接</span></div
+                >
+              </div>
+            </div>
+            <div class="base-item">
+              <span class="title">主机位置:</span>
+              <span class="value"
+                >{{
+                  atomizingMonitor['holderLocation'] !== undefined && atomizingMonitor['holderLocation'] !== null
+                    ? atomizingMonitor['holderLocation']
+                    : '-'
+                }}
+                ℃</span
+              >
+            </div>
+            <div class="base-item">
+              <span class="title">支架主机数量:</span>
+              <span class="value">{{ atomizingMonitor['holderCounts'] ? atomizingMonitor['holderCounts'] : '-' }}</span>
+            </div>
+            <div class="base-item">
+              <span class="title">支架联动数量:</span>
+              <span class="value">{{ atomizingMonitor['holderLinkCounts'] ? atomizingMonitor['holderLinkCounts'] : '-' }}</span>
+            </div>
+            <div class="base-item">
+              <span class="title">支架喷雾延时:</span>
+              <span class="value">{{ atomizingMonitor['holderDelayed'] ? atomizingMonitor['holderDelayed'] : '-' }} h</span>
+            </div>
+          </div>
+        </div>
+        <div class="right-monitor">
+          <span class="base-title">设备实时监测</span>
+          <div class="dust-group">
+            <div class="top-item">
+              <div class="icon">
+                <SvgIcon class="icon-style max-temperature" size="30" name="hd-wd" />
+              </div>
+              <div class="item-container">
+                <div class="title">巷道温度</div>
+                <div class="value"
+                  ><span class="data">{{ atomizingMonitor['temp'] ? atomizingMonitor['temp'] : '-' }}</span> <span>℃</span>
+                </div>
+              </div>
+            </div>
+            <div class="top-item">
+              <div class="icon">
+                <SvgIcon class="icon-style" size="30" name="dust-c" />
+              </div>
+              <div class="item-container">
+                <div class="title">巷道湿度</div>
+                <div class="value"
+                  ><span class="data">{{ atomizingMonitor['humidity'] ? atomizingMonitor['humidity'] : '-' }}</span> <span>%</span></div
+                >
+              </div>
+            </div>
+            <div class="top-item">
+              <div class="icon">
+                <SvgIcon class="icon-style min-temperature" size="30" name="dust-nd" />
+              </div>
+              <div class="item-container">
+                <div class="title">粉尘浓度</div>
+                <div class="value"
+                  ><span class="data">{{ atomizingMonitor['dustConc'] ? atomizingMonitor['dustConc'] : '-' }}</span> <span>mg/m³</span></div
+                >
+              </div>
+            </div>
+            <div class="top-item">
+              <div class="icon">
+                <SvgIcon class="icon-style pw-sy" size="30" name="pw-sy" />
+              </div>
+              <div class="item-container">
+                <div class="title">喷雾水压</div>
+                <div class="value"
+                  ><span class="data">{{ atomizingMonitor['waterPre'] ? atomizingMonitor['waterPre'] : '-' }}</span> <span>MPa</span></div
+                >
+              </div>
+            </div>
+            <div class="top-item">
+              <div class="icon">
+                <SvgIcon class="icon-style" size="30" name="pw-zz" />
+              </div>
+              <div class="item-container">
+                <div class="title">喷雾气压</div>
+                <div class="value"
+                  ><span class="data">{{ atomizingMonitor['airPre'] ? atomizingMonitor['airPre'] : '-' }}</span> <span>MPa</span></div
+                >
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="right-bottom">
+          <span class="base-title">实时数据监测曲线</span>
+          <div class="echarts-box">
+            <BarAndLine
+              xAxisPropType="time"
+              :dataSource="posList"
+              height="100%"
+              :chartsColumns="chartsColumns"
+              :option="echatsOption"
+              chartsType="listMonitor"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, watch, shallowRef, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import BarAndLine from '/@/components/chart/BarAndLine.vue';
+  import { SvgIcon } from '/@/components/Icon';
+  import { Decoration7 as DvDecoration7, ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+  import dayjs from 'dayjs';
+
+  export default defineComponent({
+    components: { BasicModal, BarAndLine, SvgIcon, DvScrollBoard, DvDecoration7 },
+    props: {
+      dataSource: { type: Array },
+      activeID: { type: String },
+    },
+    setup(props) {
+      const currentTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
+      const modelRef = ref({});
+      const loading = ref(true);
+      const activeDeviceID = ref('');
+      const deviceList = ref<any[]>([]);
+      const posList = ref<any[]>([]);
+      const atomizingMonitor = shallowRef({});
+      const dustSwitch = ref(false);
+
+      const echatsOption = {
+        grid: {
+          top: '20%',
+          left: '2px',
+          right: '10px',
+          bottom: '3%',
+          containLabel: true,
+        },
+        toolbox: {
+          feature: {},
+        },
+      };
+
+      const chartsColumns = [
+        {
+          legend: '巷道温度',
+          seriesName: '(℃)',
+          ymax: 5,
+          yname: '℃',
+          linetype: 'line',
+          yaxispos: 'left',
+          color: '#EE6666',
+          sort: 1,
+          xRotate: 0,
+          dataIndex: 'temp',
+        },
+        {
+          legend: '巷道湿度',
+          seriesName: '(%)',
+          ymax: 5,
+          yname: '%',
+          linetype: 'line',
+          yaxispos: 'right',
+          color: '#FDB146',
+          sort: 2,
+          xRotate: 0,
+          dataIndex: 'humidity',
+        },
+      ];
+
+      const columns = [
+        {
+          title: '安装位置',
+          dataIndex: 'position',
+          width: 60,
+          align: 'center',
+          customRender: ({ index }) => {
+            return `测点${index}`;
+          },
+        },
+        {
+          title: '安装距离(m)',
+          dataIndex: 'pos',
+          align: 'center',
+          width: 60,
+        },
+        {
+          title: '测点温度',
+          dataIndex: 'value',
+          align: 'center',
+          width: 50,
+        },
+        {
+          title: '测点状态',
+          dataIndex: 'state',
+          align: 'center',
+          width: 50,
+          customRender: () => {
+            return `正常`;
+          },
+        },
+      ];
+
+      const warningConfig = reactive({
+        data: [
+          ['测点6', '318℃', '严重报警'],
+          ['测点43', '142℃', '一般预警'],
+          ['测点23', '167℃', '一般预警'],
+          ['测点6', '198℃', '超高预警'],
+          ['测点65', '197℃', '超高预警'],
+          ['测点78', '154℃', '一般预警'],
+          ['测点61', '104℃', '一般预警'],
+          ['测点87', '150℃', '一般信息'],
+        ],
+        index: false,
+        columnWidth: [150],
+        oddRowBGC: '#009acd10',
+        evenRowBGC: '#009acd05',
+        align: ['center', 'center', 'center'],
+      });
+
+      const [register, { setModalProps }] = useModalInner();
+
+      function handleVisibleChange(visible) {
+        if (visible) {
+          loading.value = true;
+          setModalProps({ loading: true, confirmLoading: true });
+
+          setTimeout(() => {
+            loading.value = false;
+            setModalProps({ loading: false, confirmLoading: false });
+          }, 1000);
+        }
+      }
+
+      // 选择监测
+      function selectDevice(id) {
+        loading.value = true;
+        setModalProps({ loading: true, confirmLoading: true });
+        setTimeout(() => {
+          loading.value = false;
+          activeDeviceID.value = id;
+          setModalProps({ loading: false, confirmLoading: false });
+        }, 300);
+      }
+
+      watch([() => props.dataSource, () => props.activeID], ([newDataSource, newActiveID], [oldDataSource, oldActiveID]) => {
+        deviceList.value = newDataSource as any[];
+        // if (newActiveID != oldActiveID) {
+        //   activeDeviceID.value = newActiveID as string;
+        // }
+        activeDeviceID.value = activeDeviceID.value ? activeDeviceID.value : newActiveID;
+        newDataSource?.forEach((item: any, index) => {
+          if ((!activeDeviceID.value && index == 0) || item.deviceID === activeDeviceID.value) {
+            // activeDeviceID.value = item.deviceID;
+            // const list = item.summaryHour
+            // list.filter(data => {
+            //   const date = new Date();     //1. js获取当前时间
+            //   const min = date.getMinutes();  //2. 获取当前分钟
+            //   return Object.assign(data, data.dustval, { readTime: (dayjs(date.setMinutes(min + 10))).format('YYYY-MM-DD HH:mm:ss') })
+            // })
+            // if(list.length > 0) posList.value = list
+            posList.value = item['history'];
+            atomizingMonitor.value = item.readData;
+          }
+        });
+      });
+
+      return {
+        register,
+        model: modelRef,
+        currentTime,
+        dustSwitch,
+        handleVisibleChange,
+        selectDevice,
+        deviceList,
+        activeDeviceID,
+        atomizingMonitor,
+        echatsOption,
+        posList,
+        chartsColumns,
+        columns,
+        warningConfig,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .fiber-modal {
+    width: 100%;
+    height: 600px;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+
+    .modal-left {
+      width: 200px;
+      height: 100%;
+      overflow-y: auto;
+      background: #ffffff11;
+      padding: 5px;
+      border-radius: 5px;
+      .active-device-title {
+        color: aqua;
+      }
+      .link-item {
+        position: relative;
+        cursor: pointer;
+        line-height: 30px;
+        padding-left: 30px;
+        span:hover {
+          color: #89ffff;
+        }
+        &::after {
+          content: '';
+          position: absolute;
+          display: block;
+          width: 8px;
+          height: 8px;
+          top: 12px;
+          left: 10px;
+          transform: rotateZ(45deg) skew(10deg, 10deg);
+          background: #45d3fd;
+        }
+      }
+    }
+    .modal-right {
+      width: calc(100% - 220px);
+      overflow-y: auto;
+      .base-title {
+        line-height: 32px;
+        position: relative;
+        padding-left: 20px;
+        &::after {
+          content: '';
+          position: absolute;
+          display: block;
+          width: 4px;
+          height: 12px;
+          top: 4px;
+          left: 10px;
+          background: #45d3fd;
+          border-radius: 4px;
+        }
+      }
+      .base-info {
+        .base-box {
+          width: calc(100% - 20px);
+          height: 130px;
+          font-size: 14px;
+          display: flex;
+          flex-direction: row;
+          flex-wrap: wrap;
+          border: 1px solid rgba(25, 186, 255, 0.4);
+          box-shadow: inset 0 0 10px rgba(0, 157, 255, 0.3);
+          background: rgba(0, 157, 255, 0.05);
+          padding: 15px 20px;
+          margin: 0 10px 10px 10px;
+          .base-item {
+            width: 33%;
+            line-height: 30px;
+            span {
+              display: inline-block;
+            }
+            .title {
+              width: 100px;
+            }
+            .value {
+              width: 100px;
+            }
+          }
+        }
+      }
+      .right-monitor {
+        .dust-group {
+          display: flex;
+          flex-direction: row;
+          justify-content: space-between;
+          margin-bottom: 30px;
+          padding: 0 10px;
+        }
+
+        .top-item {
+          width: 155px;
+          height: 60px;
+          display: flex;
+          flex-direction: row;
+          justify-content: center;
+          align-items: center;
+          background: url('/@/assets/images/vent/model_image/dust-monitor-bg.png') no-repeat;
+          padding-top: 16px;
+          .icon {
+            width: 58px;
+            height: 60px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            position: relative;
+            top: -8px;
+          }
+          .item-container {
+            width: 100px;
+            display: flex;
+            flex-direction: column;
+            // justify-content: start;
+            div {
+              padding-left: 8px;
+            }
+            .title {
+              font-size: 14px;
+              margin-bottom: 8px;
+            }
+            .value {
+              position: relative;
+              width: 110px;
+              top: -8px;
+              .data {
+                display: inline-block;
+                width: 50px;
+                font-family: douyuFont;
+                font-weight: 600;
+                font-size: 14px;
+                -webkit-background-clip: text;
+                background-clip: text;
+                color: #28dce4;
+              }
+              span {
+                font-family: Arial, Helvetica, sans-serif;
+                font-size: 14px;
+                color: #ffffffdd;
+              }
+            }
+          }
+        }
+        .warning-box {
+          padding-top: 0px;
+          .icon {
+            margin-top: 20px;
+            .icon-style {
+              color: #fdb146;
+            }
+          }
+          .title {
+            padding-top: 0px;
+          }
+          .warning-value {
+            font-family: electronicFont;
+            font-size: 18px;
+            color: #61ddb1;
+          }
+        }
+      }
+      .right-center {
+        .dust-group {
+          display: flex;
+          flex-direction: row;
+          justify-content: space-between;
+          .dust-item {
+            width: 238px;
+            height: 148px;
+            background: url('/@/assets/images/vent/model_image/dust-bg.png') no-repeat;
+            scale: 0.9;
+            .title {
+              position: absolute;
+              top: 80px;
+              left: 70px;
+              font-size: 16px;
+            }
+            .value {
+              position: absolute;
+              top: 50px;
+              left: 50px;
+              font-family: 'douyuFont';
+              color: #20dbfd;
+              text-shadow: 0 0 25px #00d8ff;
+              font-size: 18px;
+              font-weight: bolder;
+              span {
+                font-family: Arial, Helvetica, sans-serif;
+                font-size: 16px;
+                color: aliceblue;
+                margin-left: 8px;
+              }
+            }
+          }
+        }
+      }
+      .right-bottom {
+        margin-top: 20px;
+        .echarts-box {
+          width: 100%;
+          height: 270px;
+        }
+      }
+    }
+  }
+</style>

+ 484 - 0
src/views/vent/monitorManager/deviceMonitor/components/device/modal/gaspatrol.modal.vue

@@ -0,0 +1,484 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="`瓦斯巡检监测详情  ${currentTime}`"
+    width="1200px"
+    @ok="handleOk"
+    @cancel="handleCancel"
+    wrapClassName="bundle-modal"
+  >
+    <div class="fiber-modal">
+      <div class="modal-left">
+        <div
+          v-for="device in deviceList"
+          class="link-item"
+          :class="{ 'active-device-title': device.deviceID === activeDeviceID }"
+          :key="device.deviceID"
+        >
+          <span class="" @click="selectDevice(device.deviceID)">{{ device.strinstallpos }}</span>
+        </div>
+      </div>
+      <div class="modal-right">
+        <span class="base-title">实时监测参数</span>
+        <div class="right-top">
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="coval" style="width: 62px; height: 38px; margin-top: 10px" />
+            </div>
+            <div class="item-container">
+              <div class="title">一氧化碳</div>
+              <div class="value">{{ posMonitor.coval !== undefined && posMonitor.coval !== null ? posMonitor.coval : '-' }} <span>ppm</span> </div>
+            </div>
+          </div>
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="co2val" style="width: 72px; height: 46px" />
+            </div>
+            <div class="item-container">
+              <div class="title">二氧化碳</div>
+              <div class="value">{{ posMonitor.co2val !== undefined && posMonitor.co2val !== null ? posMonitor.co2val : '-' }} <span>%</span></div>
+            </div>
+          </div>
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="gasval" style="width: 72px; height: 46px" />
+            </div>
+            <div class="item-container">
+              <div class="title">甲烷</div>
+              <div class="value">{{ posMonitor.gasval !== undefined && posMonitor.gasval !== null ? posMonitor.gasval : '-' }} <span>%</span></div>
+            </div>
+          </div>
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="ch2val" style="width: 76px; height: 42px" />
+            </div>
+            <div class="item-container">
+              <div class="title">乙烯</div>
+              <div class="value">{{ posMonitor.ch2val !== undefined && posMonitor.ch2val !== null ? posMonitor.ch2val : '-' }} <span>ppm</span></div>
+            </div>
+          </div>
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="chval" style="width: 76px; height: 42px" />
+            </div>
+            <div class="item-container">
+              <div class="title">乙炔</div>
+              <div class="value">{{ posMonitor.chval !== undefined && posMonitor.chval !== null ? posMonitor.chval : '-' }} <span>ppm</span></div>
+            </div>
+          </div>
+          <div class="top-item">
+            <div class="icon">
+              <SvgIcon class="icon-style" name="o2val" style="width: 76px; height: 50px" />
+            </div>
+            <div class="item-container">
+              <div class="title">氧气</div>
+              <div class="value">{{ posMonitor.o2val !== undefined && posMonitor.o2val !== null ? posMonitor.o2val : '-' }} <span>%</span></div>
+            </div>
+          </div>
+          <div class="top-item warning-box">
+            <div class="icon">
+              <SvgIcon class="icon-style" size="42" name="alarm-warning" style="margin-top: 5px" />
+            </div>
+            <div class="item-container">
+              <div class="title">风险等级</div>
+              <div class="warning-value">{{ posMonitor['warnLevel_str'] ? posMonitor['warnLevel_str'] : '-' }}</div>
+            </div>
+          </div>
+          <div class="top-item warning-box">
+            <div class="icon">
+              <SvgIcon class="icon-style" size="42" name="link" style="margin-top: 5px" />
+            </div>
+            <div class="item-container">
+              <div class="title">连接状态</div>
+              <div class="warning-value">{{ posMonitor['netStatus'] == 1 ? '连接' : '未连接' }}</div>
+            </div>
+          </div>
+        </div>
+        <div class="right-bottom">
+          <span class="base-title">设备监测曲线</span>
+          <div class="echarts-box">
+            <BarAndLine
+              class="echarts-line"
+              xAxisPropType="time"
+              :dataSource="historyList"
+              height="100%"
+              :chartsColumns="chartsColumns"
+              :option="echatsOption"
+              chartsType="listMonitor"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, watch, shallowRef } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import BarAndLine from '/@/components/chart/BarAndLine.vue';
+  import { SvgIcon } from '/@/components/Icon';
+  import { Decoration7 as DvDecoration7, ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+  import dayjs from 'dayjs';
+
+  export default defineComponent({
+    components: { BasicModal, BarAndLine, SvgIcon, DvScrollBoard, DvDecoration7 },
+    props: {
+      dataSource: { type: Array },
+      activeID: { type: String },
+    },
+    setup(props) {
+      const currentTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
+      const modelRef = ref({});
+      const loading = ref(true);
+      const activeDeviceID = ref('');
+      const deviceList = ref<any[]>([]);
+      const historyList = ref<any[]>([]);
+      const posList = ref<any[]>([]);
+      const posMonitor = shallowRef({});
+
+      const echatsOption = {
+        grid: {
+          top: '25%',
+          left: '30',
+          right: '45',
+          bottom: '3%',
+          containLabel: true,
+        },
+        toolbox: {
+          feature: {},
+        },
+      };
+
+      const chartsColumns = [
+        {
+          legend: '一氧化碳',
+          seriesName: '(ppm)',
+          ymax: 10,
+          yname: 'ppm',
+          linetype: 'line',
+          yaxispos: 'left',
+          color: '#FDB146',
+          sort: 1,
+          xRotate: 0,
+          dataIndex: 'coval',
+        },
+        {
+          legend: '乙炔',
+          seriesName: '',
+          ymax: 10,
+          yname: 'ppm',
+          linetype: 'line',
+          yaxispos: 'left',
+          color: '#00FFA8',
+          sort: 1,
+          xRotate: 0,
+          dataIndex: 'chval',
+        },
+        {
+          legend: '乙烯',
+          seriesName: '',
+          ymax: 10,
+          yname: 'ppm',
+          linetype: 'line',
+          yaxispos: 'left',
+          color: '#AE19FF',
+          sort: 1,
+          xRotate: 0,
+          dataIndex: 'ch2val',
+        },
+        {
+          legend: '二氧化碳',
+          seriesName: '(%)',
+          ymax: 20,
+          yname: '%',
+          linetype: 'line',
+          yaxispos: 'right',
+          color: '#9C83D9',
+          sort: 2,
+          xRotate: 0,
+          dataIndex: 'co2val',
+        },
+        {
+          legend: '甲烷',
+          seriesName: '',
+          ymax: 20,
+          yname: '%',
+          linetype: 'line',
+          yaxispos: 'right',
+          color: '#DA3914',
+          sort: 2,
+          xRotate: 0,
+          dataIndex: 'gasval',
+        },
+        {
+          legend: '氧气',
+          seriesName: '(氧气%)',
+          ymax: 30,
+          yname: '%',
+          linetype: 'line',
+          yaxispos: 'right',
+          color: '#03C2EC',
+          sort: 3,
+          xRotate: 0,
+          dataIndex: 'o2val',
+        },
+      ];
+      const [register, { setModalProps, closeModal }] = useModalInner();
+
+      function handleVisibleChange(visible) {
+        if (visible) {
+          loading.value = true;
+          setModalProps({ loading: true, confirmLoading: true });
+
+          setTimeout(() => {
+            loading.value = false;
+            setModalProps({ loading: false, confirmLoading: false });
+          }, 1000);
+        }
+      }
+
+      // 选择监测
+      function selectDevice(id) {
+        loading.value = true;
+        setModalProps({ loading: true, confirmLoading: true });
+        setTimeout(() => {
+          loading.value = false;
+          activeDeviceID.value = id;
+          setModalProps({ loading: false, confirmLoading: false });
+        }, 300);
+      }
+
+      function handleOk(e) {
+        e.preventDefault();
+        closeModal();
+      }
+
+      function handleCancel(e) {
+        e.preventDefault();
+        closeModal();
+      }
+
+      watch([() => props.dataSource, () => props.activeID], ([newDataSource, newActiveID], [oldDataSource, oldActiveID]) => {
+        // if (newActiveID != oldActiveID) {
+        //   activeDeviceID.value = newActiveID as string;
+        // }
+        activeDeviceID.value = activeDeviceID.value ? activeDeviceID.value : newActiveID;
+        deviceList.value = newDataSource?.filter((item: any, index) => {
+          if ((!activeDeviceID.value && index == 0) || item.deviceID === activeDeviceID.value) {
+            // activeDeviceID.value = item.deviceID;
+            posMonitor.value = Object.assign(item, item.readData);
+            historyList.value = item['history'];
+          }
+          item.readTime = item.readTime?.substring(11);
+          return item;
+        });
+      });
+
+      return {
+        register,
+        model: modelRef,
+        currentTime,
+        handleVisibleChange,
+        selectDevice,
+        handleOk,
+        handleCancel,
+        deviceList,
+        historyList,
+        activeDeviceID,
+        posMonitor,
+        echatsOption,
+        posList,
+        chartsColumns,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .bundle-modal {
+    .zxm-modal {
+      top: 30px !important;
+    }
+  }
+</style>
+
+<style lang="less" scoped>
+  .fiber-modal {
+    width: 100%;
+    height: 650px;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+
+    .modal-left {
+      width: 200px;
+      height: 100%;
+      overflow-y: auto;
+      background: #ffffff11;
+      padding: 5px;
+      border-radius: 5px;
+      .active-device-title {
+        color: aqua;
+      }
+      .link-item {
+        position: relative;
+        cursor: pointer;
+        line-height: 30px;
+        padding-left: 30px;
+        span:hover {
+          color: #89ffff;
+        }
+        &::after {
+          content: '';
+          position: absolute;
+          display: block;
+          width: 8px;
+          height: 8px;
+          top: 12px;
+          left: 10px;
+          transform: rotateZ(45deg) skew(10deg, 10deg);
+          background: #45d3fd;
+        }
+      }
+    }
+    .modal-right {
+      width: calc(100% - 220px);
+      overflow-y: auto;
+      .base-title {
+        line-height: 32px;
+        position: relative;
+        padding-left: 20px;
+        &::after {
+          content: '';
+          position: absolute;
+          display: block;
+          width: 4px;
+          height: 12px;
+          top: 4px;
+          left: 10px;
+          background: #45d3fd;
+          border-radius: 4px;
+        }
+      }
+      .right-top {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        margin-bottom: 10px;
+        .top-item {
+          width: 220px;
+          height: 100px;
+          display: flex;
+          flex-direction: row;
+          justify-content: center;
+          border: 1px solid rgba(25, 237, 255, 0.4);
+          box-shadow: inset 0 0 10px rgba(0, 197, 255, 0.6);
+          background: rgba(0, 0, 0, 0.06666666666666667);
+          padding-top: 20px;
+          margin: 10px 0;
+          .icon {
+            margin-right: 10px;
+            margin-top: 5px;
+            color: #fdb146;
+          }
+          .item-container {
+            width: 110px;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            div {
+              text-align: center;
+            }
+            .title {
+              font-size: 18px;
+            }
+            .value {
+              text-shadow: 0 0 25px #00fbfe;
+              background: linear-gradient(0deg, #45d3fd, #45d3fd, #61ddb1, #61ddb1);
+              font-style: normal;
+              background-size: cover;
+              font-family: electronicFont;
+              font-size: 30px;
+              -webkit-background-clip: text;
+              background-clip: text;
+              -webkit-text-fill-color: transparent;
+              position: relative;
+              top: -8px;
+
+              span {
+                font-family: Arial, Helvetica, sans-serif;
+                font-size: 18px;
+                color: aliceblue;
+              }
+            }
+          }
+        }
+        .warning-box {
+          padding-top: 0px;
+          .icon {
+            margin-top: 20px;
+            :deep(.icon-style) {
+              width: auto;
+              color: #fdb146;
+            }
+          }
+          .warning-value {
+            font-size: 18px;
+            color: #61ddb1;
+          }
+        }
+      }
+      .right-center {
+        margin-top: 20px;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        .table-box {
+          position: relative;
+          width: 500px;
+          height: 250px;
+        }
+        .warning-box {
+          width: calc(100% - 520px);
+          .warning-container {
+            width: 100%;
+            height: convert;
+            background: #009acd00;
+            :deep(.dv-scroll-board) {
+              .row-item {
+                height: 40px !important;
+                line-height: 40px !important;
+              }
+              .header-item {
+                border-top: 1px solid #91e9fe !important;
+                border-bottom: 1px solid #91e9fe !important;
+              }
+            }
+          }
+        }
+      }
+      .right-bottom {
+        margin-top: 20px;
+        .echarts-box {
+          width: 100%;
+          height: 320px;
+          position: relative;
+          .echarts-line {
+            width: calc(100% + 80px);
+            position: absolute;
+          }
+        }
+      }
+    }
+  }
+  :deep(.zxm-table-body) {
+    border: 1px solid rgba(57, 232, 255, 0.2) !important;
+    .zxm-table-tbody > tr > td {
+      border: none !important;
+    }
+  }
+  :deep(.zxm-table-cell) {
+    border-right: none !important;
+  }
+</style>

+ 499 - 0
src/views/vent/monitorManager/gateMonitor/gate.threejs.yj.ts

@@ -0,0 +1,499 @@
+import * as THREE from 'three';
+import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+import { getTextCanvas, renderVideo } from '/@/utils/threejs/util';
+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 FmYj {
+  modelName = 'FmYj';
+  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: `净通行高度(m):`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 155,
+      },
+      {
+        text: `${selectData.fclearheight ? selectData.fclearheight : '-'}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 330,
+        y: 155,
+      },
+      {
+        text: `净通行宽度(m): `,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 215,
+      },
+      {
+        text: ` ${selectData.fclearwidth ? selectData.fclearwidth : '-'}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 320,
+        y: 215,
+      },
+      {
+        text: `故障诊断:`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 0,
+        y: 275,
+      },
+      {
+        text: `${selectData.warnLevel_str ? selectData.warnLevel_str : '-'}`,
+        font: 'normal 30px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: 320,
+        y: 275,
+      },
+      {
+        text: History_Type['type'] == 'remote' ? `国能神东煤炭集团监制` : '煤炭科学技术研究院有限公司研制',
+        font: 'normal 28px Arial',
+        color: '#00FF00',
+        strokeStyle: '#007400',
+        x: History_Type['type'] == 'remote' ? 90 : 30,
+        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(3.825, 0.01, -0.48);
+        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() {
+    const fmGroup = this.group?.getObjectByName('Fm-Yj');
+    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', 22, 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', 22, 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.05) {
+    if (this.clipActionArr.frontDoor && this.clipActionArr.backDoor) {
+      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 = 22;
+            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 = 22;
+            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);
+    }
+  }
+
+  mountedThree(playerDom) {
+    this.group = new THREE.Object3D();
+    this.group.name = this.modelName;
+
+    return new Promise((resolve) => {
+      if (!this.model) {
+        resolve(null);
+      }
+      this.model.setGLTFModel(['Fm-Yj'], 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 FmYj;

+ 217 - 0
src/views/vent/monitorManager/windowMonitor/dandaoFcXk.threejs.ts

@@ -0,0 +1,217 @@
+import * as THREE from 'three';
+
+import { getTextCanvas } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class singleWindowXk {
+  model;
+  modelName = 'xkFcGroup';
+  group: THREE.Object3D = new THREE.Object3D();
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  windowsActionArr = {
+    frontWindow: [],
+  };
+  constructor(model) {
+    this.model = model;
+    this.group.name = 'xkFcGroup';
+  }
+  addLight = () => {
+    if (!this.group || !this.group) return;
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
+    directionalLight.position.set(-437, 61, 559);
+    this.group.add(directionalLight);
+  };
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-35, 25, 15);
+  }
+
+  addMonitorText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const textArr = [
+      {
+        text: `远程定量调节自动风窗`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 100,
+        y: 95,
+      },
+      {
+        text: `${selectData.OpenDegree ? '开度值(%)' : selectData.forntArea ? '过风面积(m2)' : '过风面积(m2)'}:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 145,
+      },
+      {
+        text: selectData.OpenDegree
+          ? Number(`${selectData.OpenDegree}`).toFixed(2)
+          : selectData.forntArea
+          ? Number(`${selectData.forntArea}`).toFixed(2)
+          : '-',
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 330,
+        y: 145,
+      },
+      {
+        text: `${selectData.frontRearDP ? '风窗压差(Pa)' : selectData.windSpeed ? '风速(m/s)' : '通信状态:'}:`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 200,
+      },
+      {
+        text: `${
+          selectData.frontRearDP
+            ? selectData.frontRearDP
+            : selectData.windSpeed
+            ? selectData.windSpeed
+            : selectData.netStatus == '0'
+            ? '断开'
+            : '连接'
+        }`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 330,
+        y: 200,
+      },
+      {
+        text: `${selectData.fWindowM3 ? '过风量(m³/min)' : '风窗道数'}: `,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 5,
+        y: 250,
+      },
+      {
+        text: `${selectData.fWindowM3 ? selectData.fWindowM3 : selectData.nwindownum}`,
+        font: 'normal 30px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: 330,
+        y: 250,
+      },
+      {
+        text: History_Type['type'] == 'remote' ? `国能神东煤炭集团监制` : '煤炭科学技术研究院有限公司研制',
+        font: 'normal 28px Arial',
+        color: '#009900',
+        strokeStyle: '#002200',
+        x: History_Type['type'] == 'remote' ? 90 : 30,
+        y: 300,
+      },
+    ];
+    getTextCanvas(750, 546, textArr, '').then((canvas: HTMLCanvasElement) => {
+      const textMap = new THREE.CanvasTexture(canvas); // 关键一步
+      const textMaterial = new THREE.MeshBasicMaterial({
+        // 关于材质并未讲解 实操即可熟悉                 这里是漫反射类似纸张的材质,对应的就有高光类似金属的材质.
+        map: textMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.DoubleSide, // 这里是双面渲染的意思
+      });
+      textMap.dispose();
+      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.0038, 0.004, 0.0038);
+        planeMesh.position.set(4.44, -0.165, -0.46);
+        this.group?.add(planeMesh);
+      }
+    });
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {}
+
+  play(rotationParam, flag) {
+    if (!this.windowsActionArr.frontWindow) {
+      return;
+    }
+    if (flag === 1) {
+      // 前风窗动画
+      this.windowsActionArr.frontWindow.forEach((mesh: THREE.Mesh) => {
+        gsap.to(mesh.rotation, {
+          y: THREE.MathUtils.degToRad(rotationParam.frontDeg1),
+          duration: (1 / 9) * Math.abs(rotationParam.frontDeg1 - mesh.rotation.y),
+          overwrite: true,
+        });
+      });
+    } else if (flag === 0) {
+      ([...this.windowsActionArr.frontWindow] as THREE.Mesh[]).forEach((mesh) => {
+        gsap.to(mesh.rotation, {
+          y: 0,
+          overwrite: true,
+        });
+      });
+    }
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+  }
+
+  mouseUpModel() {
+    // 10s后开始摆动
+    if (!this.animationTimer && !this.isLRAnimation) {
+      this.animationTimer = setTimeout(() => {
+        this.isLRAnimation = true;
+      }, 10000);
+    }
+  }
+
+  /* 风门动画 */
+  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;
+      }
+    }
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setGLTFModel(['Fc-Xk'], this.group).then(() => {
+        debugger;
+        this.setModalPosition();
+        this.initAnimation();
+        this.addLight();
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    this.model.clearGroup(this.group);
+    this.model = null;
+    this.group = null;
+  }
+}
+export default singleWindowXk;

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

@@ -290,7 +290,7 @@
     const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
     Object.assign(selectData, initData, selectRow, baseData);
 
-    const type = selectData.nwindownum == 1 ? 'singleWindow' : 'doubleWindow';
+    const type = selectData.nwindownum == 1 ? 'singleXkWindow' : 'doubleWindow';
     setModelType(type).then(() => {
       addMonitorText(selectData);
       playAnimation(selectRow, selectData.maxarea, true);

+ 15 - 0
src/views/vent/monitorManager/windowMonitor/window.threejs.ts

@@ -125,6 +125,17 @@ export const setModelType = (type) => {
         resolve(null);
         await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, { x: 66.257, y: 57.539, z: 94.313 }, { x: 0, y: 0, z: 0 }, model);
       }, 300);
+    } else if (windowType === 'singleXkWindow') {
+      // 显示单道风窗
+      model.startAnimation = singleWindowXkObj.render.bind(singleWindowXkObj);
+      model.scene?.remove(group);
+      group = singleWindowXkObj.group;
+      const oldCameraPosition = { x: 100, y: 0, z: 500 };
+      model.scene?.add(singleWindowXkObj.group);
+      setTimeout(async () => {
+        resolve(null);
+        await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, { x: 66.257, y: 57.539, z: 94.313 }, { x: 0, y: 0, z: 0 }, model);
+      }, 300);
     }
   });
 };
@@ -138,6 +149,7 @@ export const mountedThree = (playerDom) => {
     resolve(null);
     model.camera.position.set(100, 0, 1000);
     doubleWindowObj = new doubleWindow(model);
+    singleWindowXkObj = new singleWindowXk(model);
     if (sysOrgCode === 'sdmtjtbetmk') {
       const singleWindowBet = await import('./dandaoFcBet.threejs');
       if (singleWindowBet) singleWindowObj = new singleWindowBet.default(model);
@@ -145,6 +157,7 @@ export const mountedThree = (playerDom) => {
       singleWindowObj = new singleWindow(model);
     }
     doubleWindowObj.mountedThree(playerDom);
+    singleWindowXkObj.mountedThree();
     singleWindowObj.mountedThree(playerDom);
     model.animate();
     addLight();
@@ -159,10 +172,12 @@ export const destroy = () => {
     model.isRender = false;
     doubleWindowObj.destroy();
     singleWindowObj.destroy();
+    singleWindowXkObj.destroy();
     model.destroy();
     model = null;
     group = null;
     singleWindowObj = null;
     doubleWindowObj = null;
+    singleWindowXkObj = null;
   }
 };