Ver código fonte

Merge branch 'master' of http://39.97.59.228:8013/hrx/mky-vent-base

lxh 1 semana atrás
pai
commit
a8924ce64f
28 arquivos alterados com 6289 adições e 1399 exclusões
  1. 7 2
      package.json
  2. 1 0
      public/js/config.js
  3. BIN
      src/assets/images/vent/login/captcha.png
  4. 1 0
      src/enums/pageEnum.ts
  5. 4 2
      src/router/guard/permissionGuard.ts
  6. 17 0
      src/router/routes/index.ts
  7. 279 0
      src/views/sys/login/LoginFormInputCode.vue
  8. 304 0
      src/views/sys/login/LoginInputCode.vue
  9. 8 5
      src/views/system/user/user.data.ts
  10. 65 107
      src/views/vent/deviceManager/comment/warningTabel/BaseModal1Leather.vue
  11. 2 2
      src/views/vent/deviceManager/comment/warningTabel/indexLeather.vue
  12. 378 378
      src/views/vent/home/configurable/belt/belt-new.vue
  13. 117 117
      src/views/vent/home/configurable/belt/belt.vue
  14. 26 19
      src/views/vent/home/configurable/belt/components/detail/fireGateBoard.vue
  15. 12 4
      src/views/vent/home/configurable/belt/configurable.api.ts
  16. 190 102
      src/views/vent/home/configurable/belt/history.vue
  17. 25 18
      src/views/vent/home/configurable/belt/threejs/belt.threejs.ts
  18. 41 2
      src/views/vent/home/configurable/components/belt/ComplexList1Belt.vue
  19. 20 21
      src/views/vent/home/configurable/fireDoorMonitor.vue
  20. 2 1
      src/views/vent/home/configurable/hooks/helper.ts
  21. 639 569
      src/views/vent/monitorManager/comment/HistoryTable.vue
  22. 3749 0
      src/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue
  23. 30 1
      src/views/vent/monitorManager/fireDoorMonitor/fireDoor.threejs.ts
  24. 248 0
      src/views/vent/monitorManager/fireDoorMonitor/fireDoor.threejs.ymh.ts
  25. 12 0
      src/views/vent/monitorManager/mainFanMonitor/index.vue
  26. 5 0
      src/views/vent/monitorManager/mainFanMonitor/main.data.ts
  27. 67 9
      src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts
  28. 40 40
      src/views/vent/monitorManager/mainFanMonitor/mainWind.threejs.ts

+ 7 - 2
package.json

@@ -1,7 +1,7 @@
 {
   "name": "jeecgboot-vue3",
   "version": "6.0.0",
-  "projectVersion": "6.0.0.1",
+  "projectVersion": "6.0.1.0",
   "author": {
     "name": "jeecg",
     "email": "jeecgos@163.com",
@@ -217,5 +217,10 @@
         "three/examples/jsm/postprocessing/UnrealBloomPass.js"
       ]
     }
-  }
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ]
 }

+ 1 - 0
public/js/config.js

@@ -21,4 +21,5 @@ const VENT_PARAM = {
   historyIsMultiple: false, // 设备历史数据是否支持多选
   isShowQy: true, // 是否显示气压
   is2DModel: false, // 是否尽最大可能使用2D模型
+  isLoginByCode: false, // 登录页面是否有验证码
 }

BIN
src/assets/images/vent/login/captcha.png


+ 1 - 0
src/enums/pageEnum.ts

