瀏覽代碼

[Feat 0000] 登录功能添加验证码,添加是否显示验证码的配置

hongrunxia 1 周之前
父節點
當前提交
db6156dbed

+ 6 - 1
package.json

@@ -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, // 登录页面是否有验证码
 }

二進制
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>

+ 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>

+ 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;
     }
   }