Bläddra i källkod

[Mod 0000]防火门监控系统修改三维动画和二维动画关联逻辑

wangkeyi 9 timmar sedan
förälder
incheckning
3781fab0c5

+ 43 - 27
src/views/vent/home/configurable/firedoor/components/fireDoorBoard.vue

@@ -77,11 +77,10 @@
   import { ref, onMounted, watch, inject } from 'vue';
   import { ref, onMounted, watch, inject } from 'vue';
   import { getFormattedText } from '../../hooks/helper';
   import { getFormattedText } from '../../hooks/helper';
   import pidaihangSVG from '/@/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue';
   import pidaihangSVG from '/@/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue';
-  import { nextTick } from 'process';
   import HandleModal from '/@/views/vent/monitorManager/gateMonitor/modal.vue';
   import HandleModal from '/@/views/vent/monitorManager/gateMonitor/modal.vue';
-  import { deviceControlApi } from '/@/api/vent/index';
   import { message } from 'ant-design-vue';
   import { message } from 'ant-design-vue';
   import { doorControlApi } from '/@/views/vent/home/configurable/firedoor/configurable.api.fireDoorMonitor';
   import { doorControlApi } from '/@/views/vent/home/configurable/firedoor/configurable.api.fireDoorMonitor';
+  import { trigger3DAnimation } from '../threejs/firedoor.threejs';
   const props = withDefaults(
   const props = withDefaults(
     defineProps<{
     defineProps<{
       config: {
       config: {
@@ -116,6 +115,45 @@
       childRefs.value[index] = el;
       childRefs.value[index] = el;
     }
     }
   };
   };
+
+  /** 统一触发 2D SVG 动画 + 3D Three.js 动画 */
+  function executeAnimation(targetIndex: number, code: string) {
+    if (targetIndex === -1) return;
+    const isOpen = code.includes('Open');
+
+    // 2D SVG 动画
+    if (childRefs.value[targetIndex]) {
+      if (code === 'AllOpen_S' || code === 'AllClose_S') {
+        childRefs.value[targetIndex].animate(isOpen, isOpen, isOpen);
+      } else if (code === 'frontGateOpen_S' || code === 'frontGateClose_S') {
+        childRefs.value[targetIndex].animateFireDoor(isOpen);
+      } else if (code === 'MBOpen_S' || code === 'MBClose_S') {
+        childRefs.value[targetIndex].animateBeltDoor(isOpen);
+      }
+    }
+
+    // 3D 动画:构造期望状态的门数据并触发
+    if (props.data && Array.isArray(props.data)) {
+      const doors = props.data.map((door: any, i: number) => {
+        if (i === targetIndex && door.readData) {
+          const updated = {
+            ...door,
+            readData: { ...door.readData },
+          };
+          if (code === 'AllOpen_S' || code === 'AllClose_S' || code === 'frontGateOpen_S' || code === 'frontGateClose_S') {
+            updated.readData.frontGateOpen = { ...updated.readData.frontGateOpen, value: isOpen ? '1' : '0' };
+          }
+          if (code === 'AllOpen_S' || code === 'AllClose_S' || code === 'MBOpen_S' || code === 'MBClose_S') {
+            updated.readData.MBOpen = { ...updated.readData.MBOpen, value: isOpen ? '1' : '0' };
+          }
+          return updated;
+        }
+        return door;
+      });
+      trigger3DAnimation(doors);
+    }
+  }
+
   function monitorAnimation(selectData, index) {
   function monitorAnimation(selectData, index) {
     if (!selectData?.readData) return;
     if (!selectData?.readData) return;
     const frontOpen = selectData.readData?.frontGateOpen?.value === '1';
     const frontOpen = selectData.readData?.frontGateOpen?.value === '1';
@@ -130,13 +168,13 @@
     selectData.value = item;
     selectData.value = item;
     modalTitle.value = title;
     modalTitle.value = title;
     paramcode.value = code;
     paramcode.value = code;
-    openGateControlModal();
+    modalIsShow.value = true;
   }
   }
   function oneKeyClose(item, index, code, title) {
   function oneKeyClose(item, index, code, title) {
     selectData.value = item;
     selectData.value = item;
     modalTitle.value = title;
     modalTitle.value = title;
     paramcode.value = code;
     paramcode.value = code;
-    openGateControlModal();
+    modalIsShow.value = true;
   }
   }
 
 
   function handleOK(passWord, handlerState, value?) {
   function handleOK(passWord, handlerState, value?) {
@@ -165,16 +203,7 @@
           }
           }
 
 
           const targetIndex = props.data.findIndex((item) => item.deviceID === selectData.value.deviceID);
           const targetIndex = props.data.findIndex((item) => item.deviceID === selectData.value.deviceID);
-          if (targetIndex !== -1 && childRefs.value[targetIndex]) {
-            const isOpen = paramcode.value.includes('Open');
-            if (paramcode.value === 'AllOpen_S' || paramcode.value === 'AllClose_S') {
-              childRefs.value[targetIndex].animate(isOpen, isOpen, isOpen);
-            } else if (paramcode.value === 'frontGateOpen_S' || paramcode.value === 'frontGateClose_S') {
-              childRefs.value[targetIndex].animateFireDoor(isOpen);
-            } else if (paramcode.value === 'MBOpen_S' || paramcode.value === 'MBClose_S') {
-              childRefs.value[targetIndex].animateBeltDoor(isOpen);
-            }
-          }
+          executeAnimation(targetIndex, paramcode.value);
           // 触发刷新事件
           // 触发刷新事件
           emit('refresh');
           emit('refresh');
         } else {
         } else {
@@ -191,19 +220,6 @@
     modalType.value = '';
     modalType.value = '';
   }
   }
 
 
-  /** 封装通用的模态框打开方法 */
-  const openGateControlModal = (
-    customTitle?: string // 自定义标题(可选)
-  ) => {
-    // 统一赋值逻辑
-    modalIsShow.value = true;
-
-    // 如果有模拟密码,则直接执行操作
-    // if (globalConfig?.simulatedPassword) {
-    //   handleOK('', String(controlType));
-    // }
-  };
-
   watch(
   watch(
     () => props.data,
     () => props.data,
     (newData) => {
     (newData) => {

+ 79 - 37
src/views/vent/home/configurable/firedoor/threejs/firedoor.threejs.ts

@@ -5,6 +5,16 @@ import { ref, watch, Ref, nextTick } from 'vue';
 // 用于存储当前的门状态数据,方便外部调试或扩展
 // 用于存储当前的门状态数据,方便外部调试或扩展
 const gateList = ref<any[]>([]);
 const gateList = ref<any[]>([]);
 
 
+// 模块级变量:存储当前的 handleGateAnimate,供外部组件直接调用 3D 动画
+let currentHandleGateAnimate: ((doors: any[]) => void) | null = null;
+
+/** 供外部(如 2D 组件)调用的 3D 动画触发函数 */
+export function trigger3DAnimation(doors: any[]) {
+  if (currentHandleGateAnimate) {
+    currentHandleGateAnimate(doors);
+  }
+}
+
 /**
 /**
  * 初始化防火门 3D 动画
  * 初始化防火门 3D 动画
  * @param modal Three.js 模态框实例
  * @param modal Three.js 模态框实例
@@ -14,36 +24,74 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
   await nextTick();
   await nextTick();
 
 
   if (!modal || !modal.scene) {
   if (!modal || !modal.scene) {
-    console.error('模型加载失败');
     return;
     return;
   }
   }
 
 
-  // 获取主模型对象 (假设模型名称结构,根据实际模型调整)
-  // 注意:请确保模型加载完成后调用此函数,或者在 modal.scene 中存在这些对象
-  const fireDoorModal = modal.scene.getObjectByName('pidai')?.getObjectByName('PiDaiHang_1');
-  if (!fireDoorModal) return;
+  // 尝试多种方式定位防火门模型的容器对象
+  let fireDoorModal: THREE.Object3D | undefined;
 
 
-  resetModalParam(modal);
+  // 方式1:通过已知的层级路径 pidai → PiDaiHang_1
+  const pidaiObj = modal.scene.getObjectByName('pidai');
+  if (pidaiObj) {
+    fireDoorModal = pidaiObj.getObjectByName('PiDaiHang_1');
+  }
 
 
-  // 初始化时,强制找到所有门并归零,确保“关闭”状态是物理上的 0,0,0
-  const initFireDoor = fireDoorModal.getObjectByName('men_1');
-  const initBeltCover = fireDoorModal.getObjectByName('gaiban');
+  // 方式2:直接在场景中搜索 PiDaiHang_1
+  if (!fireDoorModal) {
+    fireDoorModal = modal.scene.getObjectByName('PiDaiHang_1');
+  }
 
 
-  if (initFireDoor) {
-    initFireDoor.rotation.set(0, 0, 0); // 强制归零
+  // 方式3:搜索名字中包含 PiDaiHang 的对象
+  if (!fireDoorModal) {
+    modal.scene.traverse((obj: THREE.Object3D) => {
+      if (!fireDoorModal && obj.name.includes('PiDaiHang')) {
+        fireDoorModal = obj;
+      }
+    });
   }
   }
-  if (initBeltCover) {
-    initBeltCover.rotation.set(0, 0, 0); // 强制归零
+
+  // 方式4:通过模型根节点 (modelName = 'pidai-fire-door') 搜索
+  if (!fireDoorModal) {
+    const modelRoot = modal.scene.getObjectByName('pidai-fire-door');
+    if (modelRoot) {
+      modelRoot.traverse((obj: THREE.Object3D) => {
+        if (!fireDoorModal && (obj.name.includes('PiDaiHang') || obj.name.includes('pidaihang') || obj.name.includes('PiDai'))) {
+          fireDoorModal = obj;
+        }
+      });
+    }
   }
   }
 
 
   if (!fireDoorModal) {
   if (!fireDoorModal) {
-    console.warn('模型元素未找到');
     return;
     return;
   }
   }
 
 
-  // 重置控制器参数,保持视角稳定
+  // 类型收窄:确保后续代码中 fireDoorModal 为非 undefined
+  const doorContainer: THREE.Object3D = fireDoorModal;
+
   resetModalParam(modal);
   resetModalParam(modal);
 
 
+  /**
+   * 在容器内查找与给定模式匹配的网格对象
+   * 优先精确匹配 men_{suffix} / gaiban_{suffix},找不到则回退到包含关键词的对象
+   */
+  const findMesh = (container: THREE.Object3D, patterns: string[]): THREE.Object3D | undefined => {
+    for (const pattern of patterns) {
+      const exact = container.getObjectByName(pattern);
+      if (exact) return exact;
+    }
+    // 模糊匹配
+    let found: THREE.Object3D | undefined;
+    container.traverse((child: THREE.Object3D) => {
+      if (found) return;
+      const lower = child.name.toLowerCase();
+      if (patterns.some((p) => lower.includes(p.toLowerCase()))) {
+        found = child;
+      }
+    });
+    return found;
+  };
+
   /**
   /**
    * 核心动画处理函数
    * 核心动画处理函数
    * @param doors 门设备数据数组
    * @param doors 门设备数据数组
@@ -51,34 +99,28 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
   const handleGateAnimate = (doors: any[]) => {
   const handleGateAnimate = (doors: any[]) => {
     if (!doors || doors.length === 0) return;
     if (!doors || doors.length === 0) return;
 
 
-    doors.forEach((gateData: any) => {
-      // 查找防火门对象 (men_1, men_2, ...)
-      const fireDoorMesh = fireDoorModal.getObjectByName(`men_1`); //  fallback 测试用
-
-      // 查找皮带密闭对象 (gaiban_1, gaiban_2, ...)
-      const beltCoverMesh = fireDoorModal.getObjectByName(`gaiban`); // fallback 测试用
+    doors.forEach((gateData: any, index: number) => {
+      // 根据门的索引查找对应的 3D 模型对象 (men_1, men_2, ...)
+      const doorSuffix = index + 1;
+      const fireDoorMesh = findMesh(doorContainer, [`men_${doorSuffix}`, `men_1`, 'men']);
+      const beltCoverMesh = findMesh(doorContainer, [`gaiban_${doorSuffix}`, 'gaiban', 'gaiban_1']);
 
 
       // 获取状态值
       // 获取状态值
-      // 注意:数据结构需与 2D 视图一致,通常是 readData.frontGateOpen.value
       const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
       const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
       const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
       const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
 
 
-      // 判断状态 (1 或 '1' 为打开)
-      const isFireDoorOpen = frontGateOpenVal == 1;
-      const isBeltCoverOpen = mbOpenVal == 1;
+      // 判断状态 (与 2D 动画保持一致:'1' 为打开)
+      const isFireDoorOpen = frontGateOpenVal == '1' || frontGateOpenVal == 1;
+      const isBeltCoverOpen = mbOpenVal == '1' || mbOpenVal == 1;
 
 
       // 执行防火门动画
       // 执行防火门动画
       if (fireDoorMesh) {
       if (fireDoorMesh) {
         animateFireDoor(fireDoorMesh, isFireDoorOpen);
         animateFireDoor(fireDoorMesh, isFireDoorOpen);
-      } else {
-        // console.warn(`Fire door mesh not found for alias: ${posAlias}`);
       }
       }
 
 
       // 执行皮带密闭动画
       // 执行皮带密闭动画
       if (beltCoverMesh) {
       if (beltCoverMesh) {
         animateBeltCover(beltCoverMesh, isBeltCoverOpen);
         animateBeltCover(beltCoverMesh, isBeltCoverOpen);
-      } else {
-        // console.warn(`Belt cover mesh not found for alias: ${posAlias}`);
       }
       }
     });
     });
   };
   };
@@ -91,14 +133,13 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
   const animateFireDoor = (mesh: THREE.Object3D, isOpen: boolean) => {
   const animateFireDoor = (mesh: THREE.Object3D, isOpen: boolean) => {
     const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
     const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
 
 
-    // 使用 GSAP 进行平滑过渡
     gsap.to(mesh.rotation, {
     gsap.to(mesh.rotation, {
       x: 0,
       x: 0,
       y: 0,
       y: 0,
       z: targetRotationZ,
       z: targetRotationZ,
-      duration: 1.5, // 动画时长,可根据需要调整
+      duration: 1.5,
       ease: 'power2.inOut',
       ease: 'power2.inOut',
-      overwrite: true, // 覆盖之前的动画
+      overwrite: true,
     });
     });
   };
   };
 
 
@@ -110,17 +151,19 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
   const animateBeltCover = (mesh: THREE.Object3D, isOpen: boolean) => {
   const animateBeltCover = (mesh: THREE.Object3D, isOpen: boolean) => {
     const targetRotationY = isOpen ? THREE.MathUtils.degToRad(-70) : 0;
     const targetRotationY = isOpen ? THREE.MathUtils.degToRad(-70) : 0;
 
 
-    // 使用 GSAP 进行平滑过渡
     gsap.to(mesh.rotation, {
     gsap.to(mesh.rotation, {
       x: 0,
       x: 0,
       y: targetRotationY,
       y: targetRotationY,
       z: 0,
       z: 0,
-      duration: 1.5, // 动画时长
+      duration: 1.5,
       ease: 'power2.inOut',
       ease: 'power2.inOut',
       overwrite: true,
       overwrite: true,
     });
     });
   };
   };
 
 
+  // 将处理函数挂载到模块级变量,供外部组件(如 2D 面板)调用
+  currentHandleGateAnimate = handleGateAnimate;
+
   // 监听数据变化,驱动动画
   // 监听数据变化,驱动动画
   watch(
   watch(
     modalMonitorData,
     modalMonitorData,
@@ -142,7 +185,7 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
     modal.initCSS3Renderer('#css3dContainer');
     modal.initCSS3Renderer('#css3dContainer');
   }
   }
 
 
-  // 暴露方法供外部调用(可选)
+  // 暴露方法供外部调用
   return {
   return {
     gateList,
     gateList,
     handleGateAnimate,
     handleGateAnimate,
@@ -152,7 +195,6 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
 function resetModalParam(modal: any) {
 function resetModalParam(modal: any) {
   if (!modal.orbitControls) return;
   if (!modal.orbitControls) return;
 
 
-  // 设置合适的控制器参数
   modal.orbitControls.rotateSpeed = 0.5;
   modal.orbitControls.rotateSpeed = 0.5;
   modal.orbitControls.zoomSpeed = 0.6;
   modal.orbitControls.zoomSpeed = 0.6;
   modal.orbitControls.panSpeed = 0.5;
   modal.orbitControls.panSpeed = 0.5;
@@ -163,7 +205,7 @@ function resetModalParam(modal: any) {
 // 清理函数
 // 清理函数
 export function destroy(modal: any) {
 export function destroy(modal: any) {
   gateList.value = [];
   gateList.value = [];
-  // 如果有其他需要清理的资源,在这里处理
+  currentHandleGateAnimate = null;
   if (modal.canvasContainer) {
   if (modal.canvasContainer) {
     // 移除事件监听器等
     // 移除事件监听器等
   }
   }