@@ -1,6 +1,7 @@
 export enum PageEnum {
   // basic login path
   BASE_LOGIN = '/login',
+  BASE_LOGIN_CODE = '/loginCode',
 
   // 暂时修改
   // BASE_LOGIN = '/monitor/monitor-fan-main',

+ 4 - 2
src/router/guard/permissionGuard.ts

@@ -21,7 +21,7 @@ import { useAutoLogin } from '/@/hooks/vent/useAutoLogin';
 import { addBrowseLog } from '@/router/helper/menuHelper';
 import { string } from 'vue-types';
 
-const LOGIN_PATH = PageEnum.BASE_LOGIN;
+const LOGIN_PATH = VENT_PARAM['isLoginByCode'] ? PageEnum.BASE_LOGIN_CODE : PageEnum.BASE_LOGIN;
 //auth2登录路由
 const OAUTH2_LOGIN_PAGE_PATH = PageEnum.OAUTH2_LOGIN_PAGE_PATH;
 
@@ -211,7 +211,9 @@ export function createPermissionGuard(router: Router) {
           getFullPath == '/500' ||
           getFullPath == '/400' ||
           getFullPath == '/login?redirect=/' ||
-          getFullPath == '/login?redirect=/login?redirect=/'
+          getFullPath == '/login?redirect=/login?redirect=/' ||
+          getFullPath == '/loginCode?redirect=/' ||
+          getFullPath == '/loginCode?redirect=/loginCode?redirect=/'
         ) {
           return;
         }

+ 17 - 0
src/router/routes/index.ts

@@ -50,6 +50,22 @@ export const LoginRoute: AppRouteRecordRaw = {
   ver: '1',
 };
 
+export const LoginInputCodeRoute: AppRouteRecordRaw = {
+  path: '/loginCode',
+  name: 'LoginCode',
+  //新版后台登录,如果想要使用旧版登录放开即可
+  component: () => import('/@/views/sys/login/LoginInputCode.vue'),
+  //数据中心登录
+  // component: () => import('/@/views/sys/login/LoginDataCenter.vue'),
+  // 内页登录
+  // component: () => import('/@/views/sys/login/LoginNeiye.vue'),
+  // component: () => import('/@/views/system/loginmini/MiniLogin.vue'),
+  meta: {
+    title: t('routes.basic.login'),
+  },
+  ver: '1',
+};
+
 export const Oauth2LoginRoute: AppRouteRecordRaw = {
   path: '/oauth2-app/login',
   name: 'oauth2-app-login',
@@ -93,6 +109,7 @@ export const basicRoutes = [
   LoginRoute,
   RootRoute,
   ventModelRedirect,
+  LoginInputCodeRoute,
   // QIANKUN_ROUTE,
   // ...mainOutRoutes,
   REDIRECT_ROUTE,

+ 279 - 0
src/views/sys/login/LoginFormInputCode.vue

@@ -0,0 +1,279 @@
+<template>
+  <div class="login-box">
+    <div class="center">
+      <!-- <LoginFormTitle v-show="getShow" class="enter-x" /> -->
+      <Form
+        class="p-4 enter-x"
+        :model="formData"
+        :rules="getFormRules"
+        ref="formRef"
+        v-show="getShow"
+        @keypress.enter="handleLogin"
+        autocomplete="off"
+      >
+        <FormItem name="account" class="enter-x">
+          <div class="input-box">
+            <Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" class="fix-auto-fill" />
+          </div>
+        </FormItem>
+        <FormItem name="password" class="enter-x">
+          <div class="input-box">
+            <InputPassword size="large" visibilityToggle v-model:value="formData.password" :placeholder="t('sys.login.password')" />
+          </div>
+        </FormItem>
+        <!--验证码 111-->
+        <FormItem name="inputCode" class="enter-x">
+          <div class="input-box">
+            <Input size="large" visibilityToggle v-model:value="formData.inputCode" :placeholder="t('sys.login.inputCode')" />
+            <div style="position: absolute; top: 25px; right: 100px">
+              <img
+                v-if="randCodeData.requestCodeSuccess"
+                style="margin-top: 2px; min-height: 40px"
+                :src="randCodeData.randCodeImage"
+                @click="handleChangeCheckCode"
+              />
+              <img v-else style="margin-top: 2px; min-height: 40px" src="../../../assets/images/checkcode.png" @click="handleChangeCheckCode" />
+            </div>
+          </div>
+        </FormItem>
+      </Form>
+      <div class="btn-box">
+        <div class="btn" @click="handleLogin"> {{ t('sys.login.loginButton') }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { reactive, ref, toRaw, unref, computed, onMounted } from 'vue';
+
+  import { Checkbox, Form, Input, Row, Col, Button } from 'ant-design-vue';
+  import { createFromIconfontCN } from '@ant-design/icons-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  import { useUserStore } from '/@/store/modules/user';
+  import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { getCodeInfo } from '/@/api/sys/user';
+  //import { onKeyStroke } from '@vueuse/core';
+
+  const ACol = Col;
+  const ARow = Row;
+  const FormItem = Form.Item;
+  const InputPassword = Input.Password;
+  const IconFont = createFromIconfontCN({
+    scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
+  });
+  const { t } = useI18n();
+  const { notification, createErrorModal } = useMessage();
+  const { prefixCls } = useDesign('login');
+  const userStore = useUserStore();
+
+  const { setLoginState, getLoginState } = useLoginState();
+  const { getFormRules } = useFormRules();
+
+  const formRef = ref();
+  const thirdModalRef = ref();
+  const loading = ref(false);
+  const rememberMe = ref(false);
+
+  const formData = reactive({
+    account: '',
+    password: '',
+    inputCode: '',
+  });
+  const randCodeData = reactive({
+    randCodeImage: '',
+    requestCodeSuccess: false,
+    checkKey: null,
+  });
+
+  const { validForm } = useFormValid(formRef);
+
+  //onKeyStroke('Enter', handleLogin);
+
+  const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN);
+
+  async function handleLogin() {
+    const data = await validForm();
+    if (!data) return;
+    try {
+      loading.value = true;
+      const { userInfo } = await userStore.login(
+        toRaw({
+          password: data.password,
+          username: data.account,
+          captcha: data.inputCode,
+          checkKey: randCodeData.checkKey,
+          mode: 'none', //不要默认的错误提示
+        })
+      );
+      if (userInfo) {
+        notification.success({
+          message: t('sys.login.loginSuccessTitle'),
+          description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
+          duration: 3,
+        });
+      }
+    } catch (error) {
+      // notification.error({
+      //   message: t('sys.api.errorTip'),
+      //   description: error.message || t('sys.api.networkExceptionMsg'),
+      //   duration: 3,
+      // });
+      loading.value = false;
+
+      //update-begin-author:taoyan date:2022-5-3 for: issues/41 登录页面,当输入验证码错误时,验证码图片要刷新一下,而不是保持旧的验证码图片不变
+      handleChangeCheckCode();
+      //update-end-author:taoyan date:2022-5-3 for: issues/41 登录页面,当输入验证码错误时,验证码图片要刷新一下,而不是保持旧的验证码图片不变
+    }
+  }
+  function handleChangeCheckCode() {
+    formData.inputCode = '';
+    //TODO 兼容mock和接口,暂时这样处理
+    randCodeData.checkKey = 1629428467008; //new Date().getTime();
+    getCodeInfo(randCodeData.checkKey).then((res) => {
+      randCodeData.randCodeImage = res;
+      randCodeData.requestCodeSuccess = true;
+    });
+  }
+
+  /**
+   * 第三方登录
+   * @param type
+   */
+  function onThirdLogin(type) {
+    thirdModalRef.value.onThirdLogin(type);
+  }
+  //初始化验证码
+  onMounted(() => {
+    handleChangeCheckCode();
+  });
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+  @ventSpace: zxm;
+
+  @{theme-green} {
+    .login-box {
+      --image-input-normal: url('/@/assets/images/themify/green/vent/login/input-normal.png');
+      --image-input-down: url('/@/assets/images/themify/green/vent/login/input-down.png');
+      --image-btn-bg: url('/@/assets/images/themify/green/vent/login/btn-bg.png');
+      --image-btn-bg-hover: url('/@/assets/images/themify/green/vent/login/btn-bg-hover.png');
+    }
+  }
+  @{theme-deepblue} {
+    .login-box {
+      --image-input-normal: url('/@/assets/images/themify/deepblue/vent/login/input-normal.png');
+      --image-input-down: url('/@/assets/images/themify/deepblue/vent/login/input-down.png');
+      --image-btn-bg: url('/@/assets/images/themify/deepblue/vent/login/btn-bg.png');
+      --image-btn-bg-hover: url('/@/assets/images/themify/deepblue/vent/login/btn-bg-hover.png');
+    }
+  }
+
+  .login-box {
+    --image-input-normal: url('/@/assets/images/vent/login/input-normal.png');
+    --image-input-down: url('/@/assets/images/vent/login/input-down.png');
+    --image-btn-bg: url('/@/assets/images/vent/login/btn-bg.png');
+    --image-btn-bg-hover: url('/@/assets/images/vent/login/btn-bg-hover.png');
+
+    position: relative;
+    margin-top: 10px;
+    :deep(.@{ventSpace}-form-item) {
+      .@{ventSpace}-input-affix-wrapper-focused,
+      .@{ventSpace}-form-item-control-input-content,
+      .@{ventSpace}-input,
+      .@{ventSpace}-input-password {
+        color: #fff !important;
+        background: #ffffff00 !important;
+        border: none !important;
+        box-shadow: none !important;
+        font-size: 28px !important;
+        &:focus,
+        &:active,
+        &:hover {
+          color: #fff !important;
+          border: none !important;
+          box-shadow: none !important;
+        }
+      }
+      .@{ventSpace}-input-affix-wrapper {
+        padding: 0 !important;
+      }
+    }
+    :deep(.@{ventSpace}-input-password) {
+      input {
+        padding: 4px 11px;
+        padding-left: 20px !important;
+        margin-left: 20px !important;
+        text-align: center;
+      }
+    }
+    :deep(.@{ventSpace}-form-item-control-input-content) {
+      display: flex;
+      justify-content: center;
+    }
+    .input-box {
+      width: 903px;
+      height: 90px;
+      border: none !important;
+      text-align: center;
+      padding-top: 30px !important;
+      padding-left: 20px !important;
+      background-color: transparent !important;
+      background-size: cover;
+      background: var(--image-input-normal) !important;
+      background-size: cover !important;
+      &:focus,
+      &:active {
+        background: #ffffff00 !important;
+        color: #fff !important;
+        border: none !important;
+        box-shadow: none !important;
+      }
+      &:focus {
+        background: var(--image-input-down) !important;
+      }
+      :deep(.@{ventSpace}-input) {
+        width: 100%;
+        text-align: center;
+        &:-webkit-autofill,
+        &:-webkit-autofill:hover,
+        &:-webkit-autofill:focus,
+        &:-webkit-autofill:active {
+          -webkit-transition-delay: 99999s;
+          -webkit-transition:
+            color 99999s ease-out,
+            background-color 99999s ease-out;
+          color: #fff;
+        }
+      }
+    }
+
+    .btn-box {
+      width: 100%;
+      position: absolute;
+      top: 330px;
+      left: 0px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      cursor: pointer;
+      .btn {
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 28px;
+        padding-top: 20px;
+        width: 562px;
+        height: 137px;
+        background: var(--image-btn-bg);
+        background: 100% auto;
+        &:active {
+          background: var(--image-btn-bg-hover);
+        }
+      }
+    }
+  }
+</style>

+ 304 - 0
src/views/sys/login/LoginInputCode.vue

@@ -0,0 +1,304 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <!-- <AdaptiveContainer :options="{ width: 1920, height: 1080 }" style="overflow-y: hidden"> -->
+  <div class="login-container" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%">
+    <div class="login-bg"></div>
+    <span class="-enter-x xl:hidden">
+      <AppLogo :alwaysShowTitle="true" />
+    </span>
+    <div class="top-header">
+      <div class="top-bg"></div>
+      <div class="login-icon"></div>
+      <div class="title">{{ title }}</div>
+    </div>
+
+    <div class="flex center">
+      <LoginFormInputCode />
+    </div>
+    <div class="bottom"> </div>
+  </div>
+  <!-- </AdaptiveContainer> -->
+</template>
+<script lang="ts" setup>
+  import { computed } from 'vue';
+  import { AppLogo } from '/@/components/Application';
+  import LoginFormInputCode from './LoginFormInputCode.vue';
+  import { useGlobSetting } from '/@/hooks/setting';
+  // import { useI18n } from '/@/hooks/web/useI18n';
+  // import { useDesign } from '/@/hooks/web/useDesign';
+  // import { useLocaleStore } from '/@/store/modules/locale';
+  import { useLoginState } from './useLogin';
+  // import AdaptiveContainer from '/@/components/Container/src/Adaptive.vue';
+
+  defineProps({
+    sessionTimeout: {
+      type: Boolean,
+    },
+  });
+  const globSetting = useGlobSetting();
+  // const { prefixCls } = useDesign('login');
+  // const { t } = useI18n();
+  // const localeStore = useLocaleStore();
+  // const showLocale = localeStore.getShowPicker;
+  const title = computed(() => globSetting?.title ?? '');
+  const { handleBackLogin } = useLoginState();
+  handleBackLogin();
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+  @prefix-cls: ~'@{namespace}-login';
+  @logo-prefix-cls: ~'@{namespace}-app-logo';
+  @countdown-prefix-cls: ~'@{namespace}-countdown-input';
+  @dark-bg: #293146;
+  @ventSpace: zxm;
+
+  html[data-theme='dark'] {
+    .@{prefix-cls} {
+      background-color: @dark-bg;
+
+      &::before {
+        background-image: url(/@/assets/svg/login-bg-dark.svg);
+      }
+
+      .@{ventSpace}-input,
+      .@{ventSpace}-input-password {
+        background-color: #232a3b;
+      }
+
+      .@{ventSpace}-btn:not(.@{ventSpace}-btn-link):not(.@{ventSpace}-btn-primary) {
+        border: 1px solid #4a5569;
+      }
+
+      &-form {
+        background: transparent !important;
+      }
+
+      .app-iconify {
+        color: #fff;
+      }
+    }
+
+    input.fix-auto-fill,
+    .fix-auto-fill input {
+      -webkit-text-fill-color: #c9d1d9 !important;
+      box-shadow: inherit !important;
+    }
+  }
+
+  // 绿色主题特化的变量
+  @{theme-green} {
+    .login-container {
+      --image-top: url('/@/assets/images/themify/green/vent/login/top.png');
+      --image-icon: url('/@/assets/images/themify/green/vent/login/icon.png');
+      --image-down: url('/@/assets/images/themify/green/vent/login/down.png');
+      --image-bg: none;
+      --container-bg: #181b24;
+    }
+    .login-icon {
+      width: 325px !important;
+      background-size: 100% 100%;
+    }
+  }
+  // 深蓝色主题特化的变量
+  @{theme-deepblue} {
+    .login-container {
+      --image-top: url('/@/assets/images/themify/deepblue/vent/login/top.png');
+      --image-icon: url('/@/assets/images/themify/deepblue/vent/login/icon.png');
+      --image-down: url('/@/assets/images/themify/deepblue/vent/login/down.png');
+      --image-bg: none;
+      --container-bg: #021632;
+    }
+  }
+
+  .login-container {
+    // 需要主题化的变量放在上面
+    --image-top: url('/@/assets/images/vent/login/top.png');
+    --image-icon: url('/@/assets/images/vent/login/icon.png');
+    --image-down: url('/@/assets/images/vent/login/down.png');
+    --image-bg: url('/@/assets/images/vent/login/bg.png');
+    --container-bg: linear-gradient(to bottom, #000c37, #001e63);
+
+    // 下面是不需要主题化的变量,例如单色图标、中性颜色等
+    --login-bg: linear-gradient(to bottom, #000c3766, #001e6366);
+    color: @vent-text-base;
+    width: 100vw;
+    height: 100%;
+    background: var(--container-bg);
+    padding: 0 !important;
+    position: relative;
+    &::before {
+      content: '';
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0px;
+      left: 0;
+      background-image: var(--image-bg);
+      background-size: cover;
+    }
+    .login-bg {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0;
+      left: 0;
+      background: var(--login-bg);
+    }
+    .top-header {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      position: relative;
+      // padding-top: 70px;
+      .top-bg {
+        width: 100%;
+        height: 129px;
+
+        background-image: var(--image-top);
+        background-size: 100% auto;
+      }
+      .login-icon {
+        position: relative;
+        width: 207px;
+        height: 234px;
+        top: 10px;
+        background-image: var(--image-icon);
+        background-size: cover;
+      }
+      .title {
+        position: absolute;
+        top: 78px;
+        color: rgb(224, 224, 224);
+        font-size: 30px;
+        text-align: center;
+        text-shadow:
+          1px 1px 1px #fff,
+          -1px -1px 1px #000;
+      }
+    }
+
+    .bottom {
+      width: 100%;
+      height: 118px;
+      position: fixed;
+      bottom: 0;
+      left: 0;
+      background-image: var(--image-down);
+      background-size: 100% auto;
+    }
+
+    .center {
+      width: 100%;
+      height: calc(100% - 540px);
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      position: relative;
+      justify-content: center;
+    }
+  }
+
+  .@{prefix-cls} {
+    min-height: 100%;
+    overflow: hidden;
+    @media (max-width: @screen-xl) {
+      background-color: #293146;
+
+      .@{prefix-cls}-form {
+        background-color: #fff;
+      }
+    }
+
+    &::before {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      margin-left: -48%;
+      // background-image: url(/@/assets/svg/login-bg.svg);
+      background-position: 100%;
+      background-repeat: no-repeat;
+      background-size: auto 100%;
+      content: '';
+      @media (max-width: @screen-xl) {
+        display: none;
+      }
+    }
+
+    .@{logo-prefix-cls} {
+      position: absolute;
+      top: 12px;
+      height: 30px;
+
+      &__title {
+        font-size: 16px;
+        color: #fff;
+      }
+
+      img {
+        width: 32px;
+      }
+    }
+
+    .container {
+      .@{logo-prefix-cls} {
+        display: flex;
+        width: 60%;
+        height: 80px;
+
+        &__title {
+          font-size: 24px;
+          color: #fff;
+        }
+
+        img {
+          width: 48px;
+        }
+      }
+    }
+
+    &-sign-in-way {
+      .anticon {
+        font-size: 22px;
+        color: #888;
+        cursor: pointer;
+
+        &:hover {
+          color: @primary-color;
+        }
+      }
+    }
+
+    input:not([type='checkbox']) {
+      min-width: 360px;
+
+      @media (max-width: @screen-xl) {
+        min-width: 320px;
+      }
+
+      @media (max-width: @screen-lg) {
+        min-width: 260px;
+      }
+
+      @media (max-width: @screen-md) {
+        min-width: 240px;
+      }
+
+      @media (max-width: @screen-sm) {
+        min-width: 160px;
+      }
+    }
+
+    .@{countdown-prefix-cls} input {
+      min-width: unset;
+    }
+
+    .@{ventSpace}-divider-inner-text {
+      font-size: 12px;
+      color: @text-color-secondary;
+    }
+  }
+</style>

+ 8 - 5
src/views/system/user/user.data.ts

@@ -26,11 +26,14 @@ export const columns: BasicColumn[] = [
     dataIndex: 'userRoles',
     width: 100,
     customRender: ({ value }) => {
-      const nameList = [];
-      value.forEach((item) => {
-        nameList.push(item['roleName']);
-      });
-      return nameList.toString();
+      if (value) {
+        const nameList = [];
+        value.forEach((item) => {
+          nameList.push(item['roleName']);
+        });
+        return nameList.toString();
+      }
+      return '';
     },
   },
   {

+ 65 - 107
src/views/vent/deviceManager/comment/warningTabel/BaseModal1Leather.vue

@@ -25,6 +25,7 @@
         </div>
         <a-button type="dashed" block @click="addWarnRow" class="setting-form__button">新增预警配置</a-button>
       </div>
+
       <!-- 配置控制设备 -->
       <div class="setting-form__wrapper">
         <h3 class="panel-title">配置控制设备</h3>
@@ -84,7 +85,6 @@ const isUpdate = ref(false);
 // 表单Ref数组
 const warnFormRefs = ref<FormActionType[]>([]);
 const controlFormRefs = ref<FormActionType[]>([]);
-// 点位弹窗临时保存当前行
 const tempCurrentModel = ref<any>(null);
 const devicePointList = ref<any[]>([]);
 
@@ -103,13 +103,11 @@ function getEmptyWarnRow() {
     monitorType: '2',
   };
 }
-// 收集预警表单Ref
 function setWarnFormRef(el: any, index: number) {
   if (el) warnFormRefs.value[index] = el;
 }
 function addWarnRow() {
   warnList.value.push(getEmptyWarnRow());
-  warnFormRefs.value.push(null);
 }
 function delWarnRow(idx: number) {
   if (warnList.value.length <= 1) return message.warning('至少保留一行');
@@ -130,160 +128,123 @@ function getEmptyControlRow() {
     monitorType: '1',
   };
 }
-// 收集控制表单Ref
 function setControlFormRef(el: any, index: number) {
   if (el) controlFormRefs.value[index] = el;
 }
 function addControlRow() {
   controlList.value.push(getEmptyControlRow());
-  controlFormRefs.value.push(null);
 }
 async function delControlRow(idx: number, record: any) {
   if (controlList.value.length <= 1) return message.warning('至少保留一行');
   controlList.value.splice(idx, 1);
   controlFormRefs.value.splice(idx, 1);
-  await workFaceWarningDelete({ id: record.id });
+  if (record.id) await workFaceWarningDelete({ id: record.id });
 }
 
-// 获取点位列表
+// 获取点位
 async function getDevicePointList(strtype: string) {
   try {
-    const result = await workFacePointList({ deviceType: strtype, valueTypes: props.monitorType });
-    devicePointList.value = result;
-  } catch (error) {
+    const res = await workFacePointList({ deviceType: strtype, valueTypes: props.monitorType });
+    devicePointList.value = res;
+  } catch {
     devicePointList.value = [];
   }
 }
-
-// 打开点位选择弹窗
 async function selectPoint(model: any) {
-  if (!model.deviceType) {
-    message.info('请先选择设备!');
-    return;
-  }
-  console.log(model, '=====model');
-  // 缓存当前编辑行
+  if (!model.deviceType) return message.info('请先选择设备!');
   tempCurrentModel.value = model;
   await getDevicePointList(model.deviceType);
   openPointModal(true, { deviceType: model.deviceType });
 }
-
-// 选中点位回填到当前行
 function setPoint(value: any[]) {
-  if (!value || !value.length || !tempCurrentModel.value) return;
+  if (!value?.length || !tempCurrentModel.value) return;
   const data = value[0];
-  const model = tempCurrentModel.value;
-  // 赋值当前行
-  model.monitorId = data.id;
-  model.pointOptions = [{ value: data.id, label: data.valuename }];
+  const m = tempCurrentModel.value;
+  m.monitorId = data.id;
+  m.pointOptions = [{ value: data.id, label: data.valuename }];
 }
 
+// 主表单
 const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
   schemas: props.formSchemas,
   showActionButtonGroup: false,
 });
 
+// 弹窗
 const [register, { setModalProps, closeModal }] = useModalInner(async (data) => {
   isUpdate.value = unref(data.isUpdate);
-  await resetFields();
   title.value = data.isUpdate ? '编辑' : '新增';
-
+  await resetFields();
   warnFormRefs.value = [];
   controlFormRefs.value = [];
   tempCurrentModel.value = null;
-
+  warnList.value = [getEmptyWarnRow()];
+  controlList.value = [getEmptyControlRow()];
   if (data.isUpdate && data.record) {
     await setFieldsValue(data.record);
-    const allAlarms = data.record.alarmList || [];
-    const warnings = allAlarms.filter((item: any) => String(item.monitorType) === '2');
-    const controls = allAlarms.filter((item: any) => String(item.monitorType) === '1');
-    warnList.value = warnings.length ? warnings : [getEmptyWarnRow()];
-    controlList.value = controls.length ? controls : [getEmptyControlRow()];
-  } else {
-    warnList.value = [getEmptyWarnRow()];
-    controlList.value = [getEmptyControlRow()];
-  }
-
-  if (data.type === 'devicePoint') {
-    title.value = unref(data.title);
-    if (data.record) {
-      await getDevicePointList(data.record['deviceType']);
-      devicePointList.value.forEach((item) => {
-        if (item.id == data.record.monitorId) {
-          const model = data.record;
-          model.monitorId = item.id;
-          model.pointOptions = [{ value: item.id, label: item.valuename }];
-        }
-      });
-      await setFieldsValue({ ...data.record });
+    const all = data.record.alarmList || [];
+    warnList.value = all.filter((i) => String(i.monitorType) === '2') || [getEmptyWarnRow()];
+    controlList.value = all.filter((i) => String(i.monitorType) === '1') || [getEmptyControlRow()];
+    await nextTick();
+    for (let i = 0; i < warnList.value.length; i++) {
+      const form = warnFormRefs.value[i];
+      const row = warnList.value[i];
+      if (form && row) await form.setFieldsValue(row);
+    }
+    for (let i = 0; i < controlList.value.length; i++) {
+      const form = controlFormRefs.value[i];
+      const row = controlList.value[i];
+      if (form && row) await form.setFieldsValue(row);
     }
   }
 });
 
-// 提交
-// 提交
+// ✅ 提交修复
 async function onSubmit() {
   setModalProps({ confirmLoading: true });
   try {
     const baseForm = await getFieldsValue();
 
-    // 获取预警数据
-    const warnValues = [];
-    for (const form of warnFormRefs.value) {
-      if (form) {
-        const val = await form.getFieldsValue();
-        delete val.pointOptions;
-        if (val.strtype) {
-          val.deviceType = val.strtype;
-        }
-        delete val.strtype;
-        const deviceId = Array.isArray(val.deviceId) ? val.deviceId[0] : val.deviceId || '';
-        if (!deviceId) continue;
-        warnValues.push({
-          ...val,
-          monitorType: 2,
-          deviceId: deviceId,
-        });
-      }
+    // 读取预警行 —— 没有 monitorId 则过滤不提交
+    const warnData = [];
+    for (let i = 0; i < warnFormRefs.value.length; i++) {
+      const form = warnFormRefs.value[i];
+      if (!form) continue;
+      const val = await form.getFieldsValue();
+      delete val.pointOptions;
+      if (val.strtype) val.deviceType = val.strtype;
+      delete val.strtype;
+      if (!val.monitorId) continue;
+
+      warnData.push({ ...val, monitorType: 2 });
     }
 
-    // 获取控制数据
-    const controlValues = [];
-    for (const form of controlFormRefs.value) {
-      if (form) {
-        const val = await form.getFieldsValue();
-        delete val.pointOptions;
+    // 读取控制行 —— 没有 monitorId 则过滤不提交
+    const controlData = [];
+    for (let i = 0; i < controlFormRefs.value.length; i++) {
+      const form = controlFormRefs.value[i];
+      if (!form) continue;
+      const val = await form.getFieldsValue();
+      delete val.pointOptions;
+      if (val.strtype) val.deviceType = val.strtype;
+      delete val.strtype;
 
-        if (val.strtype) {
-          val.deviceType = val.strtype;
-        }
-        delete val.strtype;
-        const deviceId = Array.isArray(val.deviceId) ? val.deviceId[0] : val.deviceId || '';
-        if (!deviceId) continue;
+      // ✅ 关键:没有 monitorId 就跳过,不提交
+      if (!val.monitorId) continue;
 
-        controlValues.push({
-          ...val,
-          monitorType: 1,
-          deviceId: deviceId,
-        });
-      }
+      controlData.push({ ...val, monitorType: 1 });
     }
 
-    const submitData = {
+    const params = {
       ...baseForm,
-      alarmList: [...warnValues, ...controlValues],
+      alarmList: [...warnData, ...controlData],
     };
 
-    if (!isUpdate.value) {
-      emit('add', submitData);
-    } else {
-      emit('update', submitData);
-    }
-
+    isUpdate.value ? emit('update', params) : emit('add', params);
     closeModal();
-  } catch (error) {
-    console.error('提交异常:', error);
-    message.error('提交失败,请检查表单');
+  } catch (err) {
+    console.error(err);
+    message.error('提交失败');
   } finally {
     setModalProps({ confirmLoading: false });
   }
@@ -293,38 +254,35 @@ async function onSubmit() {
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
-.setting-form {
-  padding: 0 20px;
-}
-
 .setting-form__wrapper {
   width: 49%;
   color: #fff;
   border-top: 3px solid @vent-gas-primary-text;
   background-color: @vent-gas-primary-trasparent-bg;
-  padding: 10px 10px 0 10px;
-  margin-bottom: 10px;
+  padding: 10px;
 }
 
 .setting-form__button {
-  display: block;
   width: 40%;
   margin: 10px auto;
   color: @vent-gas-primary-text;
   border-color: @vent-gas-primary-text;
-  background-color: transparent !important;
+  background: transparent !important;
 }
+
 .double-form__layout {
   display: flex;
   gap: 16px;
   margin-top: 16px;
 }
+
 .panel-title {
   margin: 0 0 10px;
   font-size: 14px;
   font-weight: bold;
   color: #fafafa;
 }
+
 .form-row {
   border: 1px solid #2e7695;
   display: flex;

+ 2 - 2
src/views/vent/deviceManager/comment/warningTabel/indexLeather.vue

@@ -91,8 +91,8 @@ async function handleDelete(record) {
 
 async function onSubmit(values) {
   const params = { ...values };
-  if (params.pNum) {
-    params.partNum = params.pNum;
+  if (params.pnum) {
+    params.partNum = params.pnum;
   }
 
   const isUpdate = !!params.id;

+ 378 - 378
src/views/vent/home/configurable/belt/belt-new.vue

@@ -48,362 +48,316 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, nextTick, computed, onUnmounted } from 'vue';
-import customHeader from './components/customHeader-belt.vue';
-import { useInitConfigs, useInitPage } from '../hooks/useInit';
-import { testBeltNew, testYjkf, testSpary } from './configurable.data';
-import ModuleCommon from './components/ModuleCommon.vue';
-import ModuleCommonDual from './components/ModuleCommonDual.vue';
-import Three3D from '/@/views/vent/home/configurable/components/three3D.vue';
-import BeltNav from './components/BeltNav.vue';
-import { useRouter, useRoute } from 'vue-router';
-import { getSystem, getMonitorAndAlertBelt, getWarnInfo, getDevice, getDataHome, getWarnResult, getStaffInfo } from './configurable.api';
-import { modalAnimate, destroy } from './threejs/belt.threejs';
-import History from './history.vue';
-import sys from '/@/locales/lang/en/sys';
-// 初始化配置
-const { configs, fetchConfigs } = useInitConfigs();
-const cfgs = computed(() => configs.value.filter((_, index) => index !== 2 && index !== 3));
-const cfgA = computed<any>(() => configs.value[2]);
-const cfgB = computed<any>(() => configs.value[3]);
-const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
-const isInitModal = ref(false);
-const pageCache = ref({
-  fire_risk_warn: { configs: testBeltNew },
-  emergencyControl: { configs: testYjkf },
-  sprayControl: { configs: testSpary },
-});
-let timer = null,
-  modal = null;
-const pageType = ref('fire_risk_warn');
-const route = useRoute();
-const modalMonitorData = ref({});
-// 下拉框选项
-/** 场景选项 */
-const options = ref([]);
-const optionValue = ref('');
-async function getSysDataSource() {
-  const res = await getDataHome({ dataList: 'risk_evaluator' }).catch(() => {});
-  options.value = res.risk_evaluator || [];
-  await nextTick();
-  if (options.value.length > 0 && !optionValue.value) {
-    const firstId = options.value[0].sys_id;
-    changeSelectRow(firstId);
-  }
-}
-// 切换检测数据
-function changeSelectRow(deviceID) {
-  optionValue.value = deviceID;
-  refresh();
-}
-// 预警等级映射
-const warnTypeMap = {
-  '102': '黄色预警(较大风险)',
-  '103': '橙色预警(重大风险)',
-  '104': '红色预警(特别重大风险)',
-};
-// 处理接口返回数据
-interface WarnResult {
-  warnName: string;
-  coRange?: string;
-  coTrend?: string;
-  tempRange?: string;
-  tempTrend?: string;
-  hclRange?: string;
-  warnCtrl?: string;
-}
-// 处理预警指标展示数据格式
-function groupWarnByType(data: any) {
-  const result: WarnResult[] = [];
-  // 遍历 102、103、104...
-  Object.keys(data).forEach((warnKey) => {
-    const list = data[warnKey] || [];
-    const warnName = warnTypeMap[warnKey as keyof typeof warnTypeMap];
-    function getWarnCtrlByWarnName(warnName) {
-      if (warnName === '黄色预警(较大风险)') return '预警';
-      if (warnName === '橙色预警(重大风险)') return '报警启动喷淋';
-      if (warnName === '红色预警(特别重大风险)') return '启动喷淋与排烟';
-      return '';
+  import { onMounted, ref, watch, nextTick, computed, onUnmounted } from 'vue';
+  import customHeader from './components/customHeader-belt.vue';
+  import { useInitConfigs, useInitPage } from '../hooks/useInit';
+  import { testBeltNew, testYjkf, testSpary } from './configurable.data';
+  import ModuleCommon from './components/ModuleCommon.vue';
+  import ModuleCommonDual from './components/ModuleCommonDual.vue';
+  import Three3D from '/@/views/vent/home/configurable/components/three3D.vue';
+  import BeltNav from './components/BeltNav.vue';
+  import { useRouter, useRoute } from 'vue-router';
+  import { getSystem, getMonitorAndAlertBelt, getWarnInfo, getDevice, getDataHome, getWarnResult, getStaffInfo } from './configurable.api';
+  import { modalAnimate, destroy } from './threejs/belt.threejs';
+  import History from './history.vue';
+  import sys from '/@/locales/lang/en/sys';
+  // 初始化配置
+  const { configs, fetchConfigs } = useInitConfigs();
+  const cfgs = computed(() => configs.value.filter((_, index) => index !== 2 && index !== 3));
+  const cfgA = computed<any>(() => configs.value[2]);
+  const cfgB = computed<any>(() => configs.value[3]);
+  const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
+  const isInitModal = ref(false);
+  const pageCache = ref({
+    fire_risk_warn: { configs: testBeltNew },
+    emergencyControl: { configs: testYjkf },
+    sprayControl: { configs: testSpary },
+  });
+  let timer = null,
+    modal = null;
+  const pageType = ref('fire_risk_warn');
+  const route = useRoute();
+  const modalMonitorData = ref({});
+  // 下拉框选项
+  /** 场景选项 */
+  const options = ref([]);
+  const optionValue = ref('');
+  async function getSysDataSource() {
+    const res = await getDataHome({ dataList: 'risk_evaluator' }).catch(() => {});
+    options.value = res.risk_evaluator || [];
+    await nextTick();
+    if (options.value.length > 0 && !optionValue.value) {
+      const firstId = options.value[0].sys_id;
+      changeSelectRow(firstId);
     }
-    // 构建当前预警对象
-    const warnObj: WarnResult = { warnName };
-    warnObj.warnCtrl = getWarnCtrlByWarnName(warnName);
-    list.forEach((item: any) => {
-      const { deviceType, fmin, fmax, trendMin, trendMax, trendCxTimeUnit } = item;
-      // CO
-      if (deviceType === 'modelsensor_co') {
-        if (fmin != null && fmax != null) {
-          warnObj.coRange = `${fmin} - ${fmax}`;
-        } else if (fmin != null && fmax == null) {
-          warnObj.coRange = `> ${fmin}`;
-        }
-        // 处理变化率
-        else if (trendMin != null && trendMax != null) {
-          if (warnName === '黄色预警(较大风险)') {
-            warnObj.coTrend = `加速上升(${trendMin} - ${trendMax})`;
-          } else if (warnName === '橙色预警(重大风险)') {
-            warnObj.coTrend = `急剧上升(${trendMin} - ${trendMax})`;
-          } else if (warnName === '红色预警(特别重大风险)') {
-            warnObj.coTrend = `极端上升(${trendMin} - ${trendMax})`;
+  }
+  // 切换检测数据
+  function changeSelectRow(deviceID) {
+    optionValue.value = deviceID;
+    refresh();
+  }
+  // 预警等级映射
+  const warnTypeMap = {
+    '102': '黄色预警(较大风险)',
+    '103': '橙色预警(重大风险)',
+    '104': '红色预警(特别重大风险)',
+  };
+  // 处理接口返回数据
+  interface WarnResult {
+    warnName: string;
+    coRange?: string;
+    coTrend?: string;
+    tempRange?: string;
+    tempTrend?: string;
+    hclRange?: string;
+    warnCtrl?: string;
+  }
+  // 处理预警指标展示数据格式
+  function groupWarnByType(data: any) {
+    const result: WarnResult[] = [];
+    // 遍历 102、103、104...
+    Object.keys(data).forEach((warnKey) => {
+      const list = data[warnKey] || [];
+      const warnName = warnTypeMap[warnKey as keyof typeof warnTypeMap];
+      function getWarnCtrlByWarnName(warnName) {
+        if (warnName === '黄色预警(较大风险)') return '预警';
+        if (warnName === '橙色预警(重大风险)') return '报警启动喷淋';
+        if (warnName === '红色预警(特别重大风险)') return '启动喷淋与排烟';
+        return '';
+      }
+      // 构建当前预警对象
+      const warnObj: WarnResult = { warnName };
+      warnObj.warnCtrl = getWarnCtrlByWarnName(warnName);
+      list.forEach((item: any) => {
+        const { deviceType, fmin, fmax, trendMin, trendMax, trendCxTimeUnit } = item;
+        // CO
+        if (deviceType === 'modelsensor_co') {
+          if (fmin != null && fmax != null) {
+            warnObj.coRange = `${fmin} - ${fmax}`;
+          } else if (fmin != null && fmax == null) {
+            warnObj.coRange = `> ${fmin}`;
+          }
+          // 处理变化率
+          else if (trendMin != null && trendMax != null) {
+            if (warnName === '黄色预警(较大风险)') {
+              warnObj.coTrend = `加速上升(${trendMin} - ${trendMax})`;
+            } else if (warnName === '橙色预警(重大风险)') {
+              warnObj.coTrend = `急剧上升(${trendMin} - ${trendMax})`;
+            } else if (warnName === '红色预警(特别重大风险)') {
+              warnObj.coTrend = `极端上升(${trendMin} - ${trendMax})`;
+            }
+          } else if (trendMin != null && trendMax == null) {
+            warnObj.coTrend = `极端上升>${trendMin}`;
           }
-        } else if (trendMin != null && trendMax == null) {
-          warnObj.coTrend = `极端上升>${trendMin}`;
         }
-      }
-      // 温度(带单位)
-      else if (deviceType === 'modelsensor_temperature') {
-        if (fmin != null && fmax != null) {
-          warnObj.tempRange = `${fmin} - ${fmax}℃`;
-        } else if (fmin != null && fmax == null) {
-          warnObj.tempRange = `≥ ${fmin}℃(持续高温)`;
-        } else if (trendMin != null) {
-          let unit = '';
-          if (trendCxTimeUnit === 0) unit = '分钟';
-          if (trendCxTimeUnit === 1) unit = '小时';
-          warnObj.tempTrend = `>+${trendMin}℃ /${unit}`;
+        // 温度(带单位)
+        else if (deviceType === 'modelsensor_temperature') {
+          if (fmin != null && fmax != null) {
+            warnObj.tempRange = `${fmin} - ${fmax}℃`;
+          } else if (fmin != null && fmax == null) {
+            warnObj.tempRange = `≥ ${fmin}℃(持续高温)`;
+          } else if (trendMin != null) {
+            let unit = '';
+            if (trendCxTimeUnit === 0) unit = '分钟';
+            if (trendCxTimeUnit === 1) unit = '小时';
+            warnObj.tempTrend = `>+${trendMin}℃ /${unit}`;
+          }
         }
-      }
-      // HCL
-      else if (deviceType === 'modelsensor_hcl') {
-        if (fmin != null && fmax != null) {
-          warnObj.hclRange = `${fmin} - ${fmax}`;
-        } else if (fmin != null && fmax == null) {
-          warnObj.hclRange = `≥ ${fmin}`;
+        // HCL
+        else if (deviceType === 'modelsensor_hcl') {
+          if (fmin != null && fmax != null) {
+            warnObj.hclRange = `${fmin} - ${fmax}`;
+          } else if (fmin != null && fmax == null) {
+            warnObj.hclRange = `≥ ${fmin}`;
+          }
         }
-      }
+      });
+      result.push(warnObj);
     });
-    result.push(warnObj);
-  });
 
-  return result;
-}
-// 刷新数据
-async function refresh() {
-  // 由于模型中需要用到风门的监测数据,这里进行公共调用(后期精确调用风门)
-  const modalRes = {};
-  const systemParams = {
-    devicetype: 'sys',
-    systemID: optionValue.value,
-  };
-  const resSys = await getSystem(systemParams);
-  const params = {
-    sysId: optionValue.value,
-    monitorType: 2,
-  };
-  const warnInfo = await getWarnInfo(params);
-  const staffInfo = await getStaffInfo({ sysId: optionValue.value });
-  Object.assign(modalRes, resSys);
-  if (pageType.value == 'fire_risk_warn') {
-    configs.value = [...testBeltNew];
-    const params = {
-      sysId: optionValue.value,
-      dataList: 'fire_risk_warn,warn_result,vehicle_co_correlate',
-      alarmLevel: '102,103,104',
-    };
-    const resWarn = await getMonitorAndAlertBelt(params);
-    resWarn.warnInfo = groupWarnByType(warnInfo);
-    console.log(resWarn.warnInfo, '111111111');
-    resWarn.staffInfo = staffInfo;
-    updateData(resWarn);
-    Object.assign(modalRes, resWarn);
-  } else if (pageType.value == 'emergencyControl') {
-    updateData(resSys);
-    configs.value = [...testYjkf];
-    const alarmParams = {
-      sysId: optionValue.value,
-      alarmLevel: '104',
+    return result;
+  }
+  // 刷新数据
+  async function refresh() {
+    // 由于模型中需要用到风门的监测数据,这里进行公共调用(后期精确调用风门)
+    const modalRes = {};
+    const systemParams = {
+      devicetype: 'sys',
+      systemID: optionValue.value,
     };
-    const alarmRes = await getWarnResult(alarmParams);
-    if (alarmRes.warn_result) {
-      data.value.warn_result = alarmRes.warn_result;
-    }
-    data.value.warnInfo = groupWarnByType(warnInfo);
-    data.value.staffInfo = staffInfo;
-    updateData(data.value);
-  } else if (pageType.value == 'sprayControl') {
-    updateData(resSys);
-    const params1 = {
+    const resSys = await getSystem(systemParams);
+    const params = {
       sysId: optionValue.value,
-      alarmLevel: '103,104',
+      monitorType: 2,
     };
-    const sprayData = [];
-    const dustData = [];
-    if (data.value?.deviceInfo) {
-      // 遍历对象的所有 value
-      Object.values(data.value.deviceInfo).forEach((item) => {
-        const hasSprayAuto = item.type && item.type.toLowerCase().includes('spray');
-        if (hasSprayAuto) {
-          sprayData.push({ ...item, ...item.readData });
-        }
-      });
-      Object.values(data.value.deviceInfo).forEach((item) => {
-        const hasDustAuto = item.type && item.type.toLowerCase().includes('dustdev');
-        if (hasDustAuto) {
-          dustData.push({ ...item, ...item.readData });
-        }
-      });
-    }
-    data.value.sprayData = sprayData;
-    data.value.dustData = dustData;
-    data.value.warnInfo = groupWarnByType(warnInfo);
-    data.value.staffInfo = staffInfo;
-    const alarmRes = await getWarnResult(params1);
-    if (alarmRes.warn_result) {
-      data.value.warn_result = alarmRes.warn_result;
+    const warnInfo = await getWarnInfo(params);
+    const staffInfo = await getStaffInfo({ sysId: optionValue.value });
+    Object.assign(modalRes, resSys);
+    if (pageType.value == 'fire_risk_warn') {
+      configs.value = [...testBeltNew];
+      const params = {
+        sysId: optionValue.value,
+        dataList: 'fire_risk_warn,warn_result,vehicle_co_correlate',
+        alarmLevel: '102,103,104',
+      };
+      const resWarn = await getMonitorAndAlertBelt(params);
+      resWarn.warnInfo = groupWarnByType(warnInfo);
+      console.log(resWarn.warnInfo, '111111111');
+      resWarn.staffInfo = staffInfo;
+      updateData(resWarn);
+      Object.assign(modalRes, resWarn);
+    } else if (pageType.value == 'emergencyControl') {
+      updateData(resSys);
+      configs.value = [...testYjkf];
+      const alarmParams = {
+        sysId: optionValue.value,
+        alarmLevel: '104',
+      };
+      const alarmRes = await getWarnResult(alarmParams);
+      if (alarmRes.warn_result) {
+        data.value.warn_result = alarmRes.warn_result;
+      }
+      data.value.warnInfo = groupWarnByType(warnInfo);
+      data.value.staffInfo = staffInfo;
+      updateData(data.value);
+    } else if (pageType.value == 'sprayControl') {
+      updateData(resSys);
+      const params1 = {
+        sysId: optionValue.value,
+        alarmLevel: '103,104',
+      };
+      const sprayData = [];
+      const dustData = [];
+      if (data.value?.deviceInfo) {
+        // 遍历对象的所有 value
+        Object.values(data.value.deviceInfo).forEach((item) => {
+          const hasSprayAuto = item.type && item.type.toLowerCase().includes('spray');
+          if (hasSprayAuto) {
+            sprayData.push({ ...item, ...item.readData });
+          }
+        });
+        Object.values(data.value.deviceInfo).forEach((item) => {
+          const hasDustAuto = item.type && item.type.toLowerCase().includes('dustdev');
+          if (hasDustAuto) {
+            dustData.push({ ...item, ...item.readData });
+          }
+        });
+      }
+      data.value.sprayData = sprayData;
+      data.value.dustData = dustData;
+      data.value.warnInfo = groupWarnByType(warnInfo);
+      data.value.staffInfo = staffInfo;
+      const alarmRes = await getWarnResult(params1);
+      if (alarmRes.warn_result) {
+        data.value.warn_result = alarmRes.warn_result;
+      }
+      configs.value = [...testSpary];
+    } else {
+      configs.value = testBeltNew;
     }
-    configs.value = [...testSpary];
-  } else {
-    configs.value = testBeltNew;
+    modalMonitorData.value = modalRes;
   }
-  modalMonitorData.value = modalRes;
-}
 
-// // 定时刷新
-function initInterval() {
-  if (timer) clearInterval(timer);
-  timer = setInterval(() => {
-    refresh();
-  }, 60000);
-}
+  // // 定时刷新
+  function initInterval() {
+    if (timer) clearInterval(timer);
+    timer = setInterval(() => {
+      refresh();
+    }, 60000);
+  }
 
-async function changePage(pageTypeStr) {
-  const target = pageTypeStr || route.query.pageType || 'fire_risk_warn';
-  if (pageType.value === target) return;
-  pageType.value = target;
-  configs.value = pageCache.value[target]?.configs || testBeltNew;
-  await nextTick();
-  await refresh();
-}
-// watch(
-//   // 监听动态路由参数 :type
-//   () => route.params.type,
-//   (newVal) => {
-//     if (newVal) {
-//       console.log('切换页面类型:', newVal);
-//       refresh(); // 切换路由自动刷新
-//     }
-//   }
-// );
+  async function changePage(pageTypeStr) {
+    const target = pageTypeStr || route.query.pageType || 'fire_risk_warn';
+    if (pageType.value === target) return;
+    pageType.value = target;
+    configs.value = pageCache.value[target]?.configs || testBeltNew;
+    await nextTick();
+    await refresh();
+  }
+  // watch(
+  //   // 监听动态路由参数 :type
+  //   () => route.params.type,
+  //   (newVal) => {
+  //     if (newVal) {
+  //       console.log('切换页面类型:', newVal);
+  //       refresh(); // 切换路由自动刷新
+  //     }
+  //   }
+  // );
 
-function initModalAnimate(modal3D) {
-  modal = modal3D;
-  modal.isRender = true;
-  modalAnimate(modal, modalMonitorData);
-}
-function clearTimer() {
-  if (timer) {
-    clearInterval(timer);
-    timer = null;
+  function initModalAnimate(modal3D) {
+    modal = modal3D;
+    modal.isRender = true;
+    modalAnimate(modal, modalMonitorData);
   }
-}
-watch(
-  () => route.query.pageType,
-  (newQueryType) => {
-    if (newQueryType) {
-      changePage(newQueryType as string);
+  function clearTimer() {
+    if (timer) {
+      clearInterval(timer);
+      timer = null;
     }
-  },
-  { immediate: true } // 初始化立刻执行
-);
+  }
+  watch(
+    () => route.query.pageType,
+    (newQueryType) => {
+      if (newQueryType) {
+        changePage(newQueryType as string);
+      }
+    },
+    { immediate: true } // 初始化立刻执行
+  );
 
-watch(
-  () => modalMonitorData.value,
-  (newData, oldData) => {
-    if (newData && !Object.keys(oldData).length) {
-      isInitModal.value = true;
+  watch(
+    () => modalMonitorData.value,
+    (newData, oldData) => {
+      if (newData && !Object.keys(oldData).length) {
+        isInitModal.value = true;
+      }
     }
-  }
-);
+  );
 
-onMounted(async () => {
-  await getSysDataSource();
-  await refresh();
-  initInterval();
-});
-onUnmounted(() => {
-  clearTimer();
-  destroy(modal);
-  modal?.destroy();
-});
+  onMounted(async () => {
+    await getSysDataSource();
+    await refresh();
+    initInterval();
+  });
+  onUnmounted(() => {
+    clearTimer();
+    destroy(modal);
+    modal?.destroy();
+  });
 </script>
 <style lang="less" scoped>
-.company-home {
-  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
-  width: 100%;
-  height: 100%;
-  color: @white;
-  position: relative;
-  font-family: 'Microsoft YaHei', sans-serif;
-  .top-bg {
-    width: 100%;
-    height: 56px;
-    position: absolute;
-    margin-top: 10px;
-    z-index: 1;
-  }
-  .header-container {
-    position: absolute;
-    top: 20px;
-    left: 20px;
-    z-index: 10;
-  }
-
-  .border {
-    width: 100%;
-    height: 94%;
-    background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
-    background-size: 100% 100%;
-    position: relative;
-    width: 100%;
-  }
-}
-
-.center-warning-container {
-  position: absolute;
-  left: 50%;
-  transform: translateX(-50%);
-  top: 50%;
-  width: 600px;
-  height: 200px;
-  background-color: rgba(0, 0, 0, 0.7);
-  border-radius: 10px;
-  padding: 15px;
-  box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
-  z-index: 5;
-  color: #fff;
-
-  .warning-header {
-    font-size: 18px;
-    font-weight: bold;
-    margin-bottom: 10px;
-    color: #ff6b6b;
-  }
-
-  .warning-list {
+  .company-home {
+    background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
     width: 100%;
     height: 100%;
-    overflow-y: auto;
-    display: flex;
-    flex-direction: column;
-    gap: 8px;
-  }
-
-  .warning-item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 8px;
-    background-color: rgba(0, 0, 0, 0.5);
-    border-radius: 5px;
-    border-left: 4px solid #ff6b6b;
+    color: @white;
+    position: relative;
+    font-family: 'Microsoft YaHei', sans-serif;
+    .top-bg {
+      width: 100%;
+      height: 56px;
+      position: absolute;
+      margin-top: 10px;
+      z-index: 1;
+    }
+    .header-container {
+      position: absolute;
+      top: 20px;
+      left: 20px;
+      z-index: 10;
+    }
 
-    .warning-time {
-      font-size: 14px;
-      color: #ccc;
+    .border {
+      width: 100%;
+      height: 94%;
+      background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      width: 100%;
     }
   }
 
-  // 中间预警结果区
   .center-warning-container {
     position: absolute;
     left: 50%;
@@ -447,67 +401,113 @@ onUnmounted(() => {
         font-size: 14px;
         color: #ccc;
       }
+    }
 
-      .warning-level {
-        font-size: 14px;
+    // 中间预警结果区
+    .center-warning-container {
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      top: 50%;
+      width: 600px;
+      height: 200px;
+      background-color: rgba(0, 0, 0, 0.7);
+      border-radius: 10px;
+      padding: 15px;
+      box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
+      z-index: 5;
+      color: #fff;
+
+      .warning-header {
+        font-size: 18px;
         font-weight: bold;
-        padding: 4px 8px;
-        border-radius: 4px;
-        &.level-critical {
-          background-color: #ff6b6b;
-          color: white;
-        }
-        &.level-high {
-          background-color: #ffcc00;
-          color: black;
-        }
-        &.level-normal {
-          background-color: #66cc66;
-          color: white;
-        }
+        margin-bottom: 10px;
+        color: #ff6b6b;
       }
 
-      .warning-action {
-        .btn-start-spray {
-          background-color: #00e1ff;
-          color: #000;
-          border: none;
-          padding: 4px 10px;
+      .warning-list {
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+      }
+
+      .warning-item {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 8px;
+        background-color: rgba(0, 0, 0, 0.5);
+        border-radius: 5px;
+        border-left: 4px solid #ff6b6b;
+
+        .warning-time {
+          font-size: 14px;
+          color: #ccc;
+        }
+
+        .warning-level {
+          font-size: 14px;
+          font-weight: bold;
+          padding: 4px 8px;
           border-radius: 4px;
-          cursor: pointer;
-          font-size: 12px;
-          transition: all 0.3s;
-          &:hover {
-            background-color: #00c3e6;
+          &.level-critical {
+            background-color: #ff6b6b;
+            color: white;
+          }
+          &.level-high {
+            background-color: #ffcc00;
+            color: black;
+          }
+          &.level-normal {
+            background-color: #66cc66;
+            color: white;
+          }
+        }
+
+        .warning-action {
+          .btn-start-spray {
+            background-color: #00e1ff;
+            color: #000;
+            border: none;
+            padding: 4px 10px;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 12px;
+            transition: all 0.3s;
+            &:hover {
+              background-color: #00c3e6;
+            }
           }
         }
       }
     }
-  }
 
-  // 巷道示意图
-  .belt-diagram {
-    position: absolute;
-    left: 50%;
-    transform: translateX(-50%);
-    bottom: 50px;
-    width: 800px;
-    height: 100px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
+    // 巷道示意图
+    .belt-diagram {
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      bottom: 50px;
+      width: 800px;
+      height: 100px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
 
-    img {
-      width: 100%;
-      height: 100%;
-      object-fit: contain;
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
     }
   }
-}
-.modal-box {
-  width: 100%;
-  height: 100%;
-  position: absolute;
-  z-index: 1;
-}
+  .modal-box {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    z-index: 1;
+  }
 </style>

+ 117 - 117
src/views/vent/home/configurable/belt/belt.vue

@@ -23,140 +23,140 @@
   </div>
 </template>
 <script setup lang="ts">
-import { onMounted, onUnmounted, ref, nextTick } from 'vue';
-import customHeader from './components/customHeader-belt.vue';
-import { useInitConfigs, useInitPage } from '../hooks/useInit';
-import { testBeltLaneFire } from './configurable.data';
-import ModuleCommon from './components/ModuleCommon.vue';
-import SubApp from '/@/components/vent/micro/createSubApp.vue';
-import { getDataHome } from './configurable.api';
+  import { onMounted, onUnmounted, ref, nextTick } from 'vue';
+  import customHeader from './components/customHeader-belt.vue';
+  import { useInitConfigs, useInitPage } from '../hooks/useInit';
+  import { testBeltLaneFire } from './configurable.data';
+  import ModuleCommon from './components/ModuleCommon.vue';
+  import SubApp from '/@/components/vent/micro/createSubApp.vue';
+  import { getDataHome } from './configurable.api';
 
-const { configs, devicesTypes, fetchConfigs } = useInitConfigs();
-const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
+  const { configs, devicesTypes, fetchConfigs } = useInitConfigs();
+  const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
 
-const currentSelectedId = ref<string>('');
+  const currentSelectedId = ref<string>('');
 
-let timer = null;
-const showHistory = ref(false);
-// 接收子组件上报的点击事件,更新全局选中状态
-const handleItemClick = async (item) => {
-  const clickId = item.id;
-  if (!clickId) return;
-  currentSelectedId.value = clickId;
-  await nextTick();
-  refreshData();
-};
+  let timer = null;
+  const showHistory = ref(false);
+  // 接收子组件上报的点击事件,更新全局选中状态
+  const handleItemClick = async (item) => {
+    const clickId = item.id;
+    if (!clickId) return;
+    currentSelectedId.value = clickId;
+    await nextTick();
+    refreshData();
+  };
 
-// 数据筛选
-const filterDataById = (sourceData, clickId: string | number) => {
-  if (!sourceData || !clickId) return sourceData;
-  const id = String(clickId);
-  const monitor = sourceData.monitor_alert?.find((item) => String(item.sysId) === id);
-  const spray = sourceData.spray_control?.find((item) => String(item.sysId) === id);
-  const gate = sourceData.gate_control?.find((item) => String(item.sysId) === id);
-  return {
-    ...sourceData,
-    monitor_alert: monitor ? [monitor] : [],
-    spray_control: spray ? [spray] : [],
-    gate_control: gate ? [gate] : [],
+  // 数据筛选
+  const filterDataById = (sourceData, clickId: string | number) => {
+    if (!sourceData || !clickId) return sourceData;
+    const id = String(clickId);
+    const monitor = sourceData.monitor_alert?.find((item) => String(item.sysId) === id);
+    const spray = sourceData.spray_control?.find((item) => String(item.sysId) === id);
+    const gate = sourceData.gate_control?.find((item) => String(item.sysId) === id);
+    return {
+      ...sourceData,
+      monitor_alert: monitor ? [monitor] : [],
+      spray_control: spray ? [spray] : [],
+      gate_control: gate ? [gate] : [],
+    };
   };
-};
 
-function refresh() {
-  fetchConfigs('belt').then(() => {
-    if (!configs.value || configs.value.length === 0) {
-      configs.value = testBeltLaneFire;
-    }
-    refreshData();
-  });
-}
-function refreshData() {
-  const dataListStr = configs.value
-    .filter((e) => e.deviceType)
-    .map((e) => e.deviceType)
-    .join(',');
+  function refresh() {
+    fetchConfigs('belt').then(() => {
+      if (!configs.value || configs.value.length === 0) {
+        configs.value = testBeltLaneFire;
+      }
+      refreshData();
+    });
+  }
+  function refreshData() {
+    const dataListStr = configs.value
+      .filter((e) => e.deviceType)
+      .map((e) => e.deviceType)
+      .join(',');
 
-  getDataHome({ dataList: dataListStr }).then((res: any) => {
-    res.spray_control = [
-      {
-        systemName: '东翼胶带运输大巷',
-        sysId: '2028657172566073346',
-        sysList: [{ netstatus: '1', deviceStatus: '1', plsy: '1#区域 1.4MPa', kzms: '手动' }],
-      },
-      {
-        sysId: '2046500718274756609',
-        systemName: '1101胶带运输顺槽',
-        sysList: [{ netstatus: '1', deviceStatus: '1', plqy: '2#区域', plsy: '1.6MPa', kzms: '自动' }],
-      },
-    ];
+    getDataHome({ dataList: dataListStr }).then((res: any) => {
+      res.spray_control = [
+        {
+          systemName: '东翼胶带运输大巷',
+          sysId: '2028657172566073346',
+          sysList: [{ netstatus: '1', deviceStatus: '1', plsy: '1#区域 1.4MPa', kzms: '手动' }],
+        },
+        {
+          sysId: '2046500718274756609',
+          systemName: '1101胶带运输顺槽',
+          sysList: [{ netstatus: '1', deviceStatus: '1', plqy: '2#区域', plsy: '1.6MPa', kzms: '自动' }],
+        },
+      ];
 
-    let showData = res;
-    if (currentSelectedId.value) {
-      showData = filterDataById(res, currentSelectedId.value);
-    } else {
-      const firstId = res.monitor_alert?.[0]?.sysId;
-      if (firstId) {
-        currentSelectedId.value = firstId;
-        showData = filterDataById(res, firstId);
+      let showData = res;
+      if (currentSelectedId.value) {
+        showData = filterDataById(res, currentSelectedId.value);
+      } else {
+        const firstId = res.monitor_alert?.[0]?.sysId;
+        if (firstId) {
+          currentSelectedId.value = firstId;
+          showData = filterDataById(res, firstId);
+        }
       }
-    }
-    updateData(showData);
-  });
-}
-// 轮询
-function initInterval() {
-  if (timer) clearInterval(timer);
-  timer = setInterval(() => {
+      updateData(showData);
+    });
+  }
+  // 轮询
+  function initInterval() {
+    if (timer) clearInterval(timer);
+    timer = setInterval(() => {
+      refresh();
+    }, 5000);
+  }
+  onMounted(() => {
     refresh();
-  }, 5000);
-}
-onMounted(() => {
-  refresh();
-  initInterval();
-});
+    initInterval();
+  });
 
-onUnmounted(() => {
-  clearInterval(timer);
-  timer = null;
-});
+  onUnmounted(() => {
+    clearInterval(timer);
+    timer = null;
+  });
 </script>
 <style lang="less" scoped>
-.spray-wrapper {
-  width: 100%;
-  height: 100%;
-  background-image: url('/@/assets/images/beltFire/baseMap.png');
-  background-size: cover;
-}
+  .spray-wrapper {
+    width: 100%;
+    height: 100%;
+    background-image: url('/@/assets/images/beltFire/baseMap.png');
+    background-size: cover;
+  }
 
-#spray3D {
-  pointer-events: all;
-}
+  #spray3D {
+    pointer-events: all;
+  }
 
-.spray-wrapper :deep(.list-item_L .list-item__icon_L) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-icon-file.png');
-}
-.spray-wrapper :deep(.list-item_N:nth-child(1)) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n5.png');
-}
-.spray-wrapper :deep(.list-item_N:nth-child(2)) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n6.png');
-}
-.company-home {
-  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
-  width: 100%;
-  height: 100%;
-  color: @white;
-  position: relative;
-  .border {
+  .spray-wrapper :deep(.list-item_L .list-item__icon_L) {
+    background-image: url('/@/assets/images/home-container/configurable/minehome/list-icon-file.png');
+  }
+  .spray-wrapper :deep(.list-item_N:nth-child(1)) {
+    background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n5.png');
+  }
+  .spray-wrapper :deep(.list-item_N:nth-child(2)) {
+    background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n6.png');
+  }
+  .company-home {
+    background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
     width: 100%;
-    height: 94%;
-    background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
-    background-size: 100% 100%;
-    margin-top: 50px;
-    .test {
-      background: url('./test.png') no-repeat;
+    height: 100%;
+    color: @white;
+    position: relative;
+    .border {
+      width: 100%;
+      height: 94%;
+      background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
       background-size: 100% 100%;
+      margin-top: 50px;
+      .test {
+        background: url('./test.png') no-repeat;
+        background-size: 100% 100%;
+      }
     }
   }
-}
 </style>

+ 26 - 19
src/views/vent/home/configurable/belt/components/detail/fireGateBoard.vue

@@ -42,7 +42,8 @@
               <gateSVG :ref="(el) => setChildRef(el, index)" :identify="String(index)" />
             </div> -->
             <div class="model-placeholder">
-              <img :src="gatePng" alt="风门" class="model-img" />
+              <!-- <img :src="gatePng" alt="风门" class="model-img" /> -->
+              <pidaihangSVG :ref="(el) => setChildRef(el, index)" :identify="String(index)" />
             </div>
           </div>
         </div>
@@ -62,9 +63,7 @@
 <script setup lang="ts">
   import { ref, onMounted, watch, inject } from 'vue';
   import { getFormattedText } from '../../../hooks/helper';
-  // import gateSVG from '../gateSVG.vue';
-  import gateSVG from '../../../../../monitorManager/gateMonitor/components/gateDualSVG.vue';
-  import gatePng from '/@/assets/images/fireDoorMonitor.png'; //暂时用图片
+  import pidaihangSVG from '/@/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue';
   import { nextTick } from 'process';
   import HandleModal from '/@/views/vent/monitorManager/gateMonitor/modal.vue';
   import { deviceControlApi } from '/@/api/vent/index';
@@ -97,23 +96,18 @@
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
   const modalType = ref(''); // 模态框内容显示类型,设备操作类型
   const paramcode = ref(''); // 模态框操作代码
-  const childRefs = ref<(InstanceType<typeof gateSVG> | null)[]>([]);
+  const childRefs = ref<(InstanceType<typeof pidaihangSVG> | null)[]>([]);
   const selectData = ref<any>({});
   const setChildRef = (el, index) => {
     if (el) {
       childRefs.value[index] = el;
     }
   };
-  function yjControl() {
-    console.log('应急控制');
-  }
   function monitorAnimation(selectData, index) {
     if (!selectData?.readData) return;
-    const frontOpen = selectData.readData.frontGateOpen === '1';
-    const midOpen = selectData.readData.midGateOpen === '1';
-    const rearOpen = selectData.readData.rearGateOpen === '1';
+    const frontOpen = selectData.readData?.frontGateOpen?.value === '1';
     if (childRefs.value[index]) {
-      childRefs.value[index].animate(frontOpen, midOpen, rearOpen);
+      childRefs.value[index].animate(frontOpen, frontOpen, frontOpen);
     }
   }
 
@@ -122,18 +116,12 @@
     modalTitle.value = title;
     paramcode.value = code;
     openGateControlModal();
-    // if (childRefs.value[index]) {
-    //   childRefs.value[index].animate(true, true, true);
-    // }
   }
   function oneKeyClose(item, index, code, title) {
     selectData.value = item;
     modalTitle.value = title;
     paramcode.value = code;
     openGateControlModal();
-    // if (childRefs.value[index]) {
-    //   childRefs.value[index].animate(false, false, false);
-    // }
   }
 
   function handleOK(passWord, handlerState, value?) {
@@ -150,7 +138,6 @@
       value: '',
       password: passWord || globalConfig?.simulatedPassword,
     };
-    console.log('data', data);
 
     doorControlApi(data)
       .then((res) => {
@@ -162,6 +149,14 @@
           } else {
             message.success('指令已下发成功!');
           }
+
+          // 找到对应的索引并更新动画状态
+          const targetIndex = props.data.findIndex((item) => item.deviceID === selectData.value.deviceID);
+          if (targetIndex !== -1 && childRefs.value[targetIndex]) {
+            // 根据操作类型设置动画状态
+            const isOpen = paramcode.value.includes('Open');
+            childRefs.value[targetIndex].animate(isOpen, isOpen, isOpen);
+          }
           // 触发刷新事件
           emit('refresh');
         } else {
@@ -410,6 +405,18 @@
     display: flex;
     align-items: center;
     justify-content: center;
+    position: relative;
+    overflow: hidden;
+  }
+  svg {
+    width: 350%;
+    height: 350%;
+    max-width: none;
+    max-height: none;
+    object-fit: contain;
+    position: absolute;
+    top: -140%;
+    left: -110%;
   }
   .model-img {
     width: 100%;

+ 12 - 4
src/views/vent/home/configurable/belt/configurable.api.ts

@@ -1,8 +1,4 @@
-import { floor, isArray, random, slice } from 'lodash-es';
 import { defHttp } from '/@/utils/http/axios';
-import { get } from '../../billboard/utils';
-import { useGlobSetting } from '/@/hooks/setting';
-import { reactive } from 'vue';
 import _ from 'lodash';
 
 enum Api {
@@ -21,6 +17,14 @@ enum Api {
   getWarnInfo = '/ventanaly-device/monitor/disaster/findAlarmsBySystemAndMonitorType',
   // 获取人员定位信息
   findStaffInfoBySystem = '/ventanaly-device/monitor/disaster/findStaffInfoBySystem',
+  // 获取皮带巷信息
+  getLeatherInfo = '/modelreq/safety/ventanalyManageSystem/list',
+  // 操作历史
+  baseList = '/safety/ventanalyGate/list',
+  // 设备历史
+  deviceHistory = '/modelreq/safety/ventanalyMonitorData/listdays',
+  // 获取皮带巷下设备
+  getDeviceData = '/ventanaly-device/safety/managesysAlarmInfo/getDevicesInfoBySysId',
 }
 export const getSystem = (params) => defHttp.post({ url: Api.getSystem, params });
 export const getMonitorAndAlertBelt = (params) => defHttp.post({ url: Api.monitorAndAlertBelt, params });
@@ -31,3 +35,7 @@ export const getDevice = (params) => defHttp.post({ url: Api.getDevice, params }
 export const getWarnResult = (params) => defHttp.post({ url: Api.getWarnResult, params });
 export const getWarnInfo = (params) => defHttp.post({ url: Api.getWarnInfo, params });
 export const getStaffInfo = (params) => defHttp.post({ url: Api.findStaffInfoBySystem, params });
+export const getLeatherInfo = (params) => defHttp.get({ url: Api.getLeatherInfo, params });
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });
+export const getDeviceHistoryApi = (params) => defHttp.get({ url: Api.deviceHistory, params });
+export const getDeviceData = (params) => defHttp.post({ url: Api.getDeviceData, params });

+ 190 - 102
src/views/vent/home/configurable/belt/history.vue

@@ -2,155 +2,243 @@
   <div class="company-home">
     <div class="border">
       <customHeader>矿井全域皮带巷三级防灭火系统</customHeader>
-      <SubApp />
+      <!-- Tab 切换 -->
+      <div class="tab-box">
+        <div class="tab-item" :class="{ active: activeTab === 'device' }" @click="switchTab('device')"> 设备历史数据 </div>
+        <div class="tab-item" :class="{ active: activeTab === 'operate' }" @click="switchTab('operate')"> 操作历史 </div>
+      </div>
+
       <div class="box-container">
-        <BasicTable ref="historyTable" @register="registerTable" :scroll="{ x: 1000, y: 500 }" />
+        <!-- 设备历史 -->
+        <div v-if="activeTab === 'device'" key="device">
+          <BasicTable ref="historyTable" @register="registerTable" :data-source="dataSource" :scroll="{ x: 1000, y: 500 }" />
+        </div>
+
+        <!-- 操作历史 -->
+        <div v-if="activeTab === 'operate'" key="operate">
+          <BasicTable ref="operateTable" @register="registerOperateTable" :data-source="ctrlSource" :scroll="{ x: 1000, y: 500 }" />
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, onMounted, nextTick } from 'vue';
 import { BasicTable } from '/@/components/Table';
 import { useListPage } from '/@/hooks/system/useListPage';
-import { defHttp } from '/@/utils/http/axios';
 import dayjs from 'dayjs';
 import customHeader from './components/customHeader-belt.vue';
-// 表格列配置(你可以随便加字段)
-const columns = ref([
+import { getLeatherInfo, getTableList, getDeviceHistoryApi, getDeviceData } from './configurable.api';
+
+const activeTab = ref('device');
+// 设备下拉选项
+const deviceOptions = ref<any>([]);
+const deviceDataOptions = ref<any>([]);
+const dataSource = ref([]);
+const ctrlSource = ref([]);
+// 工具方法
+function resetFormParam() {
+  const formData = getForm().getFieldsValue();
+  const pagination = getPaginationRef();
+  return {
+    pageNum: pagination.current,
+    pageSize: pagination.pageSize,
+    column: 'createTime',
+    startTime: formData.startTime,
+    endTime: formData.endTime,
+    deviceId: formData.gdeviceids,
+    strtype: 'sys_Leather',
+    sysId: formData.id,
+  };
+}
+
+// 获取皮带巷列表
+async function getDeviceList() {
+  const res = await getLeatherInfo({
+    strtype: 'sys_Leather',
+    devicekind: 'managesys',
+    pageNo: 1,
+    pageSize: 999,
+  });
+  deviceOptions.value = res.records.map((item) => ({
+    label: item.systemname,
+    value: item.id,
+  }));
+}
+// 获取皮带巷下设备信息
+async function getDevice(id) {
+  const res = await getDeviceData({ sysId: id });
+  deviceDataOptions.value = res.map((item) => ({
+    label: item.deviceName,
+    value: item.deviceId,
+  }));
+}
+const getSchemas = () => [
   {
-    title: '设备名称',
-    dataIndex: 'deviceName',
-    width: 180,
+    field: 'startTime',
+    label: '开始时间',
+    component: 'DatePicker',
+    defaultValue: dayjs().subtract(1, 'day').startOf('day'),
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    colProps: { span: 4 },
   },
   {
-    title: '设备位置',
-    dataIndex: 'devicePos',
-    width: 180,
+    field: 'endTime',
+    label: '结束时间',
+    component: 'DatePicker',
+    defaultValue: dayjs(),
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    colProps: { span: 4 },
   },
   {
-    title: '监测值',
-    dataIndex: 'monitorValue',
-    width: 120,
+    field: 'id',
+    label: '皮带巷',
+    component: 'Select',
+    componentProps: {
+      placeholder: '请选择皮带巷',
+      showSearch: true,
+      options: deviceOptions,
+      onChange: (e, option) => {
+        getDevice(option.value);
+      },
+    },
+    required: true,
+    colProps: { span: 4 },
   },
   {
-    title: '采集时间',
-    dataIndex: 'createTime',
-    width: 200,
+    field: 'gdeviceids',
+    label: '设备名称',
+    component: 'Select',
+    componentProps: {
+      placeholder: '请选择设备',
+      showSearch: true,
+      options: deviceDataOptions,
+      onChange: (e, option) => {
+        getHistoryData();
+      },
+    },
+    required: true,
+    colProps: { span: 4 },
   },
+];
+
+//设备历史
+const deviceColumns = ref([
+  { title: '设备名称', dataIndex: 'gdevicename', width: 180 },
+  { title: '设备位置', dataIndex: 'devicePos', width: 180 },
+  { title: '监测值', dataIndex: 'monitorValue', width: 120 },
+  { title: '采集时间', dataIndex: 'gcreatetime', width: 200 },
 ]);
 
-// 查询接口 模拟
-const getHistoryDataApi = (params) =>
-  defHttp.post({
-    url: '/monitor/device/history',
-    params,
-  });
+async function getHistoryData() {
+  const params = resetFormParam();
+  const res = await getDeviceHistoryApi(params);
+  dataSource.value = res.records;
+}
 
-// 表格注册
-const { tableContext } = useListPage({
+const { tableContext: deviceTableContext } = useListPage({
   tableProps: {
-    api: getHistoryDataApi, // 绑定查询接口
-    columns: columns.value, // 绑定表格列
+    api: async (params) => {
+      const res = await getDeviceHistoryApi(params);
+      return {
+        total: res.total || 0,
+        items: res.records || [],
+      };
+    },
+    columns: deviceColumns.value,
     showIndexColumn: true,
     bordered: true,
     size: 'small',
-
-    // 查询表单配置
     formConfig: {
       labelWidth: 80,
-      showSubmitButton: true, // 显示查询按钮
-      showResetButton: true, // 显示重置按钮
-      schemas: [
-        // 时间范围
-        {
-          field: 'beginTime',
-          label: '开始时间',
-          component: 'DatePicker',
-          defaultValue: dayjs().subtract(1, 'day').startOf('day'),
-          componentProps: {
-            showTime: true,
-            valueFormat: 'YYYY-MM-DD HH:mm:ss',
-          },
-          colProps: { span: 6 },
-        },
-        {
-          field: 'endTime',
-          label: '结束时间',
-          component: 'DatePicker',
-          defaultValue: dayjs(),
-          componentProps: {
-            showTime: true,
-            valueFormat: 'YYYY-MM-DD HH:mm:ss',
-          },
-          colProps: { span: 6 },
-        },
-        {
-          field: 'deviceName',
-          label: '皮带巷',
-          component: 'Select',
-          componentProps: { placeholder: '请选择皮带巷' },
-          colProps: { span: 6 },
-        },
-        // 设备名称搜索
-        {
-          field: 'deviceName',
-          label: '设备名称',
-          component: 'Select',
-          componentProps: { placeholder: '请选择设备' },
-          colProps: { span: 6 },
-        },
-      ],
+      schemas: getSchemas(),
+      showAdvancedButton: false,
+      autoAdvancedCol: 99,
+      alwaysShowLines: 99,
     },
+    pagination: { pageSize: 20 },
+  },
+});
+const [registerTable, { getForm, getPaginationRef }] = deviceTableContext;
+
+//操作历史
+const operateColumns = ref([
+  { title: '操作人', dataIndex: 'operateUser', width: 160 },
+  { title: '操作内容', dataIndex: 'operateContent', width: 300 },
+  { title: '操作时间', dataIndex: 'operateTime', width: 200 },
+  { title: '设备/位置', dataIndex: 'deviceName', width: 180 },
+]);
 
-    // 分页
-    pagination: {
-      pageSize: 20,
-      showSizeChanger: false,
+const { tableContext: operateTableContext } = useListPage({
+  tableProps: {
+    api: getTableList,
+    columns: operateColumns.value,
+    showIndexColumn: true,
+    bordered: true,
+    size: 'small',
+    formConfig: {
+      labelWidth: 80,
+      schemas: getSchemas(),
+      showAdvancedButton: false,
+      autoAdvancedCol: 99,
+      alwaysShowLines: 99,
     },
+    pagination: { pageSize: 20 },
   },
 });
+const [registerOperateTable] = operateTableContext;
+
+//Tab 切换
+function switchTab(tab) {
+  activeTab.value = tab;
+}
 
-const [registerTable] = tableContext;
+//初始化
+onMounted(() => {
+  getDeviceList();
+});
 </script>
 
 <style scoped lang="less">
-.spray-wrapper {
+.company-home {
   width: 100%;
   height: 100%;
-  background-image: url('/@/assets/images/beltFire/baseMap.png');
+  color: #fff;
+  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
   background-size: cover;
-}
-
-#spray3D {
-  pointer-events: all;
-}
 
-.spray-wrapper :deep(.list-item_L .list-item__icon_L) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-icon-file.png');
-}
-.spray-wrapper :deep(.list-item_N:nth-child(1)) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n5.png');
-}
-.spray-wrapper :deep(.list-item_N:nth-child(2)) {
-  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n6.png');
-}
-.company-home {
-  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
-  width: 100%;
-  height: 100%;
-  color: @white;
-  position: relative;
   .border {
     width: 100%;
     height: 94%;
     background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
     background-size: 100% 100%;
+    padding: 20px;
+    box-sizing: border-box;
     margin-top: 50px;
-    .test {
-      background: url('./test.png') no-repeat;
-      background-size: 100% 100%;
+  }
+
+  .tab-box {
+    display: flex;
+    gap: 4px;
+    margin-bottom: 16px;
+
+    .tab-item {
+      padding: 8px 20px;
+      cursor: pointer;
+      border-radius: 6px;
+
+      &.active {
+        background: #1890ff;
+        color: #fff;
+        font-weight: bold;
+      }
     }
   }
 }

+ 25 - 18
src/views/vent/home/configurable/belt/threejs/belt.threejs.ts

@@ -28,9 +28,9 @@ const blinkAnimationList: any[] = [];
 export async function modalAnimate(modal, modalMonitorData: Ref<any, any>) {
   // const data = modalMonitorData.value;
   const warningPartition: THREE.Object3D[] = [];
+  modal.scene.updateMatrixWorld(true);
 
   const beltModal = modal.scene.getObjectByName('pidai').getObjectByName('PiDaiHang_1');
-
   // 重置模型参数
   resetModalParam(modal);
   // 添加光源
@@ -41,16 +41,13 @@ export async function modalAnimate(modal, modalMonitorData: Ref<any, any>) {
   // 绘制风流路径
   const { jinfenghangPath, huifenghangPath, jinfenglianhangPath, huifenglianhangPath, pidaihangPath } = getTunPath(beltModal);
 
-  // const api = '/ventanaly-device/monitor/disaster/findDeviceInfoBySystem';
-  // const res = await defHttp.post({ url: api, params: { sysId: '2028657172566073346' } });
-  // modalData.value = res?.device;
-  // const len = Object.keys(res?.device).length;
-  const { partitionList } = drawPartition(beltModal, 100, 2000);
-
-  const { sprayPartitionList } = drawSprayPartition(beltModal, 100, 2000);
+  const api = '/ventanaly-device/monitor/disaster/findDeviceInfoBySystem';
+  const res = await defHttp.post({ url: api, params: { sysId: '2028657172566073346' } });
+  modalData.value = res?.device;
+  const len = Object.keys(res?.device).length;
+  const { partitionList } = drawPartition(beltModal, 100, len * 100);
 
-  modal.orbitControls?.update();
-  modal.camera?.updateMatrixWorld();
+  const { sprayPartitionList } = drawSprayPartition(beltModal, 100, len * 100);
 
   watch(
     modalMonitorData,
@@ -416,12 +413,12 @@ function updateMonitorPanel3D() {
 function updateSprayPanel3D(modal, sprayPartitionList) {
   for (let i = 0; i < sprayList.value.length; i++) {
     const spray = sprayList.value[i];
-    if (spray['deviceStatus'] == 1) {
+    if (spray['deviceStatus'] == 1 || i == 0 || i == 1 || i == 2 || i == 3 || i == 4 || i == 5) {
       // 喷淋开启
       if (!panelManager.getPanel(spray['deviceID']) && spray['strinstallpos']) {
         const arr = ((spray['strinstallpos'] as string) || '').split('#');
         const index = Number(arr[0]);
-        const sprayPartition = sprayPartitionList[index];
+        const sprayPartition = sprayPartitionList[index - 1];
         const box = new THREE.Box3();
         box.setFromObject(sprayPartition);
         const center = box.getCenter(new THREE.Vector3());
@@ -658,6 +655,7 @@ function getTunPath(beltModal) {
             point.getWorldPosition(pos);
             pos.applyMatrix4(beltModal.matrixWorld.clone().invert());
             posList.push(pos);
+            console.log('获取管道点', point.name, pos);
             point.visible = false;
           } else {
             break;
@@ -789,14 +787,18 @@ function handlePathAnimateByVisible(
 // 根据皮带绘制分区
 function drawPartition(beltModal: THREE.Group, partitionLen: number, len: number) {
   const penlin = beltModal.getObjectByName('penlin') as THREE.Object3D;
-  const group1 = penlin?.getObjectByName('qiang8');
+
+  const group1 = penlin?.getObjectByName('qiang8') as THREE.Object3D;
+  const group2 = group1?.clone();
+
   const partitionList: THREE.Object3D[] = [];
 
   if (group1) {
     const box = new THREE.Box3();
-    box.setFromObject(group1);
+    box.setFromObject(group2);
     const min = box.min;
     const max = box.max;
+    console.log('box', box, group2);
 
     // 为皮带巷进行分区
     const modalLen = max.x - min.x;
@@ -824,6 +826,10 @@ function drawPartition(beltModal: THREE.Group, partitionLen: number, len: number
         new THREE.Vector3(maxLen, max.y, max.z),
         new THREE.Vector3(minLen, max.y, max.z),
       ];
+
+      // for (let j = 0; j < vertices.length; j++) {
+      //   vertices[0].applyMatrix4(penlin.matrixWorld);
+      // }
       // 绘制正方体
       const partitionObject3D = drawPartitionByBox(penlin, vertices, partitionNum - i);
       partitionList.push(partitionObject3D);
@@ -949,11 +955,12 @@ function drawPartitionByBox(group: THREE.Object3D, vertices: THREE.Vector3[], in
 // 根据皮带绘制分区
 function drawSprayPartition(beltModal: THREE.Group, partitionLen: number, len: number) {
   const penlin = beltModal.getObjectByName('penlin') as THREE.Object3D;
-  const group1 = penlin?.getObjectByName('qiang8');
+  const group1 = penlin?.getObjectByName('qiang8') as THREE.Object3D;
+  const group2 = group1?.clone();
   const sprayPartitionList: THREE.Object3D[] = [];
   if (group1) {
     const box = new THREE.Box3();
-    box.setFromObject(group1);
+    box.setFromObject(group2);
     const min = box.min;
     const max = box.max;
 
@@ -984,8 +991,8 @@ function drawSprayPartition(beltModal: THREE.Group, partitionLen: number, len: n
         new THREE.Vector3(minLen, max.y, max.z),
       ];
       // 绘制正方体
-      const partitionObject3D = drawSprayPartitionByBox(penlin, vertices, partitionNum - i);
-      sprayPartitionList.push(partitionObject3D);
+      const partitionObject3D = drawSprayPartitionByBox(penlin, vertices, partitionNum - i - 1);
+      sprayPartitionList[partitionNum - i - 1] = partitionObject3D;
     }
   }
 

+ 41 - 2
src/views/vent/home/configurable/components/belt/ComplexList1Belt.vue

@@ -1,6 +1,21 @@
 <template>
   <div class="list flex items-center" :class="`list_${type}`">
     <div class="flex-grow" :class="`list_wrapper_${type}`">
+      <!-- 全矿井 固定在最顶部 -->
+      <div v-if="showAllMineItem" class="list-item" :class="`list-item_${type}`">
+        <div
+          style="cursor: pointer"
+          @click="handleItemClick(allMineItem)"
+          :class="[`list-item__content_${type}`, getBgClass(allMineItem.value), { active: allMineItem.id === activeId }]"
+        >
+          <div class="list-item__label">全矿井</div>
+          <div class="list-item__value" :class="`list-item__value_${type}`">
+            {{ getAlertName(allMineItem.value) }}
+          </div>
+        </div>
+      </div>
+
+      <!-- 原列表 -->
       <div v-for="(item, i) in listConfig" :key="`customlist${i}`" class="list-item" :class="`list-item_${type}`">
         <div
           v-for="(ctx, j) in item.contents"
@@ -10,7 +25,9 @@
           :class="[`list-item__content_${type}`, getBgClass(ctx.value), { active: ctx.id === activeId }]"
         >
           <div class="list-item__label"> {{ ctx.label }}</div>
-          <div class="list-item__value" :class="`list-item__value_${type}`"> {{ getAlertName(ctx.value) }} </div>
+          <div class="list-item__value" :class="`list-item__value_${type}`">
+            {{ getAlertName(ctx.value) }}
+          </div>
         </div>
       </div>
     </div>
@@ -18,7 +35,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref } from 'vue';
+import { onMounted, ref, computed } from 'vue';
 import { getFormattedText } from '../../hooks/helper';
 
 const props = withDefaults(
@@ -57,6 +74,27 @@ const alarmMap: Record<string, string> = {
   '104': '红色预警',
 };
 
+// 获取第一条真实数据
+const firstContent = computed(() => {
+  if (!props.listConfig.length) return null;
+  const firstItem = props.listConfig[0];
+  if (!firstItem.contents?.length) return null;
+  return firstItem.contents[0];
+});
+
+// 全矿井项(自动继承第一条数据的 value)
+const allMineItem = computed(() => ({
+  id: 'allMine',
+  label: '全矿井',
+  value: firstContent.value?.value || '0',
+  color: firstContent.value?.color,
+  info: firstContent.value?.info,
+}));
+
+// 是否显示全矿井(有数据才显示)
+const showAllMineItem = computed(() => !!firstContent.value);
+// ==============================================
+
 // 获取背景样式
 const getBgClass = (riskLevel: string) => {
   // 统一处理 null  0
@@ -89,6 +127,7 @@ const getAlertName = (riskLevel: string | number | null) => {
   const key = String(riskLevel);
   return alarmMap[key] || '正常';
 };
+
 onMounted(() => {});
 </script>
 

+ 20 - 21
src/views/vent/home/configurable/fireDoorMonitor.vue

@@ -61,29 +61,28 @@
   }
 
   onMounted(() => {
-    // 1. 获取场景列表
-    manageFireList({
-      strtype: 'sys_door',
-      pagetype: 'normal',
-    }).then((res) => {
-      if (res.records && res.records.length > 0) {
-        res.records.forEach((item) => {
-          selectorOptions.value.push({
-            value: item.id,
-            label: item.systemname,
+    // 1. 先加载页面配置
+    fetchConfigs('fireMonitor').then(() => {
+      // updateEnhancedConfigs(configs.value);
+      // 2. 获取场景列表
+      manageFireList({
+        strtype: 'sys_door',
+        pagetype: 'normal',
+      }).then((res) => {
+        if (res.records && res.records.length > 0) {
+          res.records.forEach((item) => {
+            selectorOptions.value.push({
+              value: item.id,
+              label: item.systemname,
+            });
           });
-        });
-
-        // 2. 默认选中第一个场景
-        deviceId.value = selectorOptions.value[0].value;
 
-        // 3. 初始化页面配置并加载第一个场景的数据
-        fetchConfigs('fireMonitor').then(() => {
-          configs.value = testFireDoorMonitor;
-          updateEnhancedConfigs(configs.value);
-          loadFireGateData();
-        });
-      }
+          // 3. 默认选中第一个场景
+          deviceId.value = selectorOptions.value[0].value;
+        }
+        // 加载数据
+        loadFireGateData();
+      });
     });
   });
 

+ 2 - 1
src/views/vent/home/configurable/hooks/helper.ts

@@ -1,4 +1,5 @@
-import { get, isEmpty, isNil } from 'lodash-es';
+import { get, isNil } from 'lodash-es';
+import { isEmpty } from '/@/utils/is';
 
 /** 根据配置中的 formatter 将文本格式并返回 */
 export function getFormattedText(data: any, formatter: string, trans?: Record<string, string>, defaultValue?: any): string {

+ 639 - 569
src/views/vent/monitorManager/comment/HistoryTable.vue

@@ -47,632 +47,702 @@
 </template>
 
 <script lang="ts" setup>
-  //ts语法
-  import { watchEffect, ref, watch, defineExpose, inject, nextTick, onMounted, computed } from 'vue';
-  import { FormSchema } from '/@/components/Form/index';
-  import { BasicTable } from '/@/components/Table';
-  import { useListPage } from '/@/hooks/system/useListPage';
-  import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
-  import { defHttp } from '/@/utils/http/axios';
-  import dayjs from 'dayjs';
-  import { getAutoScrollContainer } from '/@/utils/common/compUtils';
-  import { render } from '/@/utils/common/renderUtils';
-  import { useMethods } from '/@/hooks/system/useMethods';
-  import BarAndLine from '/@/components/chart/BarAndLine.vue';
-  import { getDictItemsByCode } from '/@/utils/dict';
-  import { get } from 'lodash-es';
-
-  const globalConfig = inject('globalConfig');
-  const props = defineProps({
-    columnsType: {
-      type: String,
-    },
-    columns: {
-      type: Array,
-      // required: true,
-      default: () => [],
-    },
-    deviceType: {
-      type: String,
-      required: true,
-    },
-    deviceListApi: {
-      type: Function,
-    },
-    deviceArr: {
-      type: Array,
-      // required: true,
-      default: () => [],
-    },
-    designScope: {
-      type: String,
-    },
-    sysId: {
-      type: String,
-    },
-    deviceId: {
-      type: String,
-    },
-    scroll: {
-      type: Object,
-      default: { y: 0 },
-    },
-    formSchemas: {
-      type: Array<FormSchema>,
-      default: () => [],
-    },
-    /** 仅展示已绑定设备,选择是则从系统中获取sysId下已绑定设备。仅能查询到已绑定设备的历史数据 */
-    onlyBounedDevices: {
-      type: Boolean,
-      default: false,
-    },
-    showHistoryCurve: {
-      type: Boolean,
-      default: false,
-    },
+//ts语法
+import { watchEffect, ref, watch, defineExpose, inject, nextTick, onMounted, computed } from 'vue';
+import { FormSchema } from '/@/components/Form/index';
+import { BasicTable } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
+import { defHttp } from '/@/utils/http/axios';
+import dayjs from 'dayjs';
+import { getAutoScrollContainer } from '/@/utils/common/compUtils';
+import { render } from '/@/utils/common/renderUtils';
+import { useMethods } from '/@/hooks/system/useMethods';
+import BarAndLine from '/@/components/chart/BarAndLine.vue';
+import { getDictItemsByCode } from '/@/utils/dict';
+import { get } from 'lodash-es';
+import { usePermission } from '/@/hooks/web/usePermission';
+const { hasPermission } = usePermission();
+const globalConfig = inject('globalConfig');
+const props = defineProps({
+  columnsType: {
+    type: String,
+  },
+  columns: {
+    type: Array,
+    // required: true,
+    default: () => [],
+  },
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceListApi: {
+    type: Function,
+  },
+  deviceArr: {
+    type: Array,
+    // required: true,
+    default: () => [],
+  },
+  designScope: {
+    type: String,
+  },
+  sysId: {
+    type: String,
+  },
+  deviceId: {
+    type: String,
+  },
+  scroll: {
+    type: Object,
+    default: { y: 0 },
+  },
+  formSchemas: {
+    type: Array<FormSchema>,
+    default: () => [],
+  },
+  /** 仅展示已绑定设备,选择是则从系统中获取sysId下已绑定设备。仅能查询到已绑定设备的历史数据 */
+  onlyBounedDevices: {
+    type: Boolean,
+    default: false,
+  },
+  showHistoryCurve: {
+    type: Boolean,
+    default: false,
+  },
+});
+const getDeviceListApi = (params) => defHttp.post({ url: '/monitor/device', params });
+const historyTable = ref();
+const loading = ref(false);
+const stationType = ref('plc1');
+const dataSource = ref([]);
+const intervalMap = new Map([
+  ['1', '1s'],
+  ['2', '5s'],
+  ['3', '10s'],
+  ['4', '30s'],
+  ['5', '1m'],
+  ['6', '10m'],
+  ['7', '30m'],
+  ['8', '1h'],
+  ['9', '1d'],
+]);
+const getExportXlsUrl = () => {
+  if (stationType.value !== 'redis') {
+    return '/safety/ventanalyMonitorData/export/historydata';
+  } else {
+    return '/monitor/history/exportHistoryData';
+  }
+};
+const emit = defineEmits(['change']);
+const hasMultipleChoice = hasPermission('windrect:hasMultipleChoice');
+const historyType = ref('');
+const deviceKide = ref('');
+const columns = ref([]);
+let deviceOptions = ref([]);
+const deviceTypeStr = ref('');
+const deviceTypeName = ref('');
+const deviceType = ref('');
+const chartsColumns = ref([]);
+loading.value = true;
+
+const selectedOption = computed<Record<string, any> | undefined>(() => {
+  let idval: string | undefined = getForm()?.getFieldsValue()?.gdeviceids;
+  if (VENT_PARAM.historyIsMultiple && idval) {
+    const arr = idval.split(',');
+    idval = arr[arr.length - 1];
+  }
+  return deviceOptions.value.find((e: any) => {
+    return e.value === idval;
   });
-  const getDeviceListApi = (params) => defHttp.post({ url: '/monitor/device', params });
-  const historyTable = ref();
-  const loading = ref(false);
-  const stationType = ref('plc1');
-  const dataSource = ref([]);
-  const intervalMap = new Map([
-    ['1', '1s'],
-    ['2', '5s'],
-    ['3', '10s'],
-    ['4', '30s'],
-    ['5', '1m'],
-    ['6', '10m'],
-    ['7', '30m'],
-    ['8', '1h'],
-    ['9', '1d'],
-  ]);
-
-  const getExportXlsUrl = () => {
-    if (stationType.value !== 'redis') {
-      return '/safety/ventanalyMonitorData/export/historydata';
-    } else {
-      return '/monitor/history/exportHistoryData';
+});
+
+watch(
+  () => {
+    return props.columnsType;
+  },
+  async (newVal) => {
+    if (!newVal) return;
+    deviceKide.value = newVal;
+    if (historyTable.value) {
+      getForm().resetFields();
+      // getForm().updateSchema();
+      // getForm();
     }
-  };
-  const emit = defineEmits(['change']);
-
-  const historyType = ref('');
-  const deviceKide = ref('');
-  const columns = ref([]);
-  let deviceOptions = ref([]);
-  const deviceTypeStr = ref('');
-  const deviceTypeName = ref('');
-  const deviceType = ref('');
-  const chartsColumns = ref([]);
-  loading.value = true;
-
-  const selectedOption = computed<Record<string, any> | undefined>(() => {
-    let idval: string | undefined = getForm()?.getFieldsValue()?.gdeviceids;
-    if (VENT_PARAM.historyIsMultiple && idval) {
-      const arr = idval.split(',');
-      idval = arr[arr.length - 1];
-    }
-    return deviceOptions.value.find((e: any) => {
-      return e.value === idval;
+    dataSource.value = [];
+    // const column = getTableHeaderColumns(newVal.includes('_history') ? newVal : newVal + '_history');
+    // if (column && column.length < 1) {
+    //   const arr = newVal.split('_');
+    //   console.log('历史记录列表表头------------>', arr[0] + '_monitor');
+    //   columns.value = getTableHeaderColumns(arr[0] + '_history');
+    //   if (columns.value.length < 1) {
+    //     if (historyType.value) {
+    //       columns.value = getTableHeaderColumns(historyType.value + '_history');
+    //     }
+    //   }
+    // } else {
+    //   columns.value = column;
+    // }
+    await getDeviceList();
+    nextTick(() => {
+      getDataSource();
     });
-  });
 
-  watch(
-    () => {
-      return props.columnsType;
-    },
-    async (newVal) => {
-      if (!newVal) return;
-      deviceKide.value = newVal;
-      if (historyTable.value) {
-        getForm().resetFields();
-        // getForm().updateSchema();
-        // getForm();
-      }
-      dataSource.value = [];
-      // const column = getTableHeaderColumns(newVal.includes('_history') ? newVal : newVal + '_history');
-      // if (column && column.length < 1) {
-      //   const arr = newVal.split('_');
-      //   console.log('历史记录列表表头------------>', arr[0] + '_monitor');
-      //   columns.value = getTableHeaderColumns(arr[0] + '_history');
-      //   if (columns.value.length < 1) {
-      //     if (historyType.value) {
-      //       columns.value = getTableHeaderColumns(historyType.value + '_history');
-      //     }
-      //   }
-      // } else {
-      //   columns.value = column;
-      // }
-      await getDeviceList();
-      nextTick(() => {
-        getDataSource();
-      });
-
-      if (historyTable.value) reload();
-    },
-    {
-      immediate: true,
-    }
-  );
+    if (historyTable.value) reload();
+  },
+  {
+    immediate: true,
+  }
+);
+
+watch(historyType, (type) => {
+  if (!type) return;
+  // if (historyTable.value) getForm().resetFields()
+
+  const column = getTableHeaderColumns(type.includes('_history') ? type : type + '_history');
+  if (column && column.length < 1) {
+    const arr = type.split('_');
+    columns.value = getTableHeaderColumns(arr[0] + '_history');
+  } else {
+    columns.value = column;
+  }
+  setColumns(columns.value);
+});
+
+const showCurve = ref(false);
+
+// 是否显示历史曲线,在devices_shows_history_curve字典里可以配置哪些设备类型需要显示曲线
+// 字典内的字段可以是前缀,例如fanlocal之于fanlocal_normal
+// 安全监控设备需要更多的配置,除去配置safetymonitor,还需要配置哪些安全监控设备需要曲线
+// 因此可以配置例如A1001的dataTypeName代码(可以查看真实数据参考)
+function calcShowCurveValue() {
+  const historyCurveDicts = getDictItemsByCode('devices_shows_history_curve') || [];
+  const findDict = (str) => historyCurveDicts.some(({ value }) => str.startsWith(value));
+
+  if (!props.showHistoryCurve) return false;
+  const dt = props.deviceType; // 依赖项
+
+  if (!findDict(dt)) return false;
+  if (!dt.startsWith('safetymonitor')) return true;
+
+  // 和字典的设备类型匹配后,如果是安全监控设备,需要额外的匹配安全监控类型
+  const dtns = get(selectedOption.value, 'readData.dataTypeName', ''); // 依赖项
+  return findDict(dtns);
+}
+
+function initHistoryCurveColumns() {
+  if (!props.showHistoryCurve) return;
+  const arr = historyType.value.split('_');
+  // 没错,又是安全监控。安全监控的单位无法一次定好,所以根据返回的数据协定单位
+  if (props.deviceType.startsWith('safetymonitor')) {
+    chartsColumns.value = getTableHeaderColumns(arr[0] + '_chart').map((e) => {
+      const unit = get(selectedOption.value, 'readData.unit', e.unit);
+      return {
+        ...e,
+        unit: unit,
+        seriesName: unit,
+      };
+    });
+  } else {
+    chartsColumns.value = getTableHeaderColumns(arr[0] + '_chart');
+  }
+}
+
+const tableScroll = computed(() => {
+  if (props.scroll.y && showCurve.value) return { y: props.scroll.y - 450 };
+  if (props.scroll.y) return { y: props.scroll.y - 100 };
+  return {};
+});
+
+// watch(stationType, (type) => {
+//   if (type) {
+//     nextTick(() => {
+//       getDataSource();
+//     });
+//   }
+// });
+
+watch(
+  () => props.deviceId,
+  async () => {
+    await getForm().setFieldsValue({});
+    await getDeviceList();
+  }
+);
+
+/** 获取可供查询历史数据的设备列表 */
+async function getDeviceList() {
+  // if (props.deviceType.split('_')[1] && props.deviceType.split('_')[1] === 'history') return;
+  let result;
+  let response;
+  if (props.onlyBounedDevices) {
+    response = await getDeviceListApi({
+      systemID: props.sysId,
+      devicetype: 'sys',
+    }).then(({ msgTxt }) => {
+      return { msgTxt: msgTxt.filter((e) => e.type === props.deviceType) };
+    });
+  } else if (props.sysId) {
+    response = await getDeviceListApi({
+      sysId: props.sysId,
+      devicetype: props.deviceType.startsWith('vehicle') ? 'location_normal' : props.deviceType,
+      pageSize: 10000,
+    });
+  } else if (props.deviceListApi) {
+    response = await props.deviceListApi();
+  } else {
+    response = await getDeviceListApi({ devicetype: props.deviceType, pageSize: 10000 });
+  }
 
-  watch(historyType, (type) => {
-    if (!type) return;
-    // if (historyTable.value) getForm().resetFields()
+  // 处理不同格式的数据
+  if (response['records'] && response['records'].length > 0) {
+    result = response['records'];
+  } else if (response['msgTxt'] && response['msgTxt'][0] && response['msgTxt'][0]['datalist']) {
+    result = response['msgTxt'][0]['datalist'];
+  }
+  if (response['msgTxt'] && response['msgTxt'][0]) {
+    deviceTypeName.value = response['msgTxt'][0]['typeName'];
+    deviceType.value = response['msgTxt'][0]['type'];
+  }
 
-    const column = getTableHeaderColumns(type.includes('_history') ? type : type + '_history');
-    if (column && column.length < 1) {
-      const arr = type.split('_');
-      columns.value = getTableHeaderColumns(arr[0] + '_history');
+  if (result) {
+    deviceOptions.value = [];
+    deviceOptions.value = result.map((item, index) => {
+      return {
+        label: item['strinstallpos'],
+        value: item['id'] || item['deviceID'],
+        strtype: item['strtype'] || item['deviceType'],
+        strinstallpos: item['strinstallpos'],
+        devicekind: item['devicekind'],
+        stationtype: item['stationtype'],
+        readData: item['readData'],
+      };
+    });
+    if (hasMultipleChoice) {
+      deviceOptions.value.unshift({
+        label: '全部',
+        value: 'ALL',
+        strtype: deviceOptions.value[0]?.strtype || '',
+        stationtype: deviceOptions.value[0]?.stationtype || 'plc1',
+      });
+    }
+    stationType.value = deviceOptions.value[0]['stationtype'];
+    if (props.deviceType.startsWith('vehicle')) {
+      historyType.value = 'vehicle';
     } else {
-      columns.value = column;
+      historyType.value = deviceOptions.value[0]['strtype'] || deviceOptions.value[0]['devicekind'];
     }
-    setColumns(columns.value);
-  });
 
-  const showCurve = ref(false);
-
-  // 是否显示历史曲线,在devices_shows_history_curve字典里可以配置哪些设备类型需要显示曲线
-  // 字典内的字段可以是前缀,例如fanlocal之于fanlocal_normal
-  // 安全监控设备需要更多的配置,除去配置safetymonitor,还需要配置哪些安全监控设备需要曲线
-  // 因此可以配置例如A1001的dataTypeName代码(可以查看真实数据参考)
-  function calcShowCurveValue() {
-    const historyCurveDicts = getDictItemsByCode('devices_shows_history_curve') || [];
-    const findDict = (str) => historyCurveDicts.some(({ value }) => str.startsWith(value));
-
-    if (!props.showHistoryCurve) return false;
-    const dt = props.deviceType; // 依赖项
-
-    if (!findDict(dt)) return false;
-    if (!dt.startsWith('safetymonitor')) return true;
-
-    // 和字典的设备类型匹配后,如果是安全监控设备,需要额外的匹配安全监控类型
-    const dtns = get(selectedOption.value, 'readData.dataTypeName', ''); // 依赖项
-    return findDict(dtns);
+    /** 此处使用nextTick是由于可能表单暂未更新,而下面的方法依赖表单项 */
+    nextTick(() => {
+      showCurve.value = calcShowCurveValue();
+      initHistoryCurveColumns();
+    });
   }
-
-  function initHistoryCurveColumns() {
-    if (!props.showHistoryCurve) return;
-    const arr = historyType.value.split('_');
-    // 没错,又是安全监控。安全监控的单位无法一次定好,所以根据返回的数据协定单位
-    if (props.deviceType.startsWith('safetymonitor')) {
-      chartsColumns.value = getTableHeaderColumns(arr[0] + '_chart').map((e) => {
-        const unit = get(selectedOption.value, 'readData.unit', e.unit);
-        return {
-          ...e,
-          unit: unit,
-          seriesName: unit,
-        };
-      });
+  if (VENT_PARAM.historyIsMultiple) {
+    await getForm().setFieldsValue({
+      gdeviceids: [props.deviceId ? props.deviceId : deviceOptions.value[1] ? deviceOptions.value[1]['value'] : ''],
+    });
+    await getForm().updateSchema({
+      field: 'gdeviceids',
+      componentProps: {
+        mode: 'multiple',
+        maxTagCount: 'responsive',
+      },
+    });
+  } else {
+    await getForm().setFieldsValue({
+      gdeviceids: props.deviceId ? props.deviceId : deviceOptions.value[1] ? deviceOptions.value[1]['value'] : '',
+    });
+    await getForm().updateSchema({
+      field: 'gdeviceids',
+    });
+  }
+}
+
+function resetFormParam() {
+  const formData = getForm().getFieldsValue();
+  const pagination = getPaginationRef();
+  formData['pageNo'] = pagination['current'];
+  formData['pageSize'] = pagination['pageSize'];
+  formData['column'] = 'createTime';
+
+  // ===================== 核心:最终完美处理 =====================
+  let gdeviceids = formData['gdeviceids'];
+  let realDeviceIds = [];
+
+  // 1. 统一转成数组
+  if (gdeviceids) {
+    if (Array.isArray(gdeviceids)) {
+      realDeviceIds = gdeviceids;
     } else {
-      chartsColumns.value = getTableHeaderColumns(arr[0] + '_chart');
+      realDeviceIds = String(gdeviceids)
+        .split(',')
+        .map((i) => i.trim());
     }
   }
 
-  const tableScroll = computed(() => {
-    if (props.scroll.y && showCurve.value) return { y: props.scroll.y - 450 };
-    if (props.scroll.y) return { y: props.scroll.y - 100 };
-    return {};
-  });
-
-  // watch(stationType, (type) => {
-  //   if (type) {
-  //     nextTick(() => {
-  //       getDataSource();
-  //     });
-  //   }
-  // });
-
-  watch(
-    () => props.deviceId,
-    async () => {
-      await getForm().setFieldsValue({});
-      await getDeviceList();
-    }
-  );
-
-  /** 获取可供查询历史数据的设备列表 */
-  async function getDeviceList() {
-    // if (props.deviceType.split('_')[1] && props.deviceType.split('_')[1] === 'history') return;
-    let result;
-    let response;
-    if (props.onlyBounedDevices) {
-      response = await getDeviceListApi({
-        systemID: props.sysId,
-        devicetype: 'sys',
-      }).then(({ msgTxt }) => {
-        return { msgTxt: msgTxt.filter((e) => e.type === props.deviceType) };
-      });
-    } else if (props.sysId) {
-      response = await getDeviceListApi({
-        sysId: props.sysId,
-        devicetype: props.deviceType.startsWith('vehicle') ? 'location_normal' : props.deviceType,
-        pageSize: 10000,
-      });
-    } else if (props.deviceListApi) {
-      response = await props.deviceListApi();
-    } else {
-      response = await getDeviceListApi({ devicetype: props.deviceType, pageSize: 10000 });
-    }
+  // 2. 过滤 ALL、空值
+  realDeviceIds = realDeviceIds.filter((id) => id && id !== 'ALL' && id !== '');
 
-    // 处理不同格式的数据
-    if (response['records'] && response['records'].length > 0) {
-      result = response['records'];
-    } else if (response['msgTxt'] && response['msgTxt'][0] && response['msgTxt'][0]['datalist']) {
-      result = response['msgTxt'][0]['datalist'];
-    }
-    if (response['msgTxt'] && response['msgTxt'][0]) {
-      deviceTypeName.value = response['msgTxt'][0]['typeName'];
-      deviceType.value = response['msgTxt'][0]['type'];
-    }
+  // 3. 转成逗号分隔字符串(后端要的格式)
+  const deviceIdStr = realDeviceIds.join(',');
 
-    if (result) {
-      deviceOptions.value = [];
-      deviceOptions.value = result.map((item, index) => {
-        return {
-          label: item['strinstallpos'],
-          value: item['id'] || item['deviceID'],
-          strtype: item['strtype'] || item['deviceType'],
-          strinstallpos: item['strinstallpos'],
-          devicekind: item['devicekind'],
-          stationtype: item['stationtype'],
-          readData: item['readData'],
-        };
-      });
+  // ===================== 你的业务 =====================
+  if (stationType.value !== 'redis' && deviceOptions.value[0]) {
+    formData['strtype'] = '*';
 
-      stationType.value = deviceOptions.value[0]['stationtype'];
-      if (props.deviceType.startsWith('vehicle')) {
-        historyType.value = 'vehicle';
-      } else {
-        historyType.value = deviceOptions.value[0]['strtype'] || deviceOptions.value[0]['devicekind'];
-      }
-
-      /** 此处使用nextTick是由于可能表单暂未更新,而下面的方法依赖表单项 */
-      nextTick(() => {
-        showCurve.value = calcShowCurveValue();
-        initHistoryCurveColumns();
-      });
+    if (props.sysId) {
+      formData['sysId'] = props.sysId;
     }
-    if (VENT_PARAM.historyIsMultiple) {
-      await getForm().setFieldsValue({
-        gdeviceids: [props.deviceId ? props.deviceId : deviceOptions.value[0] ? deviceOptions.value[0]['value'] : ''],
-      });
-      await getForm().updateSchema({
-        field: 'gdeviceids',
-        componentProps: {
-          mode: 'multiple',
-          maxTagCount: 'responsive',
-        },
-      });
-    } else {
-      await getForm().setFieldsValue({
-        gdeviceids: props.deviceId ? props.deviceId : deviceOptions.value[0] ? deviceOptions.value[0]['value'] : '',
-      });
-      await getForm().updateSchema({
-        field: 'gdeviceids',
-      });
-    }
-  }
 
-  function resetFormParam() {
-    const formData = getForm().getFieldsValue();
-    const pagination = getPaginationRef();
-    formData['pageNo'] = pagination['current'];
-    formData['pageSize'] = pagination['pageSize'];
-    formData['column'] = 'createTime';
-    if (stationType.value !== 'redis' && deviceOptions.value[0]) {
-      formData['strtype'] = deviceTypeStr.value
-        ? deviceTypeStr.value
-        : deviceOptions.value[0]['strtype']
-        ? deviceOptions.value[0]['strtype']
-        : props.deviceType + '*';
-      if (props.sysId) {
-        formData['sysId'] = props.sysId;
-      }
-      return formData;
-    } else {
-      const params = {
-        pageNum: pagination['current'],
-        pageSize: pagination['pageSize'],
-        column: pagination['createTime'],
-        startTime: formData['ttime_begin'],
-        endTime: formData['ttime_end'],
-        deviceId: formData['gdeviceids'],
-        strtype: props.deviceType + '*',
-        sysId: props.sysId,
-        interval: intervalMap.get(formData['skip']) ? intervalMap.get(formData['skip']) : '1h',
-        isEmployee: props.deviceType.startsWith('vehicle') ? false : true,
-      };
-      return params;
-    }
+    // 字符串格式给后端
+    formData['gdeviceids'] = deviceIdStr;
+    return formData;
+  } else {
+    const params = {
+      pageNum: pagination['current'],
+      pageSize: pagination['pageSize'],
+      column: pagination['createTime'],
+      startTime: formData['ttime_begin'],
+      endTime: formData['ttime_end'],
+      deviceId: deviceIdStr, // 字符串!
+      strtype: '*',
+      sysId: props.sysId,
+      interval: intervalMap.get(formData['skip']) || '1h',
+      isEmployee: props.deviceType.startsWith('vehicle') ? false : true,
+    };
+    return params;
   }
-
-  async function getDataSource() {
-    dataSource.value = [];
-    setLoading(true);
-    const params = await resetFormParam();
-    if (stationType.value !== 'redis') {
-      const result = await defHttp.get({ url: '/safety/ventanalyMonitorData/listdays', params: params });
-      setPagination({ total: Math.abs(result['datalist']['total']) || 0 });
-      if (result['datalist']['records'].length > 0) {
-        dataSource.value = result['datalist']['records'].map((item: any) => {
-          return Object.assign(item, item['readData']);
-        });
-      } else {
-        dataSource.value = [];
-      }
+}
+async function getDataSource() {
+  dataSource.value = [];
+  setLoading(true);
+  const params = await resetFormParam();
+  if (stationType.value !== 'redis') {
+    const result = await defHttp.get({ url: '/safety/ventanalyMonitorData/listdays', params: params });
+    setPagination({ total: Math.abs(result['datalist']['total']) || 0 });
+    if (result['datalist']['records'].length > 0) {
+      dataSource.value = result['datalist']['records'].map((item: any) => {
+        return Object.assign(item, item['readData']);
+      });
     } else {
-      const result = await defHttp.post({ url: '/monitor/history/getHistoryData', params: params });
-      setPagination({ total: Math.abs(result['total']) || 0 });
-      dataSource.value = result['records'] || [];
+      dataSource.value = [];
     }
-    setLoading(false);
+  } else {
+    const result = await defHttp.post({ url: '/monitor/history/getHistoryData', params: params });
+    setPagination({ total: Math.abs(result['total']) || 0 });
+    dataSource.value = result['records'] || [];
   }
-
-  // 列表页面公共参数、方法
-  const { tableContext, onExportXls, onExportXlsPost } = useListPage({
-    tableProps: {
-      // api: list,
-      columns: props.columnsType ? columns : (props.columns as any[]),
-      canResize: true,
-      showTableSetting: false,
-      showActionColumn: false,
-      bordered: false,
-      size: 'small',
-      showIndexColumn: true,
-      tableLayout: 'auto',
-      formConfig: {
-        labelAlign: 'left',
-        labelWidth: 80,
-        showAdvancedButton: false,
-        showSubmitButton: false,
-        showResetButton: false,
-        baseColProps: {
-          xs: 24,
-          sm: 24,
-          md: 24,
-          lg: 9,
-          xl: 7,
-          xxl: 4,
-        },
-        schemas:
-          props.formSchemas.length > 0
-            ? props.formSchemas
-            : [
-                {
-                  field: 'ttime_begin',
-                  label: '开始时间',
-                  component: 'DatePicker',
-                  defaultValue: dayjs().startOf('date'),
-                  required: true,
-                  componentProps: {
-                    showTime: true,
-                    valueFormat: 'YYYY-MM-DD HH:mm:ss',
-                    getPopupContainer: getAutoScrollContainer,
-                  },
-                  colProps: {
-                    span: 4,
-                  },
+  setLoading(false);
+}
+
+// 列表页面公共参数、方法
+const { tableContext, onExportXls, onExportXlsPost } = useListPage({
+  tableProps: {
+    // api: list,
+    columns: props.columnsType ? columns : (props.columns as any[]),
+    canResize: true,
+    showTableSetting: false,
+    showActionColumn: false,
+    bordered: false,
+    size: 'small',
+    showIndexColumn: true,
+    tableLayout: 'auto',
+    formConfig: {
+      labelAlign: 'left',
+      labelWidth: 80,
+      showAdvancedButton: false,
+      showSubmitButton: false,
+      showResetButton: false,
+      baseColProps: {
+        xs: 24,
+        sm: 24,
+        md: 24,
+        lg: 9,
+        xl: 7,
+        xxl: 4,
+      },
+      schemas:
+        props.formSchemas.length > 0
+          ? props.formSchemas
+          : [
+              {
+                field: 'ttime_begin',
+                label: '开始时间',
+                component: 'DatePicker',
+                defaultValue: dayjs().startOf('date'),
+                required: true,
+                componentProps: {
+                  showTime: true,
+                  valueFormat: 'YYYY-MM-DD HH:mm:ss',
+                  getPopupContainer: getAutoScrollContainer,
+                },
+                colProps: {
+                  span: 4,
                 },
-                {
-                  field: 'ttime_end',
-                  label: '结束时间',
-                  component: 'DatePicker',
-                  defaultValue: dayjs(),
-                  required: true,
-                  componentProps: {
-                    showTime: true,
-                    valueFormat: 'YYYY-MM-DD HH:mm:ss',
-                    getPopupContainer: getAutoScrollContainer,
-                  },
-                  colProps: {
-                    span: 4,
-                  },
+              },
+              {
+                field: 'ttime_end',
+                label: '结束时间',
+                component: 'DatePicker',
+                defaultValue: dayjs(),
+                required: true,
+                componentProps: {
+                  showTime: true,
+                  valueFormat: 'YYYY-MM-DD HH:mm:ss',
+                  getPopupContainer: getAutoScrollContainer,
                 },
-                {
-                  label: computed(() => `${deviceKide.value.startsWith('location') ? '查询人员' : '查询设备'}`),
-                  field: 'gdeviceids',
-                  component: 'Select',
-                  required: true,
-                  componentProps: {
+                colProps: {
+                  span: 4,
+                },
+              },
+              {
+                label: computed(() => `${deviceKide.value.startsWith('location') ? '查询人员' : '查询设备'}`),
+                field: 'gdeviceids',
+                component: 'Select',
+                required: true,
+                componentProps: computed(() => {
+                  const isMultiple = hasMultipleChoice;
+                  const ALL_VALUE = 'ALL';
+
+                  return {
                     showSearch: true,
+                    mode: isMultiple ? 'multiple' : undefined,
+                    multiple: isMultiple,
+                    optionType: isMultiple ? 'checkbox' : undefined,
                     filterOption: (input: string, option: any) => {
                       return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
                     },
                     options: deviceOptions,
-                    onChange: (e, option) => {
-                      if (option && (option['strinstallpos'] || option['strtype'] || option['devicekind'])) {
-                        historyType.value = option['strtype'] || option['devicekind'];
+
+                    onChange: (valueList, selectedOptions) => {
+                      const form = getForm();
+                      if (!isMultiple) {
+                        const lastOption = selectedOptions;
+                        if (lastOption) {
+                          historyType.value = lastOption.strtype || lastOption.devicekind;
+                          deviceTypeStr.value = lastOption.strtype;
+                          stationType.value = lastOption.stationtype;
+                        }
+                        nextTick(() => {
+                          showCurve.value = calcShowCurveValue();
+                          initHistoryCurveColumns();
+                          getDataSource();
+                        });
+                        return;
                       }
-                      if (option['strtype']) {
-                        deviceTypeStr.value = option['strtype'];
+                      const userSelectedIds = valueList.filter((id) => id !== ALL_VALUE);
+                      const allRealIds = deviceOptions.value.filter((o) => o.value !== ALL_VALUE).map((o) => o.value);
+                      const justClickedAll = valueList.at(-1) === ALL_VALUE;
+                      const isAllChecked = valueList.includes(ALL_VALUE);
+
+                      // 1. 手动点了【全部】→ 全选
+                      if (justClickedAll) {
+                        form.setFieldsValue({
+                          gdeviceids: [ALL_VALUE, ...allRealIds],
+                        });
+                      } else if (isAllChecked && userSelectedIds.length < allRealIds.length) {
+                        form.setFieldsValue({
+                          gdeviceids: userSelectedIds,
+                        });
+                      } else if (!isAllChecked && userSelectedIds.length === allRealIds.length && allRealIds.length > 0) {
+                        form.setFieldsValue({
+                          gdeviceids: [ALL_VALUE, ...userSelectedIds],
+                        });
+                      }
+                      const realOptions = selectedOptions?.filter((o) => o.value !== ALL_VALUE);
+                      const lastOption = realOptions?.at(-1);
+                      if (lastOption) {
+                        historyType.value = lastOption.strtype || lastOption.devicekind;
+                        deviceTypeStr.value = lastOption.strtype;
+                        stationType.value = lastOption.stationtype;
                       }
-                      stationType.value = option['stationtype'];
                       nextTick(() => {
                         showCurve.value = calcShowCurveValue();
                         initHistoryCurveColumns();
                         getDataSource();
                       });
                     },
-                  },
-                  colProps: {
-                    span: 5,
-                  },
+                  };
+                }),
+                colProps: { span: 5 },
+              },
+              {
+                label: '间隔时间',
+                field: 'skip',
+                component: 'Select',
+                defaultValue: '8',
+                componentProps: {
+                  options: [
+                    {
+                      label: '1秒',
+                      value: '1',
+                    },
+                    {
+                      label: '5秒',
+                      value: '2',
+                    },
+                    {
+                      label: '10秒',
+                      value: '3',
+                    },
+                    {
+                      label: '30秒',
+                      value: '4',
+                    },
+                    {
+                      label: '1分钟',
+                      value: '5',
+                    },
+                    {
+                      label: '10分钟',
+                      value: '6',
+                    },
+                    {
+                      label: '30分钟',
+                      value: '7',
+                    },
+                    {
+                      label: '1小时',
+                      value: '8',
+                    },
+                    {
+                      label: '1天',
+                      value: '9',
+                    },
+                  ],
                 },
-                {
-                  label: '间隔时间',
-                  field: 'skip',
-                  component: 'Select',
-                  defaultValue: '8',
-                  componentProps: {
-                    options: [
-                      {
-                        label: '1秒',
-                        value: '1',
-                      },
-                      {
-                        label: '5秒',
-                        value: '2',
-                      },
-                      {
-                        label: '10秒',
-                        value: '3',
-                      },
-                      {
-                        label: '30秒',
-                        value: '4',
-                      },
-                      {
-                        label: '1分钟',
-                        value: '5',
-                      },
-                      {
-                        label: '10分钟',
-                        value: '6',
-                      },
-                      {
-                        label: '30分钟',
-                        value: '7',
-                      },
-                      {
-                        label: '1小时',
-                        value: '8',
-                      },
-                      {
-                        label: '1天',
-                        value: '9',
-                      },
-                    ],
-                  },
-                  colProps: {
-                    span: 3,
-                  },
+                colProps: {
+                  span: 3,
                 },
-              ],
-        // fieldMapToTime: [['tickectDate', ['ttime_begin', 'ttime_end'], '']],
-      },
-      // fetchSetting: {
-      //   listField: 'datalist',
-      //   totalField: 'datalist.total',
-      // },
-      pagination: {
-        current: 1,
-        pageSize: 10,
-        pageSizeOptions: ['10', '30', '50', '100'],
-        showQuickJumper: false,
-      },
-      beforeFetch() {
-        const newParams = { ...resetFormParam() };
-        return newParams;
-      },
-      // afterFetch(result) {
-      //   const resultItems = result['records'];
-      //   resultItems.map((item) => {
-      //     Object.assign(item, item['readData']);
-      //   });
-      //   console.log('result---------------->', result);
-      //   return resultItems;
-      // },
+              },
+            ],
+      // fieldMapToTime: [['tickectDate', ['ttime_begin', 'ttime_end'], '']],
     },
-    exportConfig: {
-      name: '设备历史列表',
-      url: getExportXlsUrl,
+    // fetchSetting: {
+    //   listField: 'datalist',
+    //   totalField: 'datalist.total',
+    // },
+    pagination: {
+      current: 1,
+      pageSize: 10,
+      pageSizeOptions: ['10', '30', '50', '100'],
+      showQuickJumper: false,
     },
-  });
-
-  //注册table数据
-  const [registerTable, { reload, setLoading, getForm, setColumns, getPaginationRef, setPagination }] = tableContext;
+    beforeFetch() {
+      const newParams = { ...resetFormParam() };
+      return newParams;
+    },
+    // afterFetch(result) {
+    //   const resultItems = result['records'];
+    //   resultItems.map((item) => {
+    //     Object.assign(item, item['readData']);
+    //   });
+    //   console.log('result---------------->', result);
+    //   return resultItems;
+    // },
+  },
+  exportConfig: {
+    name: '设备历史列表',
+    url: getExportXlsUrl,
+  },
+});
+
+//注册table数据
+const [registerTable, { reload, setLoading, getForm, setColumns, getPaginationRef, setPagination }] = tableContext;
+
+function onExportXlsFn() {
+  const params = resetFormParam();
+  // 判断时间间隔和查询时间区间,数据量下载大时进行提示
+  if (stationType.value !== 'redis') {
+    return onExportXls(params);
+  } else {
+    return onExportXlsPost(params);
+  }
+}
 
-  function onExportXlsFn() {
-    const params = resetFormParam();
-    // 判断时间间隔和查询时间区间,数据量下载大时进行提示
-    if (stationType.value !== 'redis') {
-      return onExportXls(params);
-    } else {
-      return onExportXlsPost(params);
-    }
+watchEffect(() => {
+  if (historyTable.value && dataSource) {
+    const data = dataSource.value || [];
+    emit('change', data);
   }
+});
 
-  watchEffect(() => {
-    if (historyTable.value && dataSource) {
-      const data = dataSource.value || [];
-      emit('change', data);
-    }
-  });
+onMounted(async () => {
+  await getDeviceList();
+  if (deviceOptions.value[0]) {
+    nextTick(async () => {
+      await getDataSource();
+    });
+  }
 
-  onMounted(async () => {
-    await getDeviceList();
+  watch([() => getPaginationRef()['current'], () => getPaginationRef()['pageSize']], async () => {
     if (deviceOptions.value[0]) {
-      nextTick(async () => {
-        await getDataSource();
-      });
-    }
-
-    watch([() => getPaginationRef()['current'], () => getPaginationRef()['pageSize']], async () => {
       if (deviceOptions.value[0]) {
-        if (deviceOptions.value[0]) {
-          await getDataSource();
-        }
+        await getDataSource();
       }
-    });
+    }
   });
-  defineExpose({ setLoading });
+});
+defineExpose({ setLoading });
 </script>
 
 <style scoped lang="less">
-  @import '/@/design/theme.less';
-
-  :deep(.@{ventSpace}-table-body) {
-    height: auto !important;
-  }
-  :deep(.zxm-picker) {
-    height: 30px !important;
-  }
-  .history-table {
-    width: 100%;
-    :deep(.jeecg-basic-table-form-container) {
-      .@{ventSpace}-form {
-        padding: 0 !important;
-        border: none !important;
-        margin-bottom: 0 !important;
-        .@{ventSpace}-picker,
-        .@{ventSpace}-select-selector {
-          width: 100% !important;
-          height: 100%;
-          background: #00000017;
-          border: 1px solid #b7b7b7;
-          input,
-          .@{ventSpace}-select-selection-item,
-          .@{ventSpace}-picker-suffix {
-            color: #fff;
-          }
-          .@{ventSpace}-select-selection-placeholder {
-            color: #ffffffaa;
-          }
+@import '/@/design/theme.less';
+
+:deep(.@{ventSpace}-table-body) {
+  height: auto !important;
+}
+:deep(.zxm-picker) {
+  height: 30px !important;
+}
+.history-table {
+  width: 100%;
+  :deep(.jeecg-basic-table-form-container) {
+    .@{ventSpace}-form {
+      padding: 0 !important;
+      border: none !important;
+      margin-bottom: 0 !important;
+      .@{ventSpace}-picker,
+      .@{ventSpace}-select-selector {
+        width: 100% !important;
+        height: 100%;
+        background: #00000017;
+        border: 1px solid #b7b7b7;
+        input,
+        .@{ventSpace}-select-selection-item,
+        .@{ventSpace}-picker-suffix {
+          color: #fff;
+        }
+        .@{ventSpace}-select-selection-placeholder {
+          color: #ffffffaa;
         }
-      }
-      .@{ventSpace}-table-title {
-        min-height: 0 !important;
       }
     }
-    .pagination-box {
-      display: flex;
-      justify-content: flex-end;
-      align-items: center;
-      .page-num {
-        border: 1px solid #0090d8;
-        padding: 4px 8px;
-        margin-right: 5px;
-        color: #0090d8;
-      }
-      .btn {
-        margin-right: 10px;
-      }
+    .@{ventSpace}-table-title {
+      min-height: 0 !important;
     }
   }
-
-  .history-chart {
-    background-color: #0090d822;
-    margin: 0 10px;
+  .pagination-box {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    .page-num {
+      border: 1px solid #0090d8;
+      padding: 4px 8px;
+      margin-right: 5px;
+      color: #0090d8;
+    }
+    .btn {
+      margin-right: 10px;
+    }
   }
+}
+
+.history-chart {
+  background-color: #0090d822;
+  margin: 0 10px;
+}
 </style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 3749 - 0
src/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue


+ 30 - 1
src/views/vent/monitorManager/fireDoorMonitor/fireDoor.threejs.ts

@@ -9,6 +9,7 @@ let model,
   fireDoor, //液压风门
   fireDoorF, //保德风门
   fireDoorSsl, // 思山岭防火门
+  fireDoorYmh, // 思山岭防火门
   fireDoorRed, // 防火门
   group: THREE.Object3D,
   fhmType = '';
@@ -31,6 +32,9 @@ const startAnimation = () => {
     if (fhmType === 'fireDoorSsl') {
       fireDoorSsl?.mouseUpModel.call(fireDoorSsl);
     }
+    if (fhmType === 'fireDoorYmh') {
+      fireDoorYmh?.mouseUpModel.call(fireDoorYmh);
+    }
     if (fhmType === 'fireDoorRed') {
       fireDoorRed?.mouseUpModel.call(fireDoorRed);
     }
@@ -50,6 +54,9 @@ const mouseEvent = (event) => {
       if (fhmType === 'fireDoorSsl' && fireDoorSsl) {
         fireDoorSsl?.mousedownModel.call(fireDoorSsl, intersects);
       }
+      if (fhmType === 'fireDoorYmh' && fireDoorYmh) {
+        fireDoorYmh?.mousedownModel.call(fireDoorYmh, intersects);
+      }
       if (fhmType === 'fireDoorRed' && fireDoorRed) {
         fireDoorRed?.mousedownModel.call(fireDoorRed, intersects);
       }
@@ -68,6 +75,9 @@ export const play = (handlerState, flag?) => {
   if (fhmType === 'fireDoorSsl' && fireDoorSsl) {
     return fireDoorSsl.play.call(fireDoorSsl, handlerState, flag);
   }
+  if (fhmType === 'fireDoorYmh' && fireDoorYmh) {
+    return fireDoorYmh.play.call(fireDoorYmh, handlerState, flag);
+  }
   if (fhmType === 'fireDoorRed' && fireDoorRed) {
     return fireDoorRed.play.call(fireDoorRed, handlerState, flag);
   }
@@ -85,6 +95,9 @@ export const setModelType = (type) => {
   if (fhmType === 'fireDoorSsl' && fireDoorSsl) {
     fireDoorSsl.resetAnimate();
   }
+  if (fhmType === 'fireDoorYmh' && fireDoorYmh) {
+    fireDoorYmh.resetAnimate();
+  }
   if (fhmType === 'fireDoorRed' && fireDoorRed) {
     fireDoorRed.resetAnimate();
   }
@@ -108,6 +121,12 @@ export const setModelType = (type) => {
       newP: { x: 342.74781900192056, y: 183.50210411099545, z: 451.0806333923029 },
       newT: { x: 72.33938301176254, y: -35.03891296652319, z: -37.91742549963208 },
     },
+    fireDoorYmh: {
+      render: fireDoorYmh ? () => fireDoorYmh.render() : null,
+      group: fireDoorYmh ? fireDoorYmh.group : null,
+      newP: { x: 342.74781900192056, y: 183.50210411099545, z: 451.0806333923029 },
+      newT: { x: 72.33938301176254, y: -35.03891296652319, z: -37.91742549963208 },
+    },
     fireDoorRed: {
       render: fireDoorRed ? () => fireDoorRed.render() : null,
       group: fireDoorRed ? fireDoorRed.group : null,
@@ -126,7 +145,7 @@ export const setModelType = (type) => {
     model.orbitControls.minDistance = 600;
     model.orbitControls.maxDistance = 900;
     model.orbitControls.update();
-  } else if (type == 'fireDoorRed' || type == 'fireDoorSsl') {
+  } else if (type == 'fireDoorRed' || type == 'fireDoorSsl' || type == 'fireDoorYmh') {
     model.orbitControls.maxPolarAngle = Math.PI;
     model.orbitControls.minPolarAngle = 0;
     model.orbitControls.enableRotate = true;
@@ -163,6 +182,8 @@ export const initCameraCanvas = async (playerVal1) => {
     return await fireDoorF.initCamera.call(fireDoorF, playerVal1);
   } else if (fhmType === 'fireDoorSsl' && fireDoorSsl) {
     return await fireDoorSsl.initCamera.call(fireDoorSsl, playerVal1);
+  } else if (fhmType === 'fireDoorYmh' && fireDoorYmh) {
+    return await fireDoorYmh.initCamera.call(fireDoorYmh, playerVal1);
   } else if (fhmType === 'fireDoorRed' && fireDoorRed) {
     return await fireDoorRed.initCamera.call(fireDoorRed, playerVal1);
   }
@@ -172,6 +193,7 @@ const loadModel = (code): Promise<any> => {
   if (code === 'fireDoor') return import('./fireDoor.threejs.fire').then((r) => r.default);
   if (code === 'fireDoorF') return import('./fireDoor.threejs.fireF').then((r) => r.default);
   if (code === 'fireDoorSsl') return import('./fireDoor.threejs.ssl').then((r) => r.default);
+  if (code === 'fireDoorYmh') return import('./fireDoor.threejs.ymh').then((r) => r.default);
   if (code === 'fireDoorRed') return import('./fireDoor.threejs.fire.redGate').then((r) => r.default);
   return import('./fireDoor.threejs.fire.redGate').then((r) => r.default);
 };
@@ -202,6 +224,11 @@ export const mountedThree = () => {
             fireDoorSsl = new FireDoorSsl(model);
             fireDoorSsl.mountedThree();
             break;
+          case 'fireDoorYmh':
+            const FireDoorYmh = await loadModel('fireDoorYmh');
+            fireDoorYmh = new FireDoorYmh(model);
+            fireDoorYmh.mountedThree();
+            break;
           case 'fireDoorRed':
             const FireDoorRed = await loadModel('fireDoorRed');
             fireDoorRed = new FireDoorRed(model);
@@ -236,6 +263,8 @@ export const destroy = () => {
     fireDoorF = null;
     if (fireDoorSsl) fireDoorSsl.destroy();
     fireDoorSsl = null;
+    if (fireDoorYmh) fireDoorYmh.destroy();
+    fireDoorYmh = null;
     if (fireDoorRed) fireDoorRed.destroy();
     fireDoorRed = null;
     // @ts-ignore-next-line

+ 248 - 0
src/views/vent/monitorManager/fireDoorMonitor/fireDoor.threejs.ymh.ts

@@ -0,0 +1,248 @@
+import * as THREE from 'three';
+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 FireDoorYmh {
+  modelName = 'fireDoorYmh';
+  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();
+  damperOpenMesh;
+  damperClosedMesh;
+
+  clipActionArr: Record<string, THREE.AnimationAction | undefined> = {
+    /** 卷帘门动画 */
+    fengmen: undefined,
+    /** 盖板动画 */
+    gaiban: undefined,
+  };
+
+  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);
+  }
+  resetCamera() {
+    this.model.camera.far = 274;
+    this.model.orbitControls?.update();
+    this.model.camera.updateProjectionMatrix();
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(24, 24, 24);
+    this.group?.position.set(-20, 20, 10);
+  }
+
+  /* 风门动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.mixers && this.fmClock.running) {
+      this.mixers.update(1);
+    }
+  }
+
+  /* 点击风门 */
+  mousedownModel(intersects: THREE.Intersection[]) {
+    console.log('摄像头控制信息', intersects);
+  }
+
+  mouseUpModel() {}
+
+  /**
+   * 提取风门序列帧,初始化前后门动画
+   *
+   * 动画序列帧提取操作提示:使用threejs编辑器打开模型并选择模型后点击右下角播放后排查动画元素,找到分界点后手动标记或根据name标记
+   */
+  initAnimation() {
+    const group = this.group.children[0];
+    console.log('debug rr', group.animations[0].tracks);
+    /** 卷帘门的元素名称,pCude34-pCude73 */
+    if (group) {
+      const tracksA: THREE.KeyframeTrack[] = [];
+      const tracksB: THREE.KeyframeTrack[] = [];
+
+      group.animations[0].tracks.forEach((track, index) => {
+        if (index < 9) {
+          tracksA.push(track);
+        } else {
+          tracksB.push(track);
+        }
+      });
+      this.mixers = new THREE.AnimationMixer(group);
+
+      const fengmen = new THREE.AnimationClip('fengmen', 10, tracksA);
+      const clipA = this.mixers.clipAction(fengmen, group);
+      clipA.clampWhenFinished = true;
+      clipA.loop = THREE.LoopOnce;
+      this.clipActionArr.fengmen = clipA;
+
+      const gaiban = new THREE.AnimationClip('gaiban', 10, tracksB);
+      const clipB = this.mixers.clipAction(gaiban, group);
+      clipB.clampWhenFinished = true;
+      clipB.loop = THREE.LoopOnce;
+      this.clipActionArr.gaiban = clipB;
+    }
+  }
+  resetAnimate() {
+    if (this.clipActionArr.fengmen) {
+      this.clipActionArr.fengmen.reset();
+      this.clipActionArr.fengmen.time = 0.1;
+      this.clipActionArr.fengmen.stop();
+      this.fmClock.stop();
+    }
+    if (this.clipActionArr.gaiban) {
+      this.clipActionArr.gaiban.reset();
+      this.clipActionArr.gaiban.time = 0.1;
+      this.clipActionArr.gaiban.stop();
+      this.fmClock.stop();
+    }
+  }
+
+  /**
+   * 播放门开关动画的处理函数
+   * @param {number} handlerState - 处理状态,1表示开门,2表示关门
+   * @param {number} [timeScale=0.01] - 动画时间缩放因子,控制动画播放速度
+   */
+  play(handlerState, timeScale = 0.01) {
+    console.log('debug ', handlerState);
+    let handler = () => {};
+    switch (handlerState) {
+      case 1: // 打开门
+        handler = () => {
+          if (!this.clipActionArr.fengmen) return;
+          this.clipActionArr.fengmen.paused = true;
+          this.clipActionArr.fengmen.reset();
+          this.clipActionArr.fengmen.time = 0.1;
+          this.clipActionArr.fengmen.timeScale = timeScale;
+          // this.clipActionArr.fengmen.clampWhenFinished = true;
+          this.clipActionArr.fengmen.play();
+          this.fmClock.start();
+
+          // 显示打开前门文字
+          if (this.damperOpenMesh) this.damperOpenMesh.visible = true;
+        };
+        break;
+      case 2: // 关闭门
+        handler = () => {
+          if (!this.clipActionArr.fengmen) return;
+          this.clipActionArr.fengmen.paused = true;
+          this.clipActionArr.fengmen.reset(); //
+          this.clipActionArr.fengmen.time = 9;
+          this.clipActionArr.fengmen.timeScale = -timeScale;
+          // this.clipActionArr.fengmen.clampWhenFinished = true;
+          this.clipActionArr.fengmen.play();
+          this.fmClock.start();
+
+          if (this.damperOpenMesh) this.damperOpenMesh.visible = false;
+        };
+        break;
+      case 3: // 打开盖板
+        handler = () => {
+          if (!this.clipActionArr.gaiban) return;
+          this.clipActionArr.gaiban.paused = true;
+          this.clipActionArr.gaiban.reset();
+          this.clipActionArr.gaiban.time = 0.1;
+          this.clipActionArr.gaiban.timeScale = timeScale;
+          // this.clipActionArr.gaiban.clampWhenFinished = true;
+          this.clipActionArr.gaiban.play();
+          this.fmClock.start();
+
+          if (this.damperOpenMesh) this.damperOpenMesh.visible = false;
+        };
+        break;
+      case 4: // 关闭盖板
+        handler = () => {
+          if (!this.clipActionArr.gaiban) return;
+          this.clipActionArr.gaiban.paused = true;
+          this.clipActionArr.gaiban.reset(); //
+          this.clipActionArr.gaiban.time = 9;
+          this.clipActionArr.gaiban.timeScale = -timeScale;
+          // this.clipActionArr.gaiban.clampWhenFinished = true;
+          this.clipActionArr.gaiban.play();
+          this.fmClock.start();
+
+          if (this.damperOpenMesh) this.damperOpenMesh.visible = false;
+        };
+        break;
+      default:
+    }
+    handler();
+  }
+
+  mountedThree() {
+    this.group = new THREE.Object3D();
+    this.group.name = this.modelName;
+
+    return new Promise((resolve) => {
+      if (!this.model) {
+        resolve(null);
+      }
+      this.model.setGLTFModel(['fireDoorYmh'], this.group).then(() => {
+        this.setModalPosition();
+        // 初始化左右摇摆动画;
+        this.initAnimation();
+        // this.addLight();
+        // this.model.animate();
+        // resolve(this.model);
+
+        // this.damperOpenMesh = this.group.getObjectByName('Damper_Open_2');
+        // if (this.damperOpenMesh) this.damperOpenMesh.visible = false;
+        // this.damperClosedMesh = this.group.getObjectByName('Damper_Closed_2');
+        // if (this.damperClosedMesh) this.damperClosedMesh.visible = true;
+      });
+    });
+  }
+
+  destroy() {
+    if (!this.model) return;
+    if (this.mixers && this.clipActionArr.fengmen && this.clipActionArr.gaiban) {
+      this.mixers.uncacheClip(this.clipActionArr.fengmen.getClip());
+      this.mixers.uncacheAction(this.clipActionArr.fengmen.getClip(), this.group);
+      this.mixers.uncacheClip(this.clipActionArr.gaiban.getClip());
+      this.mixers.uncacheAction(this.clipActionArr.gaiban.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.fengmen = undefined;
+    this.clipActionArr.gaiban = undefined;
+
+    this.mixers = undefined;
+
+    // document.getElementById('damper3D').parentElement.remove(document.getElementById('damper3D'))
+  }
+}
+export default FireDoorYmh;

+ 12 - 0
src/views/vent/monitorManager/mainFanMonitor/index.vue

@@ -504,6 +504,18 @@
         <div class="label">{{ setValPoint['valuename'] }}:</div>
         <a-input-number v-model:value="setValPoint['value']" style="width: 150px" />
       </div>
+      <div class="" v-if="modalType == 'tckz'">
+        <div class="vent-flex-row btn-box vent-margin-t-20">
+          <div>
+            <span class="btn btn1" @click="handleOk('openWindow1_ctr')">1#天窗打开</span>
+            <span class="btn btn1" @click="handleOk('closeWindow1_ctr')">1#天窗关闭</span>
+          </div>
+          <div>
+            <span class="btn btn2" @click="handleOk('openWindow2_ctr')">2#天窗打开</span>
+            <span class="btn btn2" @click="handleOk('closeWindow2_ctr')">2#天窗打开</span>
+          </div>
+        </div>
+      </div>
     </div>
   </a-modal>
   <a-modal v-model:visible="modalWarnIsShow" :centered="true" :maskClosable="false" :title="modalTitle"

+ 5 - 0
src/views/vent/monitorManager/mainFanMonitor/main.data.ts

@@ -428,6 +428,11 @@ export const modalTypeArr = {
       value: '终止操作',
       permission: 'fan:zzcz',
     },
+    {
+      key: 'tckz',
+      value: '天窗控制',
+      permission: 'fan:tckz',
+    },
   ],
 };
 

+ 67 - 9
src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts

@@ -407,7 +407,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          // await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          }
         } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
           // 主风机反转
           if (mainObj['airJin1'] && !mainObj['airJin1'].visible) {
@@ -417,7 +422,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubInversePath');
+          // await mainObj.setSmokeDirection('back', 'tubInversePath');
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubInversePath');
+          }
         } else {
           if (mainObj['airChu1'] && !mainObj['airChu1'].visible) {
             mainObj['airJin1'].visible = false;
@@ -426,7 +436,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          // await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          }
         }
 
         if (!mainObj?.backSmoke?.frameId) mainObj?.backSmoke?.startSmoke(duration);
@@ -452,7 +467,11 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = true;
           }
           mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          }
         } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
           // 主风机反转
           if (mainObj['airJin2'] && !mainObj['airJin2'].visible) {
@@ -462,7 +481,11 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('front', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('front', 'tubInversePath');
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('front', 'tubInversePath');
+          }
         } else {
           // 默认主风机正转
           if (mainObj['airChu2'] && !mainObj['airChu2'].visible) {
@@ -472,7 +495,11 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = true;
           }
           mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('front', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('front', 'tubPositivePath');
+          }
         }
 
         if (!mainObj?.frontSmoke?.frameId) mainObj?.frontSmoke?.startSmoke(duration);
@@ -496,7 +523,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          // await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          }
         } else if (selectData.Fan1FreqReverseRun == 1 && selectData.Fan1FreqForwardRun == 0) {
           // 主风机反转
           if (mainObj['airJin1'] && !mainObj['airJin1'].visible) {
@@ -506,7 +538,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan1FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubInversePath');
+          // await mainObj.setSmokeDirection('back', 'tubInversePath');
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowInversePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubInversePath');
+          }
         } else {
           if (mainObj['airChu1'] && !mainObj['airChu1'].visible) {
             mainObj['airJin1'].visible = false;
@@ -515,7 +552,12 @@ export const playAnimate = async (selectData, duration?) => {
             mainObj['airChu2'].visible = false;
           }
           mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
-          await mainObj.setSmokeDirection('back', 'tubPositivePath');
+
+          if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+            await mainObj.setSmokeDirection('back', 'windowPositivePath');
+          } else {
+            await mainObj.setSmokeDirection('back', 'tubPositivePath');
+          }
         }
 
         if (!mainObj?.backSmoke?.frameId) mainObj?.backSmoke?.startSmoke(duration);
@@ -523,6 +565,22 @@ export const playAnimate = async (selectData, duration?) => {
         // 主风机停止
         mainObj.closeDevice('back');
       }
+      if (mainObj && modalType.includes('mainWindRect')) {
+        if (selectData['Fan1OpenWindow'] == 1 && selectData['Fan1CloseWindow'] == 0) {
+          // 打开天窗1
+          mainObj.openOrCloseWindow('back', 'openWindow');
+        } else {
+          // 关闭天窗1
+          mainObj.openOrCloseWindow('back', 'closeWindow');
+        }
+        if (selectData['Fan2OpenWindow'] == 1 && selectData['Fan2CloseWindow'] == 0) {
+          // 打开天窗1
+          mainObj.openOrCloseWindow('front', 'openWindow');
+        } else {
+          // 关闭天窗1
+          mainObj.openOrCloseWindow('front', 'closeWindow');
+        }
+      }
     }
 
     // 防爆门动画

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

@@ -38,8 +38,8 @@ class mainWindRect {
   backSmoke: Smoke | null = null; // 后面风流对象
   player1; // 视频播放器
   playerStartClickTime1 = new Date().getTime();
-  frontWindowGroup;
-  backWindowGroup;
+  frontWindowMeshList: THREE.Mesh[] = [];
+  backWindowMeshList: THREE.Mesh[] = [];
   windowAngle = 0;
   fbmAnimationClip: THREE.AnimationClip | null = null;
   fbmMixers: THREE.AnimationMixer | null = null;
@@ -243,8 +243,8 @@ class mainWindRect {
     const pathPoints: THREE.Vector3[] = [];
     const windowPositivePath = [
       {
-        path0: new THREE.Vector3(4.441, 20.267, 3.614),
-        path1: new THREE.Vector3(5.041, 6.806, 3.614),
+        path0: new THREE.Vector3(3.441, 20.267, 3.614),
+        path1: new THREE.Vector3(4.041, 6.806, 3.614),
         isSpread: true,
         spreadDirection: -1, //
       },
@@ -256,26 +256,26 @@ class mainWindRect {
       },
       {
         path0: new THREE.Vector3(41.583, 1.485, 3.614),
-        path1: new THREE.Vector3(42.741, 5.364, 3.614),
+        path1: new THREE.Vector3(45.741, 5.364, 3.614),
         isSpread: false,
         spreadDirection: 0,
       },
       {
-        path0: new THREE.Vector3(42.741, 5.364, 3.614),
-        path1: new THREE.Vector3(44.741, 17.267, 3.614),
+        path0: new THREE.Vector3(45.741, 5.364, 3.614),
+        path1: new THREE.Vector3(46.741, 17.267, 3.614),
         isSpread: true,
         spreadDirection: 1, // 1是由小变大(出),-1是由大变小(进)
       },
     ];
     const windowInversePath = [
       {
-        path0: new THREE.Vector3(44.741, 17.267, 3.614),
-        path1: new THREE.Vector3(42.741, 5.364, 3.614),
+        path0: new THREE.Vector3(46.741, 17.267, 3.614),
+        path1: new THREE.Vector3(45.741, 5.364, 3.614),
         isSpread: true,
         spreadDirection: -1, //
       },
       {
-        path0: new THREE.Vector3(42.741, 5.364, 3.614),
+        path0: new THREE.Vector3(45.741, 5.364, 3.614),
         path1: new THREE.Vector3(41.583, 1.485, 3.614),
         isSpread: false,
         spreadDirection: 0, //
@@ -287,8 +287,8 @@ class mainWindRect {
         spreadDirection: 0, // 1是由小变大,-1是由大变小
       },
       {
-        path0: new THREE.Vector3(4.441, 17.267, 3.614),
-        path1: new THREE.Vector3(5.041, 6.806, 3.614),
+        path0: new THREE.Vector3(4.041, 6.806, 3.614),
+        path1: new THREE.Vector3(3.441, 20.267, 3.614),
         isSpread: true,
         spreadDirection: 1, //
       },
@@ -697,60 +697,56 @@ class mainWindRect {
   }
 
   openOrCloseWindow(deviceType, flag) {
-    const _this = this;
     let endAngle = 0,
-      windowGroup;
+      windowMeshList;
     if (deviceType === 'front') {
-      windowGroup = this.frontWindowGroup;
+      windowMeshList = this.frontWindowMeshList;
     }
     if (deviceType === 'back') {
-      windowGroup = this.backWindowGroup;
+      windowMeshList = this.backWindowMeshList;
     }
     if (flag == 'openWindow') {
       // 打开风窗
-      endAngle = 1;
+      endAngle = Math.PI / 2;
     } else {
       // 关闭风窗
       endAngle = 0;
     }
-    if (windowGroup)
-      gsap.to(this, {
-        windowAngle: endAngle,
-        duration: Math.abs(endAngle - this.windowAngle) * 10,
+
+    if (windowMeshList) {
+      const windowAngle = {
+        value: windowMeshList[0].rotation.y,
+      };
+      const duration = Math.abs(endAngle - windowAngle.value) * 5;
+      gsap.to(windowAngle, {
+        value: endAngle,
+        duration: duration,
         ease: 'none',
         onUpdate: function () {
-          windowGroup.children.forEach((mesh) => {
-            mesh.rotation.z = _this.windowAngle;
+          windowMeshList.forEach((mesh) => {
+            mesh.rotation.y = windowAngle.value;
           });
         },
       });
+    }
   }
 
   /** 初始化风窗 */
   initWindow() {
-    if (!this.group) return;
-    this.frontWindowGroup = new THREE.Group();
-    this.frontWindowGroup.name = 'frontWindowGroup';
-
-    this.backWindowGroup = new THREE.Group();
-    this.backWindowGroup.name = 'backWindowGroup';
-    if (this.group && this.group?.children.length > 0) {
-      for (let i = this.group?.children.length - 1; i >= 0; i--) {
-        const obj = this.group?.children[i];
+    const ztfjGroup = this.group?.getObjectByName('ztfj') as THREE.Group;
+    if (!ztfjGroup) return;
+    if (ztfjGroup && ztfjGroup?.children.length > 0) {
+      for (let i = ztfjGroup?.children.length - 1; i >= 0; i--) {
+        const obj = ztfjGroup?.children[i];
         if (obj.type === 'Mesh' && obj.name && obj.name.startsWith('TC')) {
-          const mesh = obj.clone();
           if (obj.name.startsWith('TC1')) {
-            this.backWindowGroup.add(mesh);
+            this.frontWindowMeshList.push(obj);
           } else if (obj.name.startsWith('TC2')) {
-            this.frontWindowGroup.add(mesh);
+            this.backWindowMeshList.push(obj);
           }
-          obj.removeFromParent();
-          this.group?.remove(obj);
         }
       }
     }
-    this.group?.add(this.backWindowGroup);
-    this.group?.add(this.frontWindowGroup);
   }
 
   initFbmAnimation() {
@@ -798,13 +794,15 @@ class mainWindRect {
   mountedThree() {
     this.group = new THREE.Group();
     return new Promise(async (resolve) => {
-      this.model.setGLTFModel(['bg1', 'fbm', 'ztfj', 'ztfj-fc'], this.group).then(async () => {
+      this.model.setGLTFModel(['bg1', 'fbm', 'ztfj'], this.group).then(async () => {
         this.group?.position.set(-0.44, 19.88, 22.37);
         this.initSmokeMass();
         await this.setSmokePosition();
         const ztfjGroup = this.group?.getObjectByName('ztfj') as THREE.Group;
+        // ztfjGroup.visible = false;
         const fbmGroup = this.group?.getObjectByName('fbm') as THREE.Group;
         const fcGroup = this.group?.getObjectByName('ztfj-fc') as THREE.Group;
+        console.log(fcGroup);
         if (ztfjGroup) {
           ztfjGroup.position.z = ztfjGroup.position.z + 5;
           const airJinGroup = ztfjGroup.getObjectByName('JianTou1_Jin') as THREE.Object3D;
@@ -842,6 +840,8 @@ class mainWindRect {
   }
 
   destroy() {
+    this.backWindowMeshList = [];
+    this.frontWindowMeshList = [];
     this.frontSmoke.clearSmoke();
     this.backSmoke.clearSmoke();
     const fbmGroup = this.group?.getObjectByName('fbm') as THREE.Group;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff