Browse Source

添加注气动画

hongrunxia 4 weeks ago
parent
commit
044eae6931

BIN
public/texture/3-2.png


+ 19 - 7
src/layouts/default/header/components/user-dropdown/index.vue

@@ -139,11 +139,23 @@
       }
 
       function clearModalCache() {
-        if (window['CustomDB']) {
-          const tables = window['CustomDB'].tables[0];
-          tables.clear();
-          location.reload();
-        }
+        return new Promise((resolve) => {
+          if (window['CustomDB']) {
+            // 清除指定表的所有数据
+            window['CustomDB'].modal
+              .clear()
+              .then(() => {
+                console.log('表数据已成功清除!');
+                resolve(null);
+                window.location.reload();
+              })
+              .catch((error) => {
+                console.error('清除数据时发生错误:', error);
+                resolve(null);
+                window.location.reload();
+              });
+          }
+        });
       }
 
       // 切换部门
@@ -159,7 +171,7 @@
         updatePasswordRef.value.show(userStore.getUserInfo.username);
       }
       // update-end--author:liaozhiyang---date:20230901---for:【QQYUN-6333】空路由问题—首次访问资源太大
-      function handleMenuClick(e: { key: MenuEvent }) {
+      async function handleMenuClick(e: { key: MenuEvent }) {
         switch (e.key) {
           case 'logout':
             handleLoginOut();
@@ -174,7 +186,7 @@
             clearCache();
             break;
           case 'modalCache':
-            clearModalCache();
+            await clearModalCache();
             break;
           case 'depart':
             updateCurrentDepart();

+ 91 - 0
src/utils/threejs/useEvent.ts

@@ -168,3 +168,94 @@ export default function useEvent() {
 
   return { mouseDownFn, mouseUpFn, mousemoveFn };
 }
+
+export function modelMouseHandler(modal: UseThree, group: THREE.Object3D | THREE.Object3D[], event: MouseEvent, callBack?: Function) {
+  if (!modal || !modal.canvasContainer || !modal.orbitControls) return;
+  const appStore = useAppStore();
+
+  const widthScale = appStore.getWidthScale;
+  const heightScale = appStore.getHeightScale;
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  modal.mouse.x = ((-modal.canvasContainer.getBoundingClientRect().left + event.clientX) / (modal.canvasContainer.clientWidth * widthScale)) * 2 - 1;
+  modal.mouse.y =
+    -((-modal.canvasContainer.getBoundingClientRect().top + event.clientY) / (modal.canvasContainer.clientHeight * heightScale)) * 2 + 1;
+  (modal.rayCaster as THREE.Raycaster).setFromCamera(modal.mouse, modal.camera as THREE.Camera);
+  if (group) {
+    let intersects = <THREE.Intersection[]>[];
+    if (Object.prototype.toString.call(group) === '[object Array]') {
+      intersects = modal.rayCaster?.intersectObjects([...(group as THREE.Object3D[])], true) as THREE.Intersection[];
+    } else {
+      intersects = modal.rayCaster?.intersectObjects((group as THREE.Object3D).children, true) as THREE.Intersection[];
+    }
+    if (callBack) callBack(intersects);
+  }
+}
+
+export const mouseUpFn = (modal, point: THREE.Vector3, renderCallBack?) => {
+  const target0 = <THREE.Vector3>modal.orbitControls.target.clone();
+  const oldCameraPosition = <THREE.Vector3>modal.camera.position.clone();
+
+  const nor = point.clone().sub(oldCameraPosition.clone()).normalize();
+  const d = 1;
+
+  const animateObj = {
+    x1: target0.x, // 相机x
+    y1: target0.y, // 相机y
+    z1: target0.z, // 相机z
+    x2: oldCameraPosition.x, // 相机x
+    y2: oldCameraPosition.y, // 相机y
+    z2: oldCameraPosition.z, // 相机z
+  };
+  let newAnimateObj;
+
+  if (oldCameraPosition.z > target0.z) {
+    newAnimateObj = {
+      x1: point.x,
+      y1: point.y,
+      z1: point.z,
+      x2: point.x,
+      y2: Math.abs(nor.y) * d * 0.1 + point.y,
+      z2: Math.abs(nor.z) * d * 0.3 + point.z,
+    };
+  } else {
+    newAnimateObj = {
+      x1: point.x,
+      y1: point.y,
+      z1: point.z,
+      x2: point.x,
+      y2: Math.abs(nor.y) * d * 0.1 + point.y,
+      z2: -Math.abs(nor.z) * d * 0.3 + point.z,
+    };
+  }
+
+  modal.orbitControls.renderEnabled = false;
+  modal.orbitControls.enabled = false;
+  gsap.fromTo(
+    animateObj,
+    {
+      x1: target0.x, // 相机x
+      y1: target0.y, // 相机y
+      z1: target0.z, // 相机z
+      x2: oldCameraPosition.x, // 相机x
+      y2: oldCameraPosition.y, // 相机y
+      z2: oldCameraPosition.z, // 相机z
+    },
+    {
+      ...newAnimateObj,
+      duration: 1,
+      ease: 'easeInCirc',
+      onUpdate: function (object) {
+        modal.orbitControls.target.set(object.x1, object.y1, object.z1);
+        modal.camera.position.set(object.x2, object.y2, object.z2);
+        if (renderCallBack) renderCallBack();
+      },
+      onUpdateParams: [animateObj],
+      onComplete: function () {
+        modal.orbitControls.renderEnabled = true;
+        modal.orbitControls.enabled = true;
+      },
+    }
+  );
+
+  console.log('摄像机信息------->', modal.camera, modal.orbitControls);
+};

+ 2 - 2
src/views/sys/login/LoginForm.vue

@@ -63,8 +63,8 @@
   const rememberMe = ref(false);
 
   const formData = reactive({
-    account: '',
-    password: '',
+    account: 'zhuqi',
+    password: 'zhuqi123zhuqi',
     inputCode: '',
   });
   const randCodeData = reactive({

+ 3 - 3
src/views/sys/login/useLogin.ts

@@ -4,8 +4,8 @@ import { ref, computed, unref, Ref } from 'vue';
 import { useI18n } from '/@/hooks/web/useI18n';
 import { checkOnlyUser } from '/@/api/sys/user';
 import { defHttp } from '/@/utils/http/axios';
-import { OAUTH2_THIRD_LOGIN_TENANT_ID } from "/@/enums/cacheEnum";
-import { getAuthCache } from "/@/utils/auth";
+import { OAUTH2_THIRD_LOGIN_TENANT_ID } from '/@/enums/cacheEnum';
+import { getAuthCache } from '/@/utils/auth';
 
 export enum LoginStateEnum {
   LOGIN,
@@ -184,7 +184,7 @@ export function sysOAuth2Login(source) {
   url += `?state=${encodeURIComponent(window.location.origin)}`;
   //update-begin---author:wangshuai ---date:20230224  for:[QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------
   let tenantId = getAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID);
-  if(tenantId){
+  if (tenantId) {
     url += `&tenantId=${tenantId}`;
   }
   //update-end---author:wangshuai ---date:20230224  for:[QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------

+ 86 - 82
src/views/system/loginmini/MiniLogin.vue

@@ -1,6 +1,6 @@
 <template>
   <div :class="prefixCls" class="login-background-img">
-    <AppLocalePicker class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
+    <AppLocalePicker class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false" />
     <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
     <div class="aui-logo" v-if="!getIsMobile">
       <div>
@@ -89,7 +89,8 @@
                 <div class="aui-formButton">
                   <div class="aui-flex">
                     <a-button :loading="loginLoading" class="aui-link-login aui-flex-box" type="primary" @click="loginHandleClick">
-                      {{ t('sys.login.loginButton') }}</a-button>
+                      {{ t('sys.login.loginButton') }}</a-button
+                    >
                   </div>
                   <div class="aui-flex">
                     <a class="aui-linek-code aui-flex-box" @click="codeHandleClick">{{ t('sys.login.qrSignInFormTitle') }}</a>
@@ -163,8 +164,8 @@
   // import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
   import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
   import { useLocaleStore } from '/@/store/modules/locale';
-  import { useDesign } from "/@/hooks/web/useDesign";
-  import { useAppInject } from "/@/hooks/web/useAppInject";
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
   import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
 
   const IconFont = createFromIconfontCN({
@@ -387,7 +388,7 @@
    */
   function handleSuccess(value) {
     Object.assign(formData, value);
-    Object.assign(phoneFormData, { mobile: "", smscode: "" });
+    Object.assign(phoneFormData, { mobile: '', smscode: '' });
     type.value = 'login';
     activeIndex.value = 'accountLogin';
     handleChangeCheckCode();
@@ -448,11 +449,11 @@
     color: #aaa !important;
   }
 
-  :deep(.jeecg-dark-switch){
-    position:absolute;
+  :deep(.jeecg-dark-switch) {
+    position: absolute;
     margin-right: 10px;
   }
-  .aui-link-login{
+  .aui-link-login {
     height: 42px;
     padding: 10px 15px;
     font-size: 14px;
@@ -460,100 +461,103 @@
     margin-top: 15px;
     margin-bottom: 8px;
   }
-  .aui-phone-logo{
+  .aui-phone-logo {
     position: absolute;
     margin-left: 10px;
     width: 60px;
-    top:2px;
+    top: 2px;
     z-index: 4;
   }
-  .top-3{
+  .top-3 {
     top: 0.45rem;
   }
 </style>
 
 <style lang="less">
-@prefix-cls: ~'@{namespace}-mini-login';
-@dark-bg: #293146;
+  @prefix-cls: ~'@{namespace}-mini-login';
+  @dark-bg: #293146;
 
-html[data-theme='dark'] {
-  .@{prefix-cls} {
-    background-color: @dark-bg !important;
-    background-image: none;
+  html[data-theme='dark'] {
+    .@{prefix-cls} {
+      background-color: @dark-bg !important;
+      background-image: none;
 
-    &::before {
-      background-image: url(/@/assets/svg/login-bg-dark.svg);
-    }
-    .aui-inputClear{
-      background-color: #232a3b !important;
-    }
-    .ant-input,
-    .ant-input-password {
-      background-color: #232a3b !important;
-    }
+      &::before {
+        background-image: url(/@/assets/svg/login-bg-dark.svg);
+      }
+      .aui-inputClear {
+        background-color: #232a3b !important;
+      }
+      .ant-input,
+      .ant-input-password {
+        background-color: #232a3b !important;
+      }
 
-    .ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
-      border: 1px solid #4a5569 !important;
-    }
+      .ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
+        border: 1px solid #4a5569 !important;
+      }
 
-    &-form {
-      background: @dark-bg !important;
-    }
+      &-form {
+        background: @dark-bg !important;
+      }
 
-    .app-iconify {
-      color: #fff !important;
-    }
-    .aui-inputClear input,.aui-input-line input,.aui-choice{
-      color: #c9d1d9 !important;
-    }
+      .app-iconify {
+        color: #fff !important;
+      }
+      .aui-inputClear input,
+      .aui-input-line input,
+      .aui-choice {
+        color: #c9d1d9 !important;
+      }
 
-    .aui-formBox{
-      background-color: @dark-bg !important;
-    }
-    .aui-third-text span{
-      background-color: @dark-bg !important;
-    }
-    .aui-form-nav .aui-flex-box{
-      color: #c9d1d9 !important;
-    }
+      .aui-formBox {
+        background-color: @dark-bg !important;
+      }
+      .aui-third-text span {
+        background-color: @dark-bg !important;
+      }
+      .aui-form-nav .aui-flex-box {
+        color: #c9d1d9 !important;
+      }
 
-    .aui-formButton .aui-linek-code{
-      background:  @dark-bg !important;
-      color: white !important;
-    }
-    .aui-code-line{
-      border-left: none !important;
-    }
-    .ant-checkbox-inner,.aui-success h3{
-      border-color: #c9d1d9;
-    }
-    //update-begin---author:wangshuai ---date:20230828  for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
-    &-sign-in-way {
-      .anticon {
-        font-size: 22px !important;
-        color: #888 !important;
-        cursor: pointer !important;
-
-        &:hover {
-          color: @primary-color !important;
+      .aui-formButton .aui-linek-code {
+        background: @dark-bg !important;
+        color: white !important;
+      }
+      .aui-code-line {
+        border-left: none !important;
+      }
+      .ant-checkbox-inner,
+      .aui-success h3 {
+        border-color: #c9d1d9;
+      }
+      //update-begin---author:wangshuai ---date:20230828  for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
+      &-sign-in-way {
+        .anticon {
+          font-size: 22px !important;
+          color: #888 !important;
+          cursor: pointer !important;
+
+          &:hover {
+            color: @primary-color !important;
+          }
         }
       }
+      //update-end---author:wangshuai ---date:20230828  for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
     }
-    //update-end---author:wangshuai ---date:20230828  for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
-  }
 
-  input.fix-auto-fill,
-  .fix-auto-fill input {
-    -webkit-text-fill-color: #c9d1d9 !important;
-    box-shadow: inherit !important;
-  }
-  
-  .ant-divider-inner-text {
-    font-size: 12px !important;
-    color: @text-color-secondary !important;
-  }
-  .aui-third-login a{
-    background: transparent;
+    input.fix-auto-fill,
+    .fix-auto-fill input {
+      -webkit-text-fill-color: #c9d1d9 !important;
+      box-shadow: inherit !important;
+    }
+
+    .ant-divider-inner-text {
+      font-size: 12px !important;
+      color: @text-color-secondary !important;
+    }
+    .aui-third-login a {
+      background: transparent;
+    }
   }
-}
 </style>

+ 77 - 82
src/views/vent/home/configurable/components/gasInject/zqDetailCard.vue

@@ -9,100 +9,95 @@
         </div>
       </div>
     </div>
-
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-
-let props = defineProps({
-  //详情界面配置项
-  optionDetail: {
-    type: Array as any
-  },
-  //数据序列
-  dataIndex: {
-    type: Number
-  },
-  //分组数据
-  zqDetailData: {
-    type: Object as any
-  },
-
-})
+  import { ref } from 'vue';
+
+  let props = defineProps({
+    //详情界面配置项
+    optionDetail: {
+      type: Array as any,
+    },
+    //数据序列
+    dataIndex: {
+      type: Number,
+    },
+    //分组数据
+    zqDetailData: {
+      type: Object as any,
+    },
+  });
 </script>
 
 <style lang="less" scoped>
-@import '/@/design/theme.less';
+  @import '/@/design/theme.less';
 
-@{theme-deepblue} {
-  .detail-card {
-    --image-inject_zq_card: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-5.png');
-    --image-inject_zq_card1: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-7.png');
+  @{theme-deepblue} {
+    .detail-card {
+      --image-inject_zq_card: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-5.png');
+      --image-inject_zq_card1: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-7.png');
+    }
   }
-}
-
-.detail-card {
-  --image-inject_zq_card: url('@/assets/images/gasInjection/9-5.png');
-  --image-inject_zq_card1: url('@/assets/images/gasInjection/9-7.png');
-  width: 100%;
-  height: 100%;
-
-  .card-title {
-    display: flex;
-    align-items: center;
-    width: 100%;
-    height: 52px;
-    margin-bottom: 4px;
-    padding: 0px 20px;
-    font-size: 16px;
-    color: #fff;
-    background: var(--image-inject_zq_card) no-repeat;
-    background-size: 100% 100%;
-  }
-
-  .card-content {
-
-    width: 100%;
-    height: calc(100% - 56px);
-    border-radius: 15px;
-    /* 内阴影 */
-    box-shadow: inset 0px 0px 15px rgba(34, 142, 220, 0.8);
-    padding: 10px 15px;
 
-  }
-
-  .card-content-box {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    align-items: center;
+  .detail-card {
+    --image-inject_zq_card: url('@/assets/images/gasInjection/9-5.png');
+    --image-inject_zq_card1: url('@/assets/images/gasInjection/9-7.png');
     width: 100%;
     height: 100%;
-    overflow-y: auto;
-  }
-
-  .card-content-item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    width: 100%;
-    // height: 26px;
-    margin: 5px 0px;
-    padding: 0px 15px;
-    color: #fff;
-    background: linear-gradient(to right, rgba(17, 50, 86, 1), rgba(17, 50, 86, .1));
-  }
-
-  .content-item-label {
-    color: #c3f5ff;
-  }
-
-  .content-item-value {
 
-    font-family: 'douyuFont';
-    color: #91faff;
+    .card-title {
+      display: flex;
+      align-items: center;
+      width: 100%;
+      height: 52px;
+      margin-bottom: 4px;
+      padding: 0px 20px;
+      font-size: 16px;
+      color: #fff;
+      background: var(--image-inject_zq_card) no-repeat;
+      background-size: 100% 100%;
+    }
+
+    .card-content {
+      width: 100%;
+      height: calc(100% - 56px);
+      border-radius: 15px;
+      /* 内阴影 */
+      box-shadow: inset 0px 0px 15px rgba(34, 142, 220, 0.8);
+      padding: 10px 15px;
+    }
+
+    .card-content-box {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      align-items: center;
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+    }
+
+    .card-content-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      width: 100%;
+      // height: 26px;
+      margin: 5px 0px;
+      padding: 0px 15px;
+      color: #fff;
+      background: linear-gradient(to right, rgba(17, 50, 86, 1), rgba(17, 50, 86, 0.1));
+    }
+
+    .content-item-label {
+      color: #c3f5ff;
+    }
+
+    .content-item-value {
+      font-family: 'douyuFont';
+      color: #91faff;
+    }
   }
-}
 </style>

+ 251 - 249
src/views/vent/home/configurable/components/gasInject/zqMonitorDetail.vue

@@ -54,285 +54,287 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, watch } from 'vue'
-import { SvgIcon } from '/@/components/Icon';
-import zqDetailCard from './zqDetailCard.vue'
-import zqHistoryExport from './zqHistoryExport.vue'
+  import { reactive, ref, watch } from 'vue';
+  import { SvgIcon } from '/@/components/Icon';
+  import zqDetailCard from './zqDetailCard.vue';
+  import zqHistoryExport from './zqHistoryExport.vue';
 
-let props = defineProps({
-  //是否打开历史数据页面
-  isShowExport: {
-    type: Boolean
-  },
-  //table列
-  tableColumn:{
-    type:Array
-  },
-  //详情界面配置项
-  optionDetail:{
-    type:Array
-  },
-  data:{
-    type:Object as any
-  }
-})
-
-let showExport = ref(false)
-//注气详情数据
-let zqDetailData1 = reactive({
-  value1: '10',
-  value2: '10',
-  value3: '10',
-  value4: '10',
-  value5:'10',
-   value6:'10',
-})
-let zqDetailData2 = reactive({
-  value1: '20',
-  value2: '20',
-  value3: '20',
-  value4: '20',
-   value5:'20',
-   value6:'20',
-})
-let zqDetailData3 = reactive({
-  value1: '30',
-  value2: '30',
-  value3: '30',
-  value4: '30',
-   value5:'30',
-   value6:'30',
-})
-let zqDetailData4 = reactive({
-  value1: '40',
-  value2: '40',
-  value3: '40',
-  value4: '40',
-   value5:'40',
-   value6:'40',
-})
+  let props = defineProps({
+    //是否打开历史数据页面
+    isShowExport: {
+      type: Boolean,
+    },
+    //table列
+    tableColumn: {
+      type: Array,
+    },
+    //详情界面配置项
+    optionDetail: {
+      type: Array,
+    },
+    data: {
+      type: Object as any,
+    },
+  });
 
+  let showExport = ref(false);
+  //注气详情数据
+  let zqDetailData1 = reactive({
+    value1: '10',
+    value2: '10',
+    value3: '10',
+    value4: '10',
+    value5: '10',
+    value6: '10',
+  });
+  let zqDetailData2 = reactive({
+    value1: '20',
+    value2: '20',
+    value3: '20',
+    value4: '20',
+    value5: '20',
+    value6: '20',
+  });
+  let zqDetailData3 = reactive({
+    value1: '30',
+    value2: '30',
+    value3: '30',
+    value4: '30',
+    value5: '30',
+    value6: '30',
+  });
+  let zqDetailData4 = reactive({
+    value1: '40',
+    value2: '40',
+    value3: '40',
+    value4: '40',
+    value5: '40',
+    value6: '40',
+  });
 
-//历史数据跳转
-function handleExport() {
-  showExport.value = true
-}
-//历史数据返回至详情
-function handleBack(){
-  showExport.value=false
-}
+  //历史数据跳转
+  function handleExport() {
+    showExport.value = true;
+  }
+  //历史数据返回至详情
+  function handleBack() {
+    showExport.value = false;
+  }
 
-watch(() => props.isShowExport, (newV, oldV) => {
-  showExport.value = newV
-}, { immediate: true })
+  watch(
+    () => props.isShowExport,
+    (newV, oldV) => {
+      showExport.value = newV;
+    },
+    { immediate: true }
+  );
 </script>
 
 <style lang="less" scoped>
-@import '/@/design/theme.less';
-
-@{theme-deepblue} {
-  .gas-inject-card {
-    --image-inject_zq_monitor: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-3.png');
-    --image-inject_zq_monitor1: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-6.png');
+  @import '/@/design/theme.less';
 
+  @{theme-deepblue} {
+    .gas-inject-card {
+      --image-inject_zq_monitor: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-3.png');
+      --image-inject_zq_monitor1: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/9-6.png');
+    }
   }
-}
-
-.zq-monitor {
-  --image-inject_zq_monitor: url('@/assets/images/gasInjection/9-3.png');
-  --image-inject_zq_monitor1: url('@/assets/images/gasInjection/9-6.png');
-  width: 100%;
-  height: 650px;
-  padding: 15px 30px;
-  box-sizing: border-box;
 
-  .nav-box {
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
+  .zq-monitor {
+    --image-inject_zq_monitor: url('@/assets/images/gasInjection/9-3.png');
+    --image-inject_zq_monitor1: url('@/assets/images/gasInjection/9-6.png');
     width: 100%;
-    height: 39px;
-    padding: 0px 20px;
-    background: var(--image-inject_zq_monitor) no-repeat;
-    background-size: 100% 100%;
-  }
+    height: 650px;
+    padding: 15px 30px;
+    box-sizing: border-box;
 
-  .nav-btn {
-    width: 125px;
-    height: 78%;
-    padding: 2px;
-    border: 1px solid #1081d7;
-    cursor: pointer;
-    color: #fff;
-  }
+    .nav-box {
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+      width: 100%;
+      height: 39px;
+      padding: 0px 20px;
+      background: var(--image-inject_zq_monitor) no-repeat;
+      background-size: 100% 100%;
+    }
 
-  .nav-text {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    background-color: #1081d7;
-  }
+    .nav-btn {
+      width: 125px;
+      height: 78%;
+      padding: 2px;
+      border: 1px solid #1081d7;
+      cursor: pointer;
+      color: #fff;
+    }
 
-  .icon-style {
-    margin-right: 5px;
-  }
+    .nav-text {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background-color: #1081d7;
+    }
 
-  .main-content {
-    display: flex;
-    justify-content: space-between;
-    width: 100%;
-    height: calc(100% - 39px);
-    padding: 30px 0px 20px 0px;
-  }
+    .icon-style {
+      margin-right: 5px;
+    }
 
-  .common-area {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    width: 320px;
-    height: 100%;
-  }
+    .main-content {
+      display: flex;
+      justify-content: space-between;
+      width: 100%;
+      height: calc(100% - 39px);
+      padding: 30px 0px 20px 0px;
+    }
 
-  .area-item {
-    width: 100%;
-    height: calc(50% - 15px);
-  }
+    .common-area {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      width: 320px;
+      height: 100%;
+    }
 
-  .center-area {
-    position: relative;
-    width: calc(100% - 640px);
-    height: 100%;
-  }
+    .area-item {
+      width: 100%;
+      height: calc(50% - 15px);
+    }
 
-  .center-content {
-    position: absolute;
-    left: 50%;
-    top: 50%;
-    transform: translate(-50%, -50%);
-    width: 500px;
-    height: 315px;
-    border-radius: 20px;
-    box-shadow: inset 0px 0px 20px rgba(34, 142, 220, 0.8);
-  }
+    .center-area {
+      position: relative;
+      width: calc(100% - 640px);
+      height: 100%;
+    }
 
-  .line-L {
-    display: flex;
-    position: absolute;
-    left: 0;
-    top: 24px;
-    width: 90px;
-    height: 282px;
-  }
+    .center-content {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      height: 315px;
+      border-radius: 20px;
+      box-shadow: inset 0px 0px 20px rgba(34, 142, 220, 0.8);
+    }
 
-  .line-R {
-    display: flex;
-    position: absolute;
-    right: 0;
-    top: 24px;
-    width: 90px;
-    height: 282px;
-  }
+    .line-L {
+      display: flex;
+      position: absolute;
+      left: 0;
+      top: 24px;
+      width: 90px;
+      height: 282px;
+    }
 
-  .line-L-1 {
-    width: 50%;
-    height: 100%;
-    border-top: 2px solid #277297;
-    border-right: 2px solid #277297;
-    border-bottom: 2px solid #277297;
-  }
+    .line-R {
+      display: flex;
+      position: absolute;
+      right: 0;
+      top: 24px;
+      width: 90px;
+      height: 282px;
+    }
 
-  .line-R-1 {
-    position: absolute;
-    left: 50%;
-    top: 0;
-    width: 50%;
-    height: 100%;
-    border-top: 2px solid #277297;
-    border-left: 2px solid #277297;
-    border-bottom: 2px solid #277297;
-  }
+    .line-L-1 {
+      width: 50%;
+      height: 100%;
+      border-top: 2px solid #277297;
+      border-right: 2px solid #277297;
+      border-bottom: 2px solid #277297;
+    }
 
-  .t-icon {
-    position: absolute;
-    left: 0;
-    top: -8px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-  }
+    .line-R-1 {
+      position: absolute;
+      left: 50%;
+      top: 0;
+      width: 50%;
+      height: 100%;
+      border-top: 2px solid #277297;
+      border-left: 2px solid #277297;
+      border-bottom: 2px solid #277297;
+    }
 
-  .t-icon-r {
-    position: absolute;
-    right: 0;
-    top: -10px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-    transform: rotate(180deg);
-  }
+    .t-icon {
+      position: absolute;
+      left: 0;
+      top: -8px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+    }
 
-  .t-icon1 {
-    position: absolute;
-    left: 0;
-    bottom: -8px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-  }
+    .t-icon-r {
+      position: absolute;
+      right: 0;
+      top: -10px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+      transform: rotate(180deg);
+    }
 
-  .t-icon-r1 {
-    position: absolute;
-    right: 0;
-    bottom: -10px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-    transform: rotate(180deg);
-  }
+    .t-icon1 {
+      position: absolute;
+      left: 0;
+      bottom: -8px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+    }
 
-  .line-L-2 {
-    position: absolute;
-    width: 50%;
-    height: 2px;
-    left: 50%;
-    top: 190px;
-    background-color: #277297;
-  }
+    .t-icon-r1 {
+      position: absolute;
+      right: 0;
+      bottom: -10px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+      transform: rotate(180deg);
+    }
 
-  .line-R-2 {
-    position: absolute;
-    width: 50%;
-    height: 2px;
-    left: 0;
-    top: 190px;
-    background-color: #277297;
-  }
+    .line-L-2 {
+      position: absolute;
+      width: 50%;
+      height: 2px;
+      left: 50%;
+      top: 190px;
+      background-color: #277297;
+    }
 
-  .t-icon2 {
-    position: absolute;
-    right: 5px;
-    top: 182px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-  }
+    .line-R-2 {
+      position: absolute;
+      width: 50%;
+      height: 2px;
+      left: 0;
+      top: 190px;
+      background-color: #277297;
+    }
+
+    .t-icon2 {
+      position: absolute;
+      right: 5px;
+      top: 182px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+    }
 
-  .t-icon2-r {
-    position: absolute;
-    left: 5px;
-    top: 182px;
-    width: 30px;
-    height: 18px;
-    background: var(--image-inject_zq_monitor1) no-repeat;
-    background-size: 100% 100%;
-    transform: rotate(180deg);
+    .t-icon2-r {
+      position: absolute;
+      left: 5px;
+      top: 182px;
+      width: 30px;
+      height: 18px;
+      background: var(--image-inject_zq_monitor1) no-repeat;
+      background-size: 100% 100%;
+      transform: rotate(180deg);
+    }
   }
-}
 </style>

+ 7 - 8
src/views/vent/home/configurable/gasInjection.vue

@@ -35,12 +35,11 @@
         />
       </template>
       <div :class="{ 'vent-modal': menuName == 'zjm', 'vent-modal-1': menuName == 'syq' || menuName == 'zqxt' || menuName == 'ccxt' }">
-        <div class="modal-box">
+        <div class="modal-box" id="modalBox">
           <Three3D ref="three3D" :modalName="'zhuqi'" class="modal-3d" @success="initModalAnimate" />
+          <div class="modal-css3d" id="css3dContainer"> </div>
         </div>
       </div>
-      <!-- <div v-if="menuName === 'syq'" class="syq-modal">
-        </div> -->
     </div>
   </div>
 </template>
@@ -50,7 +49,7 @@
   import ModuleGasInject from './components/ModuleGasInject.vue';
   import navMenu from './components/gasInject/navMenu.vue';
   import Three3D from './components/three3D.vue';
-  import { getSystemApi } from './configurable.api';
+  import { getHomeData, getSystemApi } from './configurable.api';
   // import { useRoute } from 'vue-router';
   import { testConfigGasInject, testConfigGasInjectZq, testConfigGasInjectCc, testConfigGasInjectSy } from './configurable.data';
   import { animateCamera } from '/@/utils/threejs/util';
@@ -91,8 +90,8 @@
         const oldCamera = modal.camera;
         const oldCameraPosition = { x: oldCamera.position.x, y: oldCamera.position.y, z: oldCamera.position.z };
         if (param == 'zqxt') {
-          const newP = { x: -29.4124614570094, y: -1.6093280622283133, z: 39.51639267244655 };
-          const newT = { x: 11.205344322178817, y: -1.7174801102124406, z: 40.19437751610255 };
+          const newP = { x: -30.683057066775824, y: -0.9161184591449614, z: 39.6716716890212 };
+          const newT = { x: 11.204235206978213, y: -1.0276506907362775, z: 40.37084673927868 };
           await animateCamera(oldCameraPosition, { x: 0, y: 0, z: 0 }, newP, newT, modal);
         } else if (param == 'zjm') {
           const newP = { x: -45.69228602978097, y: 49.59721939545517, z: 2.6454258202266985 };
@@ -118,11 +117,11 @@
     fetchConfigs('gas_injection').then(() => {
       configs.value = testConfigGasInject;
       //  updateEnhancedConfigs(configs.value);
-      getSystemApi({devicetype:'sys', systemID: '2036323791827165185' }).then(updateData);
+      getSystemApi({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
     });
 
     setInterval(() => {
-      getSystemApi({devicetype:'sys', systemID: '2036323791827165185' }).then(updateData);
+      getSystemApi({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
     }, 60000);
   });
 

+ 435 - 107
src/views/vent/home/configurable/threejs/gasInjection.ts

@@ -5,24 +5,22 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
 import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
 import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
 import { PathPointList, PathGeometry } from 'three.path';
-
+import { modelMouseHandler } from '/@/utils/threejs/useEvent';
+import { panelManager } from './PanelManager';
+import gsap from 'gsap';
+import { e } from 'unocss';
+
+const cgqMarkerList = [];
+const lljMarkerList = [];
+const pipList = [];
+let mouseoverEvent: any;
+let mouseUpEvent: any;
+let clickSelecteObject: any;
 export function modalAnimate(modal) {
-  modal.renderer.toneMappingExposure = 1.2;
-
-  console.log('初始化模型', modal);
-  modal.isRender = true;
+  const curveNum = 8;
+  const unitNum = 8;
 
-  // 【关键步骤】设置旋转速度
-  // 默认是 1.0,设置为 0.5 会让移动幅度减半(更慢/更精细)
-  modal.orbitControls.rotateSpeed = 0.5;
-
-  // 可选:如果你也想减慢缩放和平移的速度
-  modal.orbitControls.zoomSpeed = 0.5; // 缩放速度
-  modal.orbitControls.panSpeed = 0.5; // 平移速度
-
-  // 启用阻尼效果(可选,让运动更有惯性,感觉更平滑)
-  modal.orbitControls.enableDamping = true;
-  modal.orbitControls.dampingFactor = 0.9;
+  resetModalParam(modal);
 
   const bounds = {
     min: new THREE.Vector3(-25, -2, -35),
@@ -37,12 +35,19 @@ export function modalAnimate(modal) {
   touming3.parent.remove(touming3);
 
   const time = Date.now() * 0.001;
-  const clock = new THREE.Clock();
 
-  const bloomObj = addZhuqiAnimate(zhuqiModal, modal);
-  const arrowObj = addArrow(zhuqiModal);
-  const unitObj = addUnit(zhuqiModal);
+  const arrowObj = addArrow(zhuqiModal, curveNum);
+  const unitObj = addUnit(zhuqiModal, unitNum);
+  const pipObj = addPip(zhuqiModal, curveNum);
+  console.log('模型对象', zhuqiModal);
+
+  initCss2DContainer(modal);
+  // 添加鼠标拾取
+  initMouseEvent(modal, zhuqiModal);
+
+  initPanel(modal, zhuqiModal);
 
+  const clock = new THREE.Clock();
   // 帧刷新
   modal.startAnimation = () => {
     /**气泡运动 - 遍历所有颜色组*/
@@ -83,19 +88,6 @@ export function modalAnimate(modal) {
       instancedMesh.instanceMatrix.needsUpdate = true;
     });
 
-    /**自发光*/
-    if (bloomObj) {
-      // const { zhuqiPips, bloomComposer, finalComposer, materials, darkMaterial, shader, bloomLayer } = bloomObj;
-      // modal.scene.traverse((item) => {
-      //   darkenNonBloomed(item, bloomLayer, materials, darkMaterial);
-      // });
-      // bloomComposer.render();
-      // modal.scene.traverse((item) => {
-      //   restoreMaterial(item, materials);
-      // });
-      // finalComposer.render();
-    }
-
     /**箭头流动效果*/
     if (arrowObj) {
       const { texture } = arrowObj;
@@ -107,9 +99,158 @@ export function modalAnimate(modal) {
       const { arrowTexture } = unitObj;
       arrowTexture.offset.x -= 0.0576;
     }
+
+    if (pipObj) {
+      const { texture } = pipObj;
+      texture.offset.x -= 0.1076;
+    }
+
+    [...cgqMarkerList, ...lljMarkerList].forEach((marker: THREE.Mesh) => {
+      // 更新水波纹动画
+      const delta = clock.getElapsedTime();
+      if (marker.userData.update) {
+        marker.userData.update(delta);
+      }
+    });
   };
 }
 
+function initPanel(modal, zhuqiModal) {
+  const changjingpPipe38 = zhuqiModal.getObjectByName('guandao')?.getObjectByName('changjingpPipe38');
+  if (changjingpPipe38) {
+    const option = {
+      instanceId: changjingpPipe38.name,
+      sensorData: {
+        positionName: '总支管#',
+        temperature: 65,
+        smoke: 80,
+        co: 45,
+      },
+      threshold: { temperature: 20 },
+      position: [],
+      scale: 0.006,
+    };
+    createMonitorPanel3D(modal, changjingpPipe38, option);
+    const position = panelManager.getPanel(changjingpPipe38.name)?.vm.cssObject.position.clone();
+    panelManager.getPanel(changjingpPipe38.name)?.vm.cssObject.position.set(position.x, position.y + 1, position.z + 3.5);
+  }
+}
+
+function initMouseEvent(modal, zhuqiModal) {
+  const zhuqiModalGroup = zhuqiModal.getObjectByName('zhuqiModalGroup');
+  mouseoverEvent = (event) => {
+    modelMouseHandler(modal, pipList, event, (intersects) => {
+      partEvent(intersects, pipList, modal, event);
+    });
+  };
+  mouseUpEvent = (event) => {
+    modelMouseHandler(modal, pipList, event, (intersects) => {
+      partEvent(intersects, pipList, modal, event);
+    });
+  };
+  modal.canvasContainer?.addEventListener('mousemove', mouseoverEvent);
+  modal.canvasContainer?.addEventListener('mouseup', mouseUpEvent);
+}
+
+function partEvent(intersects, partitionList, modal, event) {
+  partitionList.forEach((partition) => {
+    if (!clickSelecteObject || (clickSelecteObject && partition.name !== clickSelecteObject.name)) {
+      partition.visible = false;
+    }
+  });
+  if (intersects && intersects.length > 0) {
+    const partitionObj = intersects.find((item) => {
+      if (item.object.type === 'Line') {
+        const partition = partitionList.find((partition) => partition.name === item.object.name);
+        if (partition) {
+          partition.visible = true;
+        }
+        return true;
+      }
+      return false;
+    });
+    // 如果是点击事件
+    if (event.type === 'mouseup' && event.button == 0) {
+      if (clickSelecteObject) {
+        panelManager.destroyPanel(clickSelecteObject.name);
+        clickSelecteObject = null;
+      }
+      if (partitionObj) {
+        clickSelecteObject = partitionObj.object;
+        const index = clickSelecteObject.name.split('pipLine')[1];
+        const name = parseInt(index) + 1 + '#';
+        const option = {
+          instanceId: clickSelecteObject.name,
+          sensorData: {
+            positionName: name,
+            temperature: 65,
+            smoke: 80,
+            co: 45,
+          },
+          threshold: { temperature: 20 },
+          position: [],
+          scale: 0.004,
+        };
+        createMonitorPanel3D(modal, clickSelecteObject, option);
+      }
+    }
+
+    return partitionObj;
+  } else {
+    if (event.type === 'mouseup' && event.button == 0) {
+      if (clickSelecteObject) {
+        panelManager.destroyPanel(clickSelecteObject.name);
+        clickSelecteObject = null;
+      }
+    }
+  }
+}
+
+function createMonitorPanel3D(modal, partition, options?) {
+  const box = new THREE.Box3();
+  box.setFromObject(partition);
+  const center = box.getCenter(new THREE.Vector3());
+  options.position = [center.x, center.y, center.z];
+
+  panelManager.createPanel(modal.scene, options);
+
+  // // 点击后相机
+  // const target0 = modal.camera.position.clone();
+  // const target1 = modal.orbitControls.target.clone();
+  // gsap.fromTo(
+  //   modal.orbitControls.target,
+  //   { x: target1.x, y: target1.y, z: target1.z },
+  //   {
+  //     x: center.x - 4,
+  //     y: center.y + 1,
+  //     z: center.z,
+  //     duration: 0.4,
+  //     ease: 'easeInCirc',
+  //     onUpdate: function (object) {
+  //       if (object) modal.camera?.lookAt(new THREE.Vector3(object.x, object.y, object.z));
+  //     },
+  //   }
+  // );
+}
+
+function resetModalParam(modal) {
+  modal.renderer.toneMappingExposure = 1.35;
+
+  console.log('初始化模型', modal);
+  modal.isRender = true;
+
+  // 【关键步骤】设置旋转速度
+  // 默认是 1.0,设置为 0.5 会让移动幅度减半(更慢/更精细)
+  modal.orbitControls.rotateSpeed = 0.3;
+  // 可选:如果你也想减慢缩放和平移的速度
+  modal.orbitControls.zoomSpeed = 0.3; // 缩放速度
+  modal.orbitControls.panSpeed = 0.3; // 平移速度
+
+  // 启用阻尼效果(可选,让运动更有惯性,感觉更平滑)
+  modal.orbitControls.enableDamping = true;
+  modal.orbitControls.dampingFactor = 0.9;
+}
+
 function initGasAnimate(modal, colorConfigs: any[] = []) {
   // 默认配置:如果没有传入颜色配置,使用默认的红蓝两种
   if (colorConfigs.length === 0) {
@@ -118,14 +259,14 @@ function initGasAnimate(modal, colorConfigs: any[] = []) {
         name: 'gas',
         color: 0xffa2a244,
         emissive: 0xff4400,
-        bubbleCount: 200,
+        bubbleCount: 100,
         hslHue: 0.4, // 红色
       },
       {
         name: 'n2',
         color: 0x4fedf788,
         emissive: 0x0044aa,
-        bubbleCount: 500,
+        bubbleCount: 300,
         hslHue: 0.5, // 蓝色
       },
     ];
@@ -303,75 +444,6 @@ function addZhuqiAnimate(zhuqiModal, modal) {
   return null;
 }
 
-function addArrow1(zhuqiModal) {
-  const changjingzhuqiguan = zhuqiModal.getObjectByName('changjing5');
-  if (changjingzhuqiguan) {
-    const zhuqiPips = changjingzhuqiguan.children.filter((item) => {
-      return item.name.includes('changjingcurve10');
-    });
-
-    const flowUniforms = {
-      uTime: { value: 0 },
-      uColor: { value: new THREE.Color(0x00aaff) }, // 流体颜色
-      uSpeed: { value: 0.5 }, // 流动速度
-      uBaseColor: { value: new THREE.Color(0x222222) }, // 管道底色
-    };
-
-    const flowMaterial = new THREE.ShaderMaterial({
-      uniforms: flowUniforms,
-      vertexShader: `
-        varying vec2 vUv;
-        void main() {
-            vUv = uv;
-            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
-        }
-    `,
-      fragmentShader: `
-        uniform float uTime;
-        uniform vec3 uColor;
-        uniform float uSpeed;
-        uniform vec3 uBaseColor;
-        varying vec2 vUv;
-
-        void main() {
-            // 核心:沿 V 轴 (长度方向) 移动纹理
-            // fract 实现无限循环
-            float flow = fract(vUv.y + uTime * uSpeed);
-            
-            // 简单的条纹效果
-            float stripe = step(0.8, fract(flow * 5.0)); 
-            
-            // 混合颜色:底色 + 流动的高亮色
-            vec3 finalColor = mix(uBaseColor, uColor, stripe * 0.8);
-            
-            // 增加一点菲涅尔效果让它看起来更立体 (可选)
-            gl_FragColor = vec4(finalColor, 1.0);
-        }
-    `,
-      side: THREE.DoubleSide,
-    });
-
-    const material1 = new THREE.MeshStandardMaterial({
-      color: '#f00',
-      roughness: 1,
-      metalness: 1,
-    });
-    debugger;
-    zhuqiPips.forEach((item) => {
-      // const oldGeometry = item.geometry.clone();
-      // // const geometry = new THREE.TubeGeometry(oldGeometry.parameters.path, oldGeometry.parameters.tubularSegments, 0.05, 8, false);
-      // const newMesh = new THREE.Mesh(oldGeometry, flowMaterial.clone());
-      // item.parent.add(newMesh);
-      // // item.parent.remove(item);
-      // item.visible = false;
-      // // material.needsUpdate = true;
-      // // item.material = material;
-    });
-    return { zhuqiPips, flowMaterial };
-  }
-  return null;
-}
-
 function addArrow(zhuqiModal, curveNum = 8) {
   const changjing5 = zhuqiModal.getObjectByName('changjing5');
   if (changjing5) {
@@ -394,7 +466,7 @@ function addArrow(zhuqiModal, curveNum = 8) {
     // 绘制钻孔曲线
     // 根据份数计算均线位置, max.x - min.x 为总长, 端点坐标
     // 纵长
-    const num1 = 9; // 钻孔个数+1
+    const num1 = curveNum + 1; // 钻孔个数+1
     const gap = (max.x - (min.x + 200)) / num1;
     const curvePoints: THREE.Vector3[] = [];
     for (let i = 0; i < num1; i++) {
@@ -451,7 +523,7 @@ function addArrow(zhuqiModal, curveNum = 8) {
     }
 
     const textureLoader = new THREE.TextureLoader();
-    const texture = textureLoader.load(`/public/texture/2-1.png`);
+    const texture = textureLoader.load(`/texture/2-1.png`);
 
     // 设置阵列模式 RepeatWrapping
     texture.wrapS = THREE.RepeatWrapping;
@@ -486,6 +558,7 @@ function addArrow(zhuqiModal, curveNum = 8) {
 
       const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(vec3List), 100, 40, 8, false);
       const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
+      mesh.name = 'zk' + i;
       pCube53.add(mesh1);
       mesh1.renderOrder = 1;
     }
@@ -530,7 +603,7 @@ function addUnit(zhuqiModal, unitNum = 8) {
     // }
 
     const textureLoader = new THREE.TextureLoader();
-    const texture = textureLoader.load(`/public/texture/3-2.png`);
+    const texture = textureLoader.load(`/texture/3-2.png`);
 
     // 设置阵列模式 RepeatWrapping
     texture.wrapS = THREE.RepeatWrapping;
@@ -632,6 +705,159 @@ function addUnit(zhuqiModal, unitNum = 8) {
   return null;
 }
 
+function addPip(zhuqiModal, curveNum = 8) {
+  const zhuqiguandao = zhuqiModal.getObjectByName('zhuqiguandao');
+  const changjingpPipe37 = zhuqiModal.getObjectByName('guandao').getObjectByName('changjingpPipe37');
+  const box = new THREE.Box3();
+  box.setFromObject(changjingpPipe37);
+  const min = box.min;
+  const max = box.max;
+  const endP = new THREE.Vector3(max.x - 30, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2);
+  const startP = new THREE.Vector3(min.x + 30, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2);
+
+  const endP0 = new THREE.Vector3(max.x, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2);
+  const startP0 = new THREE.Vector3(min.x, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2);
+
+  const len = endP.x - startP.x;
+  const gap = len / curveNum;
+
+  const zhuqiguandaoList: THREE.Object3D[] = [];
+
+  for (let i = 0; i < curveNum; i++) {
+    const position = new THREE.Vector3(0 + gap * i, 0, 0);
+    const zhuqiguandaoClone = zhuqiguandao.clone(); // 创建克隆
+    zhuqiguandaoClone.name = 'zhuqiguandao' + i;
+    zhuqiguandaoClone.position.copy(position);
+
+    // 添加点击标识
+    zhuqiModal.add(zhuqiguandaoClone);
+    // addMarker(zhuqiguandaoClone);
+    zhuqiguandaoList.push(zhuqiguandaoClone);
+  }
+  zhuqiguandao.visible = false;
+
+  const { texture } = pipAnimation(zhuqiguandaoList, startP0, endP0, zhuqiModal);
+
+  return { texture };
+}
+
+function pipAnimation(zhuqiguandaoList, startP, endP, zhuqiModal) {
+  const zhuqiModalGroup = new THREE.Object3D();
+  zhuqiModalGroup.name = 'zhuqiModalGroup';
+
+  const textureLoader = new THREE.TextureLoader();
+  const texture = textureLoader.load(`/texture/2-1.png`);
+
+  // 设置阵列模式 RepeatWrapping
+  texture.wrapS = THREE.RepeatWrapping;
+  texture.wrapT = THREE.RepeatWrapping;
+  // 设置x方向的重复数(沿着管道路径方向)
+  // 设置y方向的重复数(环绕管道方向)
+  texture.repeat.x = 20;
+  texture.repeat.y = 6;
+  // 设置管道纹理偏移数,便于对中
+  texture.offset.y = 100;
+  const tubeMaterial = new THREE.MeshPhongMaterial({
+    map: texture,
+    color: '#fff',
+    transparent: true,
+    opacity: 1.0,
+  });
+  const material = new THREE.MeshStandardMaterial({
+    color: '#4F4F4F',
+    roughness: 0.31,
+    metalness: 0.74,
+  });
+
+  const zhuqi_dian = zhuqiModal.getObjectByName('zhuqi_dian');
+  const dian4 = zhuqi_dian.getObjectByName('dian4');
+  const dian3 = zhuqi_dian.getObjectByName('dian3');
+
+  const center = new THREE.Vector3();
+  const box4 = new THREE.Box3();
+  box4.setFromObject(dian4);
+  box4.getCenter(center);
+
+  const pipSP = new THREE.Vector3();
+  const box3 = new THREE.Box3();
+  box3.setFromObject(dian3);
+  box3.getCenter(pipSP);
+
+  // 构建三段管道
+  const pipGeometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([pipSP, center], false, 'centripetal'), 30, 8, 10, false);
+  const pipGeometry2 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([center, startP], false, 'centripetal'), 30, 8, 10, false);
+  const pipGeometry3 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([center, endP], false, 'centripetal'), 30, 8, 10, false);
+
+  const pip1 = new THREE.Mesh(pipGeometry1, tubeMaterial);
+  const pip2 = new THREE.Mesh(pipGeometry2, tubeMaterial);
+  const pip3 = new THREE.Mesh(pipGeometry3, tubeMaterial);
+
+  zhuqiModal.add(pip1, pip2, pip3);
+
+  try {
+    for (let i = 0; i < zhuqiguandaoList.length; i++) {
+      const zhuqiguandao = zhuqiguandaoList[i];
+      const zhuqi_dian_1 = zhuqiguandao.getObjectByName('zhuqi_dian_1');
+
+      const dian1 = zhuqi_dian_1?.getObjectByName('dian2') as THREE.Object3D;
+      const dian2 = zhuqi_dian_1?.getObjectByName('dian1') as THREE.Object3D;
+      const dian3 = zhuqi_dian_1?.getObjectByName('dian') as THREE.Object3D;
+
+      const position1 = new THREE.Vector3();
+      const position2 = new THREE.Vector3();
+      const position3 = new THREE.Vector3();
+
+      const box1 = new THREE.Box3();
+      box1.setFromObject(dian1);
+      box1.getCenter(position1);
+
+      const box2 = new THREE.Box3();
+      box2.setFromObject(dian2);
+      box2.getCenter(position2);
+
+      const box3 = new THREE.Box3();
+      box3.setFromObject(dian3);
+      box3.getCenter(position3);
+
+      const position21 = position2.clone().setY(position2.y - 5);
+      const position22 = position2.clone().setZ(position2.z + 5);
+      const curve = new THREE.QuadraticBezierCurve3(position22, position2, position21);
+      const points = curve.getPoints(30);
+
+      const geometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([position1, ...points, position3], false, 'chordal'), 20, 6, 10, false);
+      const mesh = new THREE.Mesh(geometry, tubeMaterial);
+      mesh.position.copy(zhuqiguandao.position);
+      mesh.name = 'pip' + i;
+      zhuqiModal.add(mesh);
+
+      // 绘制点击区域
+      // 鼠标经过时显示线框
+      const material = new THREE.MeshBasicMaterial({
+        color: '#f00',
+        transparent: true,
+        opacity: 1,
+      });
+      const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([position1, ...points, position3], false, 'chordal'), 20, 6, 10, false);
+      // const mesh1 = new THREE.Mesh(geometry1, material);
+      const mesh1 = new THREE.Line(geometry1, material);
+      mesh1.position.copy(zhuqiguandao.position);
+      mesh1.name = 'pipLine' + i;
+      // mesh1.visible = false;
+      zhuqiModal.add(mesh1);
+      pipList.push(mesh1);
+    }
+  } catch (error) {
+    debugger;
+  }
+  // zhuqiModal.add(zhuqiModalGroup);
+
+  return { texture };
+}
+
+function initCss2DContainer(modal) {
+  modal.initCSS3Renderer('#css3dContainer');
+}
+
 /**
  * 计算满足不等式 gap*2 > (len - gap*2)/num 的最小临界值
  * @param {number} len - 总长度
@@ -649,3 +875,105 @@ function getMinGapThreshold(len, num) {
   // 公式推导结果: gap > len / (2 * (num + 1))
   return len / (2 * (num + 1));
 }
+
+function createRippleMarker() {
+  // 主容器(方便整体控制)
+  const marker = new THREE.Group();
+
+  // --------------------------
+  // 配置参数(可自由修改)
+  // --------------------------
+  const config = {
+    radius: 10, // 标注大小
+    baseColor: 0x00aaff, // 底色
+    rippleColor: 0xffffff, // 波纹颜色
+    rippleCount: 3, // 波纹层数
+    animateSpeed: 1.5, // 动画速度
+    opacity: 0.8, // 整体透明度
+  };
+
+  // --------------------------
+  // 1)底层静态圆形
+  // --------------------------
+  const baseGeometry = new THREE.CircleGeometry(config.radius, 32);
+  const baseMaterial = new THREE.MeshBasicMaterial({
+    color: config.baseColor,
+    transparent: true,
+    opacity: config.opacity * 0.3,
+  });
+  const baseCircle = new THREE.Mesh(baseGeometry, baseMaterial);
+  marker.add(baseCircle);
+
+  // --------------------------
+  // 2)动态波纹(多层)
+  // --------------------------
+  const ripples = [];
+  for (let i = 0; i < config.rippleCount; i++) {
+    const geo = new THREE.RingGeometry(
+      0, // 内半径
+      config.radius, // 外半径
+      config.radius * 2
+    );
+    const mat = new THREE.MeshBasicMaterial({
+      color: config.rippleColor,
+      transparent: true,
+      opacity: 0,
+      side: THREE.DoubleSide,
+      depthWrite: false, // 防止透明遮挡
+    });
+    const ripple = new THREE.Mesh(geo, mat);
+    ripple.userData.delay = i * 0.4; // 错开动画
+    ripple.userData.speed = config.animateSpeed;
+    ripples.push(ripple);
+    marker.add(ripple);
+  }
+
+  // --------------------------
+  // 3)动画更新函数
+  // --------------------------
+  marker.userData.update = (delta) => {
+    ripples.forEach((rip: THREE.Mesh) => {
+      let t = (delta * rip.userData.speed - rip.userData.delay) % 3;
+      if (t < 0) t += 3;
+
+      // 波纹从中心向外扩散
+      const progress = Math.min(t / 2, 1);
+      const radius = progress * config.radius;
+
+      // 更新环形大小
+      rip.geometry.dispose();
+      rip.geometry = new THREE.RingGeometry(radius * 0.8, radius, 32);
+
+      // 透明度:出现 → 最亮 → 消失
+      rip.material.opacity = progress < 0.5 ? progress * 2 : (1 - progress) * 0.8;
+    });
+  };
+
+  // marker.userData.lookAtCamera = true;
+  return marker;
+}
+
+function addMarker(zhuqiguandao: THREE.Object3D) {
+  const posObj1 = zhuqiguandao?.getObjectByName('网格004_6');
+  const box1 = new THREE.Box3();
+  box1.setFromObject(posObj1);
+  const center1 = box1.getCenter(new THREE.Vector3());
+  const marker_cgq = createRippleMarker();
+  const position = new THREE.Vector3(center1.x, center1.y, center1.z);
+  marker_cgq.position.copy(position.setZ(position.z - 10));
+  marker_cgq.name = 'cgq';
+  marker_cgq.rotateY(Math.PI / 2);
+  zhuqiguandao.add(marker_cgq);
+  cgqMarkerList.push(marker_cgq);
+
+  const posObj2 = zhuqiguandao?.getObjectByName('网格004_2');
+  const box2 = new THREE.Box3();
+  box2.setFromObject(posObj2);
+  const center2 = box2.getCenter(new THREE.Vector3());
+  const marker_llj = createRippleMarker();
+  const pos = new THREE.Vector3(center2.x, center2.y, center2.z);
+  marker_llj.position.copy(pos);
+  marker_llj.name = 'llj';
+  zhuqiguandao.add(marker_llj);
+  lljMarkerList.push(marker_llj);
+}