2
0

2 Коммиты d87f08351e ... d4bb9183fa

Автор SHA1 Сообщение Дата
  lxh d4bb9183fa Merge branch 'master' of http://39.97.59.228:8013/hrx/hsq-pro 1 месяц назад
  lxh 46b15c789d 最新版提交 1 месяц назад

+ 1 - 0
src/api/sys/user.ts

@@ -12,6 +12,7 @@ import { PageEnum } from '/@/enums/pageEnum';
 const { createErrorModal } = useMessage();
 enum Api {
   Login = '/sys/login',
+  //Login='/sys/sso/hsqSsoLogin',
   phoneLogin = '/sys/phoneLogin',
   Logout = '/sys/logout',
   autoLogin = '/sys/autoLogin',

+ 19 - 0
src/store/modules/user.ts

@@ -161,6 +161,25 @@ export const useUserStore = defineStore({
         return Promise.reject(error);
       }
     },
+
+    //async login(
+    //  params: any): Promise<GetUserInfoModel | null> {
+    //  try {
+    //    const { goHome = true, mode = 'none', successMode, ...loginParams } = params;
+    //    // 进行加密
+    //    //const encryption = new AesEncryption({ key: loginCipher.key, iv: loginCipher.iv });
+    //    //loginParams.password = encryption.encryptByAES(loginParams.password);
+    //    //const data = await loginApi(loginParams, mode, successMode);
+    //    const data = await loginApi(loginParams);
+    //    const { token, userInfo } = data;
+    //    // save token
+    //    this.setToken(token);
+    //    // this.setTenant(userInfo.loginTenantId);
+    //    return this.afterLoginAction(goHome, data);
+    //  } catch (error) {
+    //    return Promise.reject(error);
+    //  }
+    //},
     /**
      * 扫码登录事件
      */

+ 60 - 23
src/views/sys/login/Login copy.vue

@@ -5,12 +5,7 @@
       <Spin :spinning="loading" size="large">
         <template #indicator>
           <div class="dot-spinner" aria-hidden="true">
-            <span
-              v-for="i in 8"
-              :key="i"
-              class="dot"
-              :style="dotStyle(i)"
-            />
+            <span v-for="i in 8" :key="i" class="dot" :style="dotStyle(i)" />
           </div>
         </template>
         <div class="spin-holder" />
@@ -21,15 +16,32 @@
 </template>
 
 <script setup lang="ts">
-import { ref,onMounted, watch } from 'vue'
+import { ref, onMounted, watch, toRaw, reactive } from 'vue'
 import { Spin } from 'ant-design-vue'
 import { useRoute, useRouter } from 'vue-router'
+import { useUserStore } from '/@/store/modules/user';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { getCodeInfo } from '/@/api/sys/user';
 
 /** 登录加载状态,对接登录接口后可在请求前后切换 */
 const loading = ref(true)
+const { t } = useI18n();
+  const { notification, createErrorModal } = useMessage();
 const router = useRouter()
 /** 监听 URL 地址栏中的 type 查询参数 */
 const route = useRoute()
+const userStore = useUserStore();
+ const loginForm = reactive({
+    account: 'hsq',
+    password: 'hsq123hsq',
+    inputCode: '',
+  });
+   const randCodeData = reactive({
+    randCodeImage: '',
+    requestCodeSuccess: false,
+    checkKey: null,
+  });
 
 function dotStyle(i: number) {
   const index = i - 1
@@ -43,28 +55,52 @@ function dotStyle(i: number) {
   }
 }
 
-watch(
-  () => route.query.type,
+async function handleLogin(params:any) {
+  try {
+    const { userInfo } = await userStore.login(
+      toRaw({
+        ticket: params.ticket,
+        type: params.type,
+      })
+    );
+    if (userInfo) {
+      loading.value = false
+      notification.success({
+        message: t('sys.login.loginSuccessTitle'),
+        description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
+        duration: 3,
+      });
+    }
+  } catch (error) {
+    //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() {
+    loginForm.inputCode = '';
+    //TODO 兼容mock和接口,暂时这样处理
+    randCodeData.checkKey = 1629428467008; //new Date().getTime();
+    getCodeInfo(randCodeData.checkKey).then((res) => {
+      randCodeData.randCodeImage = res;
+      randCodeData.requestCodeSuccess = true;
+    });
+  }
+
+  watch(
+  () => route.query,
   (type) => {
-    const typeValue = Array.isArray(type) ? type[0] : type
-    if (typeValue == null || typeValue === '') return
     // 根据 type 处理业务逻辑
-    if (typeValue === '1') {
-     //向192.168.247.84:80请求
-    } else {
-     //192.168.247.17:81请求
-    }
+    handleLogin({ticket: type.ticket, type: type.type})
   },
   { immediate: true }
 )
 
-
-onMounted(() => {
-  setTimeout(() => {
-    loading.value = false
-    router.push('/configurable/default/hsq/home')
-  }, 3000)
-})
+  //初始化验证码
+  onMounted(() => {
+    handleChangeCheckCode();
+  });
 </script>
 
 <style lang="less" scoped>
@@ -142,6 +178,7 @@ onMounted(() => {
   from {
     transform: rotate(0deg);
   }
+
   to {
     transform: rotate(360deg);
   }

+ 3 - 3
src/views/vent/cad/dwgViewer.vue

@@ -101,10 +101,10 @@
   const MARKER_MESH_RADIUS = 100;
   /** 信息框相对布点右边缘的间距(px) */
   const INFO_PANEL_GAP = 15;
-  /** 信息框字号基准:布点屏幕半宽为该值时,字号为 12px */
+  /** 信息框字号基准:布点屏幕半宽为该值时,字号为 14px */
   const INFO_PANEL_BASE_HALF = 36;
   /** 信息框字号基准(px) */
-  const INFO_PANEL_BASE_FONT = 12;
+  const INFO_PANEL_BASE_FONT = 14;
   /**
    * 初始化仅加载必要字体,减少 setFont 阻塞时间
    * (图纸中文依赖 shx + 一份 TrueType/typeface)
@@ -643,7 +643,7 @@
     infoPanel.style.cssText = `
       position:absolute; left:0; top:0;
       transform:translateY(-50%);
-      background:rgba(0,0,0,0.88); color:#fff; font-size:12px;
+      background:rgba(0,0,0,0.88); color:#fff; font-size:14px;
       border:1px solid rgba(255,255,255,0.2); border-radius:6px;
       padding:6px 8px; white-space:nowrap; z-index:600;
       pointer-events:auto; line-height:1.8;

+ 107 - 64
src/views/vent/monitorManager/hsqHome/components/AlarmMonitor.vue

@@ -1,9 +1,17 @@
 <template>
   <div class="alarm-monitor">
     <div class="alarm-top">
-      <div class="card-item" v-for="(item, index) in option" :key="index">
-        <div class="item-value">{{ cardData[item.value] }}</div>
-        <div class="item-label">{{ item.label }}</div>
+      <div class="time-range">
+        <a-range-picker
+          v-model:value="timeRange"
+          class="alarm-range-picker"
+          show-time
+          value-format="YYYY-MM-DD HH:mm:ss"
+          format="YYYY-MM-DD HH:mm:ss"
+          :placeholder="['开始时间', '结束时间']"
+          :allow-clear="false"
+          @change="getDscAlarmLogList"
+        />
       </div>
     </div>
     <div class="alarm-bot">
@@ -14,18 +22,21 @@
 
 <script setup lang="ts">
 import { onMounted, ref } from 'vue';
+import dayjs from 'dayjs';
 import CommonTable from './CommonTable.vue';
-import { autoLogList } from '../hsqHome.api';
+import { autoLogList, dscAlarmLogList } from '../hsqHome.api';
 import {alarmColumns} from '../hsqHome.data'
 
+const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
+
 let props = defineProps({
-  /**
-   * 顶部卡片配置项
-   * 每项包含 label(显示名称)和 value(对应 cardData 中的 key)
-   */
-  option: {
-    type: Array as any
-  },
+  // /**
+  //  * 顶部卡片配置项
+  //  * 每项包含 label(显示名称)和 value(对应 cardData 中的 key)
+  //  */
+  // option: {
+  //   type: Array as any
+  // },
   /**
    * 卡片数据对象
    * key 对应 option 中每项的 value,value 为实际展示数值
@@ -38,32 +49,62 @@ let props = defineProps({
   }
 })
 
+/** 时间范围:[开始时间, 结束时间],默认近 7 天至当前时间 */
+const timeRange = ref<[string, string]>([
+  dayjs().subtract(7, 'day').format(DATE_FORMAT),
+  dayjs().format(DATE_FORMAT),
+])
+
 // 表格数据源:从后端获取报警日志列表数据
 let tableData = ref<any[]>([])
 
+// /**
+//  * 获取报警日志列表数据
+//  *
+//  * 调用 autoLogList API,按所选开始/结束时间查询,
+//  * 将返回的报警日志数据填充到表格中用于展示
+//  *
+//  * @returns {Promise<void>}
+//  */
+// async function getAlarmData() {
+//   let res = await autoLogList({
+//     pageNum: 1,
+//     pageSize: 100,
+//     sysId: '2074416476136718337',
+//     startTime: timeRange.value?.[0],
+//     endTime: timeRange.value?.[1],
+//   });
+//   tableData.value = res.records.map(el=>{
+//     return {
+//       ...el,
+//       // 将数字类型的报警等级映射为中文描述
+//       // 101:低风险  102:一般风险  103:较大风险  104:重大风险  201:报警
+//       alarmLevelC: el.alarmLevel == 101 ? '低风险' : el.alarmLevel == 102 ? '一般风险' : el.alarmLevel == 103 ? '较大风险' : el.alarmLevel == 104 ? '重大风险' : el.alarmLevel == 201 ? '报警' : '低风险'  
+//     }
+//   });
+// }
+
 /**
- * 获取报警日志列表数据
- *
- * 调用 autoLogList API,默认查询第1页、每页10条记录,
- * 将返回的报警日志数据填充到表格中用于展示
- *
- * @returns {Promise<void>}
+ * 获取双光谱摄像机报警记录-分页列表查询
+ * 调用 dscAlarmLogList 接口获取双光谱摄像机报警记录分页列表,
+ * 并将返回数据赋值给 warnList
  */
-async function getAlarmData() {
-  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: '2074416476136718337',  });
-  tableData.value = res.records.map(el=>{
-    return {
-      ...el,
-      // 将数字类型的报警等级映射为中文描述
-      // 101:低风险  102:一般风险  103:较大风险  104:重大风险  201:报警
-      alarmLevelC: el.alarmLevel == 101 ? '低风险' : el.alarmLevel == 102 ? '一般风险' : el.alarmLevel == 103 ? '较大风险' : el.alarmLevel == 104 ? '重大风险' : el.alarmLevel == 201 ? '报警' : '低风险'  
-    }
-  });
+async function getDscAlarmLogList() {
+  const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000, startTime: timeRange.value?.[0], endTime: timeRange.value?.[1] });
+  if (res && res.records) {
+    tableData.value = res.records.map(el=>{
+      return {
+        ...el,
+        // 将数字类型的报警等级映射为中文描述
+        eventTypeC: el.eventType == 10001 ? '热源报警' : el.eventType ==   10002	 ? '闯入报警' : '-'  
+      }
+    })
+  }
 }
 
 // 组件挂载后立即获取报警日志数据
 onMounted(() => {
-  getAlarmData();
+  getDscAlarmLogList();
 })
 </script>
 
@@ -81,50 +122,52 @@ onMounted(() => {
 
   .alarm-top {
     display: flex;
-    justify-content: space-around;
+    justify-content: center;
     align-items: center;
-    width: calc(100% - 20px);
-    height: 25%;
-    background: var(--image-card-bg) no-repeat bottom;
-    background-size: 100% auto;
-    margin: 0px 10px;
+    width: calc(100% - 14px);
+    height: 30px;
+    // background: var(--image-card-bg) no-repeat bottom;
+    // background-size: 100% auto;
+    margin: 5px 7px;
+    // padding: 0 8px;
+    // box-sizing: border-box;
   }
 
-  .alarm-bot {
+  .time-range {
     width: 100%;
-    height: 75%;
   }
 
-  .card-item {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    width: 89px;
-    height: 49px;
-    background: var(--image-card-bg1) no-repeat;
-    background-size: 100% 100%;
-
-    &:nth-child(1) {
-      .item-value {
-        font-family: 'douyuFont';
-        color: #ff3d3d;
-      }
-    }
+  .alarm-range-picker {
+    width: 100%;
+  }
 
-    &:nth-child(2) {
-      .item-value {
-        font-family: 'douyuFont';
-        color: #ff983d;
-      }
-    }
+  :deep(.zxm-picker) {
+    width: 100%;
+    height: 28px;
+    border: 1px solid #3ae1ff !important;
+    background-color: rgba(16, 64, 100, 0.85) !important;
+    box-shadow: 0 0 6px rgba(58, 225, 255, 0.25);
+  }
 
-    &:nth-child(3) {
-      .item-value {
-        font-family: 'douyuFont';
-        color: #3dff69;
-      }
-    }
+  :deep(.zxm-picker-input > input) {
+    color: #fff !important;
+    font-size: 14px;
+    text-align: center;
+  }
+
+  :deep(.zxm-picker-input > input::placeholder) {
+    color: rgba(200, 236, 255, 0.45) !important;
+  }
+
+  :deep(.zxm-picker-separator),
+  :deep(.zxm-picker-suffix),
+  :deep(.zxm-picker-clear) {
+    color: #3ae1ff !important;
+  }
+
+  .alarm-bot {
+    width: 100%;
+    height: calc(100% - 40px);
   }
 }
 </style>

+ 0 - 2
src/views/vent/monitorManager/hsqHome/components/CustomChart.vue

@@ -84,8 +84,6 @@ const genChartOption = () => {
       })
     }
   });
-
-
   const baseDataZoom = dataZoom.map((e, i) => {
     return {
       type: 'slider',

+ 72 - 22
src/views/vent/monitorManager/hsqHome/components/DeviceCenter.vue

@@ -6,10 +6,10 @@
         <span style="margin-left: 5px;">{{ detailDatas.strserno }}</span>
       </div>
       <div class="title-r">
-        <span :class="detailDatas.syswarnLevel ? 'icon-pie1' : 'icon-pie'">
-          <span :class="detailDatas.syswarnLevel ? 'pie-item1' : 'pie-item'"></span>
+        <span :class="detailDatas.netStatus ? 'icon-pie' : 'icon-pie1'">
+          <span :class="detailDatas.netStatus ? 'pie-item' : 'pie-item-warn'"></span>
         </span>
-        <span>{{ detailDatas.syswarnLevel ? '报警' : '正常' }}</span>
+        <span>{{ detailDatas.netStatus ? '在线' : '离线' }}</span>
       </div>
     </div>
     <div class="basic-box">
@@ -62,7 +62,7 @@
               <SvgIcon class="icon-style" size="20" name="yxzt" />
               <div class="label-status-text">运行状态</div>
               <div :class="detailDatas.netStatus == 0 ? 'value-status-warn' : 'value-status-text'">{{
-                detailDatas.netStatus==1 ? '在线' : detailDatas.status==0 ? '离线' : '-' }}</div>
+                detailDatas.netStatus == 1 ? '在线' : detailDatas.status == 0 ? '离线' : '-' }}</div>
             </div>
             <div class="card-item">
               <SvgIcon class="icon-style" size="20" name="wlyc" />
@@ -109,14 +109,18 @@
     <div class="basic-box1">
       <basicBorder title="历史报警记录">
         <div class="basic-history">
+          <div class="time-range">
+            <a-range-picker v-model:value="timeRange" class="alarm-range-picker" show-time
+              value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" :placeholder="['开始时间', '结束时间']"
+              :allow-clear="false" @change="getDscAlarmLogList" />
+          </div>
           <div class="history-title">
-            <div class="title-item" v-for="(item, index) in alarmColumns" :key="index">{{ item.name }}</div>
+            <div class="title-item" v-for="(item, index) in dscAlarmLogColumns" :key="index">{{ item.name }}</div>
           </div>
           <div class="history-content">
             <div class="content-item" v-for="(item, index) in tableData" :key="index">
-              <div class="item-text" v-for="(ite,ind) in alarmColumns" :key="ind">
-                <div v-if="ite.prop=='alarmLevelC'"  :style="{ color: item.alarmLevelC == '低风险' ? '#00d8ff' : item.alarmLevelC == '一般风险' ? '#fcfc22' : item.alarmLevelC == '较大风险' ? '#ff7010' : item.alarmLevelC == '重大风险' ? '#df4e43' : item.alarmLevelC == '报警' ? '#ff0000' : '' }">{{item[ite.prop]  }}</div>
-                <div v-else>{{ item[ite.prop] }}</div>
+              <div class="item-text" v-for="(ite, ind) in dscAlarmLogColumns" :key="ind">
+                <div >{{ item[ite.prop] }}</div>
               </div>
             </div>
           </div>
@@ -130,9 +134,12 @@
 import { onUnmounted, reactive, ref, watch, onMounted } from 'vue'
 import basicBorder from './basicBorder.vue'
 import { SvgIcon } from '/@/components/Icon';
-import { autoLogList } from '../hsqHome.api.js'
-import {alarmColumns} from '../hsqHome.data.js'
+import { autoLogList, dscAlarmLogList } from '../hsqHome.api.js'
+import { dscAlarmLogColumns } from '../hsqHome.data.js'
+import dayjs from 'dayjs';
+
 
+const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
 let props = defineProps({
   detailData: {
     type: Object,
@@ -142,6 +149,12 @@ let props = defineProps({
 
 let detailDatas = ref<any>({})
 let tableData = ref<any[]>([])
+/** 时间范围:[开始时间, 结束时间],默认近 7 天至当前时间 */
+const timeRange = ref<[string, string]>([
+  dayjs().subtract(7, 'day').format(DATE_FORMAT),
+  dayjs().format(DATE_FORMAT),
+])
+
 // https获取监测数据
 let timer: null | NodeJS.Timeout = null;
 
@@ -153,8 +166,7 @@ let timer: null | NodeJS.Timeout = null;
  */
 function getMonitor(flag?: boolean) {
   timer = setTimeout(async () => {
-    await getAutoLogList();
-
+    await getDscAlarmLogList();
     if (timer) {
       timer = null;
     }
@@ -162,18 +174,23 @@ function getMonitor(flag?: boolean) {
   }, flag ? 0 : 30000);
 }
 
-async function getAutoLogList() {
-  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: '2074416476136718337', });
-  tableData.value = res.records.map(el => {
-    return {
+/**
+ * 获取双光谱摄像机报警记录-分页列表查询
+ * 调用 dscAlarmLogList 接口获取双光谱摄像机报警记录分页列表,
+ * 并将返回数据赋值给 warnList
+ */
+async function getDscAlarmLogList() {
+  const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000, startTime: timeRange.value?.[0], endTime: timeRange.value?.[1] });
+  if (res && res.records) {
+    tableData.value = res.records.map(el => ({
       ...el,
-      alarmLevelC: el.alarmLevel == 101 ? '低风险' : el.alarmLevel == 102 ? '一般风险' : el.alarmLevel == 103 ? '较大风险' : el.alarmLevel == 104 ? '重大风险' : el.alarmLevel == 201 ? '报警' : '低风险'
-    }
-  });
+      eventTypeC: el.eventType === 10001 ? '热源报警' : el.eventType==10002 ? '闯入报警' : '',
+    }))
+  }
 }
 
 watch(() => props.detailData, (newVal, oldVal) => {
-  detailDatas.value =Object.assign({},newVal.readData,newVal,)
+  detailDatas.value = Object.assign({}, newVal.readData, newVal,)
 }, { immediate: true })
 
 onMounted(() => {
@@ -219,7 +236,7 @@ onUnmounted(() => {
   .title-l {
     width: 177px;
     height: 100%;
-  color: #01fefc;
+    color: #01fefc;
     font-weight: bolder;
     padding-left: 16px;
     background: var(--image-box-bg) no-repeat;
@@ -436,6 +453,39 @@ onUnmounted(() => {
     height: 100%;
   }
 
+  .time-range {
+    width: 100%;
+  }
+
+  .alarm-range-picker {
+    width: 100%;
+    margin-bottom: 10px;
+  }
+
+  :deep(.zxm-picker) {
+    width: 100%;
+    height: 28px;
+    border: 1px solid #3ae1ff !important;
+    background-color: rgba(16, 64, 100, 0.85) !important;
+    box-shadow: 0 0 6px rgba(58, 225, 255, 0.25);
+  }
+
+  :deep(.zxm-picker-input > input) {
+    color: #fff !important;
+    font-size: 14px;
+    text-align: center;
+  }
+
+  :deep(.zxm-picker-input > input::placeholder) {
+    color: rgba(200, 236, 255, 0.45) !important;
+  }
+
+  :deep(.zxm-picker-separator),
+  :deep(.zxm-picker-suffix),
+  :deep(.zxm-picker-clear) {
+    color: #3ae1ff !important;
+  }
+
   .history-title {
     display: flex;
     justify-content: space-between;
@@ -455,7 +505,7 @@ onUnmounted(() => {
   }
 
   .history-content {
-    height: calc(100% - 40px);
+    height: calc(100% - 80px);
     overflow-y: auto;
   }
 

+ 151 - 99
src/views/vent/monitorManager/hsqHome/components/DeviceControl.vue

@@ -4,21 +4,9 @@
     <!-- 控制设备下拉选择 -->
     <div class="device-select-row">
       <span class="select-label">控制设备</span>
-      <div class="device-select" @click.stop="toggleDeviceMenu">
-        <span class="select-text" :title="currentDevice">{{ currentDevice }}</span>
-        <span :class="['select-arrow', { open: deviceMenuOpen }]">
-          <svg viewBox="0 0 10 10" width="10" height="10">
-            <path d="M1 3.2 L5 7.2 L9 3.2" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"
-              stroke-linejoin="round" />
-          </svg>
-        </span>
-        <ul v-if="deviceMenuOpen" class="select-dropdown" @click.stop>
-          <li v-for="item in deviceOptions" :key="item.value" :class="{ active: item.label=== currentDevice }"
-            @click="selectDevice(item)">
-            {{ item.label }}
-          </li>
-        </ul>
-      </div>
+      <a-select v-model:value="currentDevice" class="device-select" :options="deviceOptions" :bordered="false"
+        placeholder="请选择设备" :get-popup-container="(trigger) => trigger.parentNode" @change="onDeviceChange"
+        @dropdown-visible-change="onDeviceDropdownVisible" />
     </div>
 
     <!-- 功能 Tab -->
@@ -84,7 +72,7 @@
         <span class="mode-value">{{ scanMode || '' }}</span>
         <span class="mode-line"></span>
         <span :class="['mode-arrow', { open: openMenu === 'scan' }]">▼</span>
-        <ul v-if="openMenu === 'scan'" class="mode-dropdown" @click.stop>
+        <ul v-if="openMenu === 'scan'" class="mode-dropdown drop-up" @click.stop>
           <li v-for="item in scanModes" :key="item" @click="pickMode('scan', item)">{{ item }}</li>
         </ul>
       </div>
@@ -95,7 +83,7 @@
         <span class="mode-value">{{ colorMode || '' }}</span>
         <span class="mode-line"></span>
         <span :class="['mode-arrow', { open: openMenu === 'color' }]">▼</span>
-        <ul v-if="openMenu === 'color'" class="mode-dropdown" @click.stop>
+        <ul v-if="openMenu === 'color'" class="mode-dropdown drop-up" @click.stop>
           <li v-for="item in colorModes" :key="item" @click="pickMode('color', item)">{{ item }}</li>
         </ul>
       </div>
@@ -128,10 +116,12 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { onMounted, onUnmounted, ref, watch, type PropType } from 'vue'
 import { tabsPtz, lensControls } from '../hsqHome.data'
+import { duaSpeCameraControl } from '../hsqHome.api'
+import { useMessage } from '/@/hooks/web/useMessage';
 
-let props=defineProps({
+let props = defineProps({
   deviceOptions: {
     type: Array as PropType<any[]>,
     default: () => []
@@ -149,18 +139,18 @@ const emit = defineEmits<{
   (e: 'optionChange', payload: { irZoom: boolean; irControl: boolean }): void
 }>()
 
-/** 当前选中的设备 */
+/** 当前选中的设备 value */
 const currentDevice = ref('')
-/** 设备下拉菜单是否展开 */
-const deviceMenuOpen = ref(false)
+/** 当前选中的设备类型 */
+const currentDeviceType = ref('')
 /** 当前激活的功能 Tab */
 const activeTab = ref('ptz')
 /** 云台控制速度(0-100) */
 const speed = ref(28)
 /** 当前扫描模式 */
-const scanMode = ref('')
+const scanMode = ref('预置位巡航')
 /** 当前调色模式 */
-const colorMode = ref('')
+const colorMode = ref('白热')
 /** 当前展开的下拉菜单(扫描/调色/无) */
 const openMenu = ref<'scan' | 'color' | ''>('')
 /** 红外变倍开关 */
@@ -169,32 +159,99 @@ const irZoom = ref(true)
 const irControl = ref(false)
 
 /** 扫描模式选项 */
-const scanModes = ['自动扫描', '水平扫描', '垂直扫描', '关闭']
+const scanModes = ['手动', '预置位巡航', '角度巡航', '角度全景图']
 /** 调色模式选项 */
-const colorModes = ['白热', '黑热', '彩虹', '铁红']
-
+const colorModes = ['白热', '黑热', '铁红', '熔岩', '彩虹', '铁灰', '红热', '蓝红色']
+const { createMessage } = useMessage();
+
+async function sendAction(param) {
+  let res = await duaSpeCameraControl({ ...param })
+  console.log(res,'云台控制---')
+  if (res.code === 200) {
+    createMessage.success(res.msg || '操作成功')
+  } else {
+    createMessage.error(res.msg || '操作失败')
+  }
+}
 /** 发送操作指令(云台方向/镜头控制等) */
 function emitAction(type: string, value: string) {
   emit('action', { type, value })
+  switch (value) {
+    case 'up':
+      sendAction({
+        deviceId: currentDevice.value,
+        password: '',
+        devicetype: currentDeviceType.value,
+        paramcode: '1',
+        cmd: 1,
+        stop: 0,
+        speed: speed.value,
+      })
+      break;
+    case 'down':
+      sendAction({
+        deviceId: currentDevice.value,
+        password: '',
+        devicetype: currentDeviceType.value,
+        paramcode: '1',
+        cmd: 2,
+        stop: 0,
+        speed: speed.value,
+      })
+      break;
+    case 'left':
+      sendAction({
+        deviceId: currentDevice.value,
+        password: '',
+        devicetype: currentDeviceType.value,
+        paramcode: '1',
+        cmd: 3,
+        stop: 0,
+        speed: speed.value,
+      })
+      break;
+    case 'right':
+      sendAction({
+        deviceId: currentDevice.value,
+        password: '',
+        devicetype: currentDeviceType.value,
+        paramcode: '1',
+        cmd: 4,
+        stop: 0,
+        speed: speed.value,
+      })
+      //  case 'stop':
+      // sendAction({
+      //   deviceId: currentDevice.value,
+      //   password: '',
+      //   devicetype: currentDeviceType.value,
+      //   paramcode: '1',
+      //   // cmd: ,
+      //   stop: 0,
+      //   speed: speed.value,
+      // })
+      break;
+  }
 }
 
-/** 切换设备下拉菜单的展开/收起 */
-function toggleDeviceMenu() {
-  deviceMenuOpen.value = !deviceMenuOpen.value
-  openMenu.value = ''
+/** 选择设备并触发 deviceChange 事件 */
+function onDeviceChange(value: string) {
+  const item = props.deviceOptions.find((d) => d.value === value)
+  if (item) {
+    emit('deviceChange', item)
+  }
 }
 
-/** 选择设备,关闭菜单并触发 deviceChange 事件 */
-function selectDevice(item) {
-  currentDevice.value = item.label
-  deviceMenuOpen.value = false
-  emit('deviceChange', item)
+/** 展开设备下拉时收起扫描/调色菜单 */
+function onDeviceDropdownVisible(open: boolean) {
+  if (open) {
+    openMenu.value = ''
+  }
 }
 
 /** 切换扫描/调色模式下拉菜单(互斥展开) */
 function toggleMenu(key: 'scan' | 'color') {
   openMenu.value = openMenu.value === key ? '' : key
-  deviceMenuOpen.value = false
 }
 
 /** 选择扫描/调色模式并触发对应事件 */
@@ -209,9 +266,8 @@ function pickMode(type: 'scan' | 'color', value: string) {
   openMenu.value = ''
 }
 
-/** 点击页面其他区域时关闭所有下拉菜单 */
+/** 点击页面其他区域时关闭扫描/调色下拉菜单 */
 function onDocClick() {
-  deviceMenuOpen.value = false
   openMenu.value = ''
 }
 
@@ -224,10 +280,11 @@ watch([irZoom, irControl], () => {
   emit('optionChange', { irZoom: irZoom.value, irControl: irControl.value })
 })
 watch(() => props.deviceOptions, (newVal) => {
-  if(newVal.length > 0){
-    currentDevice.value = newVal[0].label
+  if (newVal.length > 0) {
+    currentDevice.value = newVal[0].value
+    currentDeviceType.value = newVal[0].deviceType
   }
-},{immediate: true})
+}, { immediate: true })
 
 /** 挂载时注册全局点击关闭事件 */
 onMounted(() => {
@@ -278,69 +335,59 @@ onUnmounted(() => {
     color: #fff;
   }
 
-  // 自定义下拉选择框
   .device-select {
-    position: relative;
     flex: 1;
+    min-width: 0;
+  }
+
+  :deep(.zxm-select) {
+    width: 100%;
     height: 38px;
-    display: flex;
-    align-items: center;
-    padding: 0 28px 0 14px;
-    background: var(--image-bg) no-repeat;
-    background-size: 100% 100%;
-    cursor: pointer;
+  }
 
-    .select-text {
-      flex: 1;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      font-size: 12px;
-      color: #fff;
-    }
+  :deep(.zxm-select-selector) {
+    height: 38px !important;
+    padding: 0 28px 0 14px !important;
+    background: var(--image-bg) no-repeat !important;
+    background-size: 100% 100% !important;
+    border: none !important;
+    box-shadow: none !important;
+    color: #fff !important;
+  }
 
-    // 下拉箭头
-    .select-arrow {
-      position: absolute;
-      right: 20px;
-      color: #fff;
-      display: inline-flex;
-      transition: transform 0.2s;
+  :deep(.zxm-select-selection-search) {
+    inset-inline-start: 14px !important;
+  }
 
-      &.open {
-        transform: rotate(180deg);
-      }
-    }
+  :deep(.zxm-select-selection-item),
+  :deep(.zxm-select-selection-placeholder) {
+    line-height: 38px !important;
+    font-size: 12px;
+    color: #fff !important;
+  }
 
-    // 下拉菜单
-    .select-dropdown {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: calc(100% + 2px);
-      z-index: 20;
-      margin: 0;
-      padding: 4px 0;
-      list-style: none;
-      background: #0a1f3a;
-      border: 1px solid var(--dc-accent);
-      box-shadow: var(--dc-glow);
-      max-height: 140px;
-      overflow: auto;
+  :deep(.zxm-select-arrow) {
+    color: #fff !important;
+    right: 16px;
+  }
 
-      li {
-        padding: 6px 10px;
-        font-size: 12px;
-        color: #cfe9ff;
-        cursor: pointer;
+  :deep(.zxm-select-dropdown) {
+    padding: 4px 0;
+    background: #0a1f3a;
+    border: 1px solid var(--dc-accent);
+    box-shadow: var(--dc-glow);
+  }
 
-        &:hover,
-        &.active {
-          background: rgba(0, 183, 255, 0.2);
-          color: #fff;
-        }
-      }
-    }
+  :deep(.zxm-select-item) {
+    padding: 6px 10px;
+    font-size: 12px;
+    color: #cfe9ff;
+  }
+
+  :deep(.zxm-select-item-option-active),
+  :deep(.zxm-select-item-option-selected) {
+    background: rgba(0, 183, 255, 0.2) !important;
+    color: #fff !important;
   }
 }
 
@@ -693,7 +740,7 @@ onUnmounted(() => {
       left: 0;
       right: 0;
       top: calc(100% + 2px);
-      z-index: 20;
+      z-index: 30;
       margin: 0;
       padding: 4px 0;
       list-style: none;
@@ -701,6 +748,11 @@ onUnmounted(() => {
       border: 1px solid var(--dc-accent);
       box-shadow: var(--dc-glow);
 
+      &.drop-up {
+        top: auto;
+        bottom: calc(100% + 2px);
+      }
+
       li {
         padding: 5px 8px;
         font-size: 12px;
@@ -764,6 +816,6 @@ onUnmounted(() => {
 
 // 隐藏滚动条
 ::-webkit-scrollbar {
-    display: none;
-  }
+  display: none;
+}
 </style>

+ 51 - 35
src/views/vent/monitorManager/hsqHome/components/DeviceLeft.vue

@@ -1,41 +1,26 @@
-<!-- 设备列表组件(左侧面板):展示光谱摄像机设备列表,支持搜索、分类导航和设备操作 -->
 <template>
-  <!-- 设备列表主容器 -->
   <div class="long-list">
-    <!-- 列表标题栏:显示标题和设备总数 -->
     <div class="list-title">
       <span>设备列表</span>
       <span style="margin-left: 5px;">{{ count }}</span>
     </div>
-
-    <!-- 列表内容区域:包含搜索框、导航栏和设备卡片 -->
     <div class="list-content">
-      <!-- 搜索框区域:支持按设备编号或名称搜索 -->
       <div class="list-search">
         <a-input v-model:value="searchData" placeholder="搜索设备编号,名称" size="small" />
       </div>
-
-      <!-- 内容容器:包含分类导航和设备列表 -->
       <div class="content-container">
-        <!-- 分类导航栏:支持不同类型设备的切换(当前仅支持光谱摄像机) -->
         <div class="content-nav">
-          <!-- 导航项:动态绑定激活状态样式,点击触发切换事件 -->
           <div :class="activeIndex == index ? 'nav-item-active' : 'nav-item'" v-for="(item, index) in navList"
             :key="index" @click="changeNav(item, index)">
             {{ item.label }}
           </div>
         </div>
-
-        <!-- 设备列表容器:可滚动区域显示所有设备卡片 -->
         <div class="content-box">
-          <!-- 单个设备卡片:遍历listData数组渲染每个设备的信息 -->
-          <div class="basic-device" v-for="(item, index) in listData" :key="index">
-            <!-- 左侧状态指示器区域 -->
+          <div class="basic-device" v-for="(item, index) in filteredListData" :key="index">
             <div class="icon-left">
               <!-- 状态指示圆点:根据报警级别显示不同颜色(绿色正常/红色报警) -->
-              <div :class="item.syswarnLevel ? 'icon-item-warn' : 'icon-item'"></div>
+              <div :class="item.netStatus ? 'icon-item' : 'icon-item-warn'"></div>
             </div>
-
             <!-- 设备标题区域:包含序列号和操作按钮 -->
             <div class="basic-title">
               <!-- 设备序列号显示 -->
@@ -48,21 +33,16 @@
                 <div class="btn" @click="handlerClick('restart', item, index)">重启</div>
               </div>
             </div>
-
             <!-- 设备详细信息区域:2x2网格布局展示关键信息 -->
             <div class="basic-content">
-              <!-- 第一行左:设备名称 -->
               <div class="content-item">{{ item.strname || '-' }}</div>
-              <!-- 第一行右:运行状态(带状态图标) -->
               <div class="content-item">
                 <span class="icon-pie">
-                  <span class="pie-item"></span>
+                  <span class="pie-item" :class="item.netStatus ? 'pie-item' : 'pie-item-warn'"></span>
                 </span>
-                <span>{{ item.syswarnLevel ? '报警' : '正常' }}</span>
+                <span>{{ item.netStatus ? '在线' : '离线' }}</span>
               </div>
-              <!-- 第二行左:安装位置 -->
               <div class="content-item">{{ item.strinstallpos || '-' }}</div>
-              <!-- 第二行右:温度数据 -->
               <div class="content-item">
                 <span>{{ item.readData.temperature || '-' }}</span>
                 <span>℃</span>
@@ -76,9 +56,7 @@
 </template>
 
 <script setup lang="ts">
-// 导入Vue 3组合式API:生命周期钩子和响应式数据工具
-import { onMounted, onUnmounted, ref } from 'vue'
-// 导入API接口函数:管理系统列表查询、系统监控数据获取
+import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
 import { managesysList, monitorSystem } from '../hsqHome.api.js'
 
 let active = ref(0)
@@ -88,9 +66,25 @@ const systemId = ref('')
 /** 响应式数据:设备列表总数,显示在标题栏中 */
 const count = ref(0)
 
-/** 响应式数据:搜索框的绑定值,用于过滤设备(当前未实现过滤逻辑) */
+/** 响应式数据:搜索框的绑定值,用于按设备编号、名称过滤 */
 const searchData = ref('')
 
+/** 响应式数据:设备列表原始数据,存储从后端获取的所有设备信息 */
+const sourceList = ref<any[]>([])
+
+/** 根据搜索关键字过滤后的设备列表 */
+const filteredListData = computed(() => {
+  const keyword = searchData.value.trim().toLowerCase()
+  if (!keyword) {
+    return sourceList.value
+  }
+  return sourceList.value.filter((item) => {
+    const serno = String(item.strserno ?? '').toLowerCase()
+    const name = String(item.strname ?? '').toLowerCase()
+    return serno.includes(keyword) || name.includes(keyword)
+  })
+})
+
 /** 响应式数据:当前激活的导航项索引,用于控制导航栏高亮状态 */
 const activeIndex = ref(0)
 
@@ -99,9 +93,6 @@ const navList = ref<any[]>([
   { label: '光谱摄像机', value: '0' },
 ])
 
-/** 响应式数据:设备列表数据源,存储从后端获取的所有设备信息 */
-const listData = ref<any[]>([])
-
 /** 定义组件事件:向父组件传递details事件,用于通信选中设备的操作类型和数据 */
 const $emit = defineEmits(['details'])
 
@@ -177,15 +168,22 @@ async function getData() {
   console.log(res, '设备列表数据')
 
   // 提取光谱摄像机设备列表数据,使用空数组作为默认值防止undefined错误
-  listData.value = res.deviceInfo.duaSpeCamera.datalist || [];
+  sourceList.value = res.deviceInfo.duaSpeCamera.datalist || [];
 
   // 统计并记录设备总数量,用于标题栏显示
-  count.value = res.deviceInfo.duaSpeCamera.datalist.length;
+  count.value = sourceList.value.length;
   // 初始化时,默认选中第一个设备
-  active.value = 0
-  handlerClick('detail', listData.value[0], 0)
+  if (sourceList.value.length > 0) {
+    active.value = 0
+    handlerClick('detail', sourceList.value[0], 0)
+  }
 }
 
+/** 搜索条件变化时重置选中项,避免索引越界 */
+watch(searchData, () => {
+  active.value = 0
+})
+
 /**
  * 异步获取管理系统列表的初始化函数
  * 在组件挂载时首先调用此函数,获取当前用户有权限访问的系统列表
@@ -580,6 +578,24 @@ onUnmounted(() => {
       background-color: rgba(79, 255, 173);
       /* 实心荧光绿:清晰的中心点 */
     }
+
+    .pie-item-warn {
+      position: absolute;
+      /* 绝对定位:相对于.icon-pie容器 */
+      left: 50%;
+      /* 水平居中 */
+      top: 50%;
+      /* 垂直居中 */
+      transform: translate(-50%, -50%);
+      /* 居中偏移 */
+      width: 6px;
+      /* 内圈直径:比外圈小,形成环形效果 */
+      height: 6px;
+      border-radius: 50%;
+      /* 圆形 */
+      background-color: #ff4d4f;
+      /* 实心荧光绿:清晰的中心点 */
+    }
   }
 
   /* 自定义搜索框样式(覆盖Ant Design默认样式) */

+ 76 - 56
src/views/vent/monitorManager/hsqHome/components/DeviceView.vue

@@ -11,10 +11,18 @@
 
     <!-- 2×4 监控画面网格 -->
     <div class="view-grid">
-      <div v-for="item in channels" :key="item.id" class="view-cell" @click="selectChannel(item)">
+      <div class="view-cell" @click="selectChannel">
         <div class="media-wrap">
-          <video v-if="item.poster" class="media-image" ref="video" controls></video>
-          <div v-else class="media-empty">暂无画面</div>
+
+          <div v-if="renderPlayer" ref="playerRef" style="
+     display: flex;
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+      pointer-events: none;
+    ">
+          </div>
+
         </div>
       </div>
     </div>
@@ -22,16 +30,9 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-// 监控通道封面图导入
-import poster1 from '/@/assets/images/home-container/configurable/hsq/video/1-1.png'
-import poster2 from '/@/assets/images/home-container/configurable/hsq/video/1-2.png'
-import poster3 from '/@/assets/images/home-container/configurable/hsq/video/1-4.png'
-import poster4 from '/@/assets/images/home-container/configurable/hsq/video/1-5.png'
-import poster5 from '/@/assets/images/home-container/configurable/hsq/video/2-2.png'
-import poster6 from '/@/assets/images/home-container/configurable/hsq/video/2-6.png'
-import poster7 from '/@/assets/images/home-container/configurable/hsq/video/2-7.png'
-import poster8 from '/@/assets/images/home-container/configurable/hsq/video/7-3.png'
+import { onBeforeUnmount, onMounted, ref } from 'vue'
+import { useCamera } from '/@/hooks/system/useCamera';
+
 
 /** 操作按钮数据结构 */
 interface ActionItem {
@@ -39,19 +40,22 @@ interface ActionItem {
   label: string
 }
 
-/** 监控通道数据结构 */
-interface ChannelItem {
-  id: string
-  name: string
-  poster: string
-}
+let props = defineProps({
+  deviceId: {
+    type: String,
+    default: ''
+  },
+});
 
 /** 组件自定义事件 */
 const emit = defineEmits<{
   (e: 'action', key: string): void
-  (e: 'select', channel: ChannelItem): void
+  (e: 'select', channel: any): void
 }>()
 
+const { getCamera, removeCamera } = useCamera();
+const playerRef = ref();
+const renderPlayer = ref(true);
 /** 顶部操作按钮列表 */
 const actions: ActionItem[] = [
   { key: 'heat', label: '最新热源' },
@@ -60,18 +64,6 @@ const actions: ActionItem[] = [
   { key: 'alarmOff', label: '关闭声光报警' },
 ]
 
-/** 8 个通道封面图资源 */
-const posters = [poster1, poster2, poster3, poster4, poster5, poster6, poster7, poster8]
-
-/** 监控通道列表(2×4 网格,共 8 个通道) */
-const channels = ref<ChannelItem[]>(
-  posters.map((poster, index) => ({
-    id: `ch-${index + 1}`,
-    name: `通道${index + 1}`,
-    poster,
-  })),
-)
-
 /** 当前激活的操作按钮 */
 const activeAction = ref('')
 /** 当前选中的通道 ID */
@@ -84,10 +76,18 @@ function onAction(key: string) {
 }
 
 /** 选中监控通道,触发 select 事件 */
-function selectChannel(item: ChannelItem) {
+function selectChannel(item) {
   selectedId.value = item.id
   emit('select', item)
 }
+
+onMounted(async () => {
+  await getCamera(props.deviceId, playerRef, renderPlayer);
+});
+
+onBeforeUnmount(() => {
+  removeCamera(playerRef);
+});
 </script>
 
 <style lang="less" scoped>
@@ -150,35 +150,30 @@ function selectChannel(item: ChannelItem) {
 .view-grid {
   flex: 1;
   display: flex;
-  flex-wrap: wrap;
-  gap: 15px 10px;
-  justify-content: flex-start;
-  align-items: flex-start;
+  // flex-wrap: wrap;
+  // gap: 15px 10px;
+  // justify-content: flex-start;
+  // align-items: flex-start;
   overflow-y: auto;
 
 }
 
-// 单个监控画面单元格
-.view-cell {
-  width: calc(25% - 10px);
-  height: 220px;
-  cursor: pointer;
-  background: var(--image-bg) no-repeat center;
-  background-size: 100% 100%;
-}
+// // 单个监控画面单元格
+// .view-cell {
+//   width: calc(25% - 10px);
+//   height: 220px;
+//   cursor: pointer;
+//   // background: var(--image-bg) no-repeat center;
+//   // background-size: 100% 100%;
+// }
 
-// 媒体内容包裹层
-.media-wrap{
-  height: 100%;
-  padding: 10px 10px 12px 10px;
-  box-sizing: border-box;
-}
+// // 媒体内容包裹层
+// .media-wrap {
+//   height: 100%;
+//   padding: 10px 10px 12px 10px;
+//   box-sizing: border-box;
+// }
 
-// 监控视频画面
-.media-image{
-  height: 100%;
-  width: 100%;
-}
 
 // 无画面占位提示
 .media-empty {
@@ -187,4 +182,29 @@ function selectChannel(item: ChannelItem) {
   align-items: center;
   justify-content: center;
 }
+:deep(#LivePlayerBox) {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  flex-wrap: wrap;
+  pointer-events: none;
+  gap:  10px;
+
+  .liveVideo {
+    width: 349px !important;
+    height: 252px !important;
+    padding: 12px 8px !important;
+    align-self: auto !important;
+    // margin: 8px !important;
+    background-size: 100% 100% !important;
+  }
+
+  .video-parent {
+    pointer-events: auto !important;
+    align-self: auto !important;
+  }
+}
+::-webkit-scrollbar {
+    display: none;
+  }
 </style>

+ 160 - 0
src/views/vent/monitorManager/hsqHome/components/HistoryLine.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="history-line">
+    <!-- 控制设备下拉选择 -->
+    <div class="device-select-row">
+      <!-- <span class="select-label">控制设备</span> -->
+      <a-select v-model:value="currentDevice" class="device-select" :options="deviceOptions" placeholder="请选择设备"
+        :get-popup-container="(trigger) => trigger.parentNode" @change="onDeviceChange" />
+    </div>
+    <div class="device-row-line">
+      <CustomChart :chart-config="chartConfig" :chart-data="chartData"></CustomChart>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, type PropType } from 'vue';
+import CustomChart from './CustomChart.vue';
+import { chartConfig } from '../hsqHome.data'
+import { listdays } from '../hsqHome.api'
+import dayjs from 'dayjs';
+
+const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
+let props = defineProps({
+  deviceOptions: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  }
+})
+
+/** 当前选中的设备 value */
+const currentDevice = ref('')
+/** 当前选中的设备类型 */
+const deviceType = ref('')
+/** 实时温度曲线图表数据 */
+let chartData = ref<any[]>([])
+/** 时间范围:[开始时间, 结束时间],默认近 7 天至当前时间 */
+const timeRange = ref<[string, string]>([
+  dayjs().subtract(30, 'day').format(DATE_FORMAT),
+  dayjs().format(DATE_FORMAT),
+])
+
+
+
+/** 选择设备,触发 deviceChange 事件 */
+function onDeviceChange(value: string) {
+  const item = props.deviceOptions.find((d) => d.value === value)
+  currentDevice.value = value
+  deviceType.value = item?.devicetype
+  if (item) {
+    // emit('deviceChange', item)
+  }
+}
+
+/** 获取历史温度曲线数据 */
+async function getChartData() {
+  let res = await listdays({  ttime_begin: timeRange.value?.[0], ttime_end: timeRange.value?.[1],skip: '8',gdeviceids: currentDevice.value,pageNo: 1, pageSize: 1000,  strtype: deviceType.value,  })
+  console.log(res, '历史温度曲线数据')
+  if (res.datalist.records.length) {
+      chartData.value = res.datalist.records.map((v,index) => ({
+        pos: v.ttime,
+        value:parseFloat(v.readData.avg)
+      }))
+  }
+}
+
+watch(() => props.deviceOptions, (newVal) => {
+  if (newVal.length > 0) {
+    currentDevice.value = newVal[0].value
+    deviceType.value = newVal[0].devicetype
+    getChartData()
+  }
+}, { immediate: true })
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+.history-line {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 10px;
+  box-sizing: border-box;
+
+  .device-select-row {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    margin-bottom: 5px;
+    flex-shrink: 0;
+
+    .select-label {
+      white-space: nowrap;
+      color: #fff;
+    }
+
+    .device-select {
+      flex: 1;
+      min-width: 0;
+    }
+  }
+
+  .device-row-line {
+    width: 100%;
+    height: calc(100% - 35px);
+  }
+
+  :deep(.zxm-select) {
+    width: 100%;
+    height: 28px;
+  }
+
+  :deep(.zxm-select-selector) {
+    height: 28px !important;
+    padding: 0 24px 0 11px !important;
+    border: 1px solid #3ae1ff !important;
+    background-color: rgba(16, 64, 100, 0.85) !important;
+    box-shadow: 0 0 6px rgba(58, 225, 255, 0.25);
+    color: #fff !important;
+  }
+
+  :deep(.zxm-select-selection-search) {
+    inset-inline-start: 11px !important;
+  }
+
+  :deep(.zxm-select-selection-item),
+  :deep(.zxm-select-selection-placeholder) {
+    line-height: 26px !important;
+    font-size: 14px;
+    color: #fff !important;
+  }
+
+  :deep(.zxm-select-selection-placeholder) {
+    color: rgba(200, 236, 255, 0.45) !important;
+  }
+
+  :deep(.zxm-select-arrow) {
+    color: #3ae1ff !important;
+  }
+
+  :deep(.zxm-select-dropdown) {
+    padding: 4px 0;
+    background: rgba(16, 64, 100, 0.98);
+    border: 1px solid #3ae1ff;
+    box-shadow: 0 0 6px rgba(58, 225, 255, 0.25);
+  }
+
+  :deep(.zxm-select-item) {
+    padding: 6px 10px;
+    font-size: 14px;
+    color: #fff;
+  }
+
+  :deep(.zxm-select-item-option-active),
+  :deep(.zxm-select-item-option-selected) {
+    background: rgba(58, 225, 255, 0.2) !important;
+    color: #fff !important;
+  }
+}
+</style>

+ 57 - 16
src/views/vent/monitorManager/hsqHome/components/WarnBottom.vue

@@ -1,25 +1,52 @@
-<template>
-  <div class="echart-bar-new">
-    <div class="list-title">
-      报警趋势统计
+  <template>
+    <div class="echart-bar-new">
+      <div class="list-title">
+        报警趋势统计
+      </div>
+      <div ref="line" class="list-content"></div>
     </div>
-    <div ref="line" class="list-content"></div>
-  </div>
-</template>
+  </template>
 
 <script setup lang="ts">
 import { nextTick, onMounted, ref } from 'vue'
 import * as echarts from 'echarts';
+import { dscAlarmLogList } from '../hsqHome.api';
+import dayjs from 'dayjs';
 
+const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
 //获取dom元素节点
-let line = ref<any>();
+const line = ref<any>();
+/** 时间范围:[开始时间, 结束时间],默认近 7 天至当前时间 */
+const timeRange = ref<[string, string]>([
+  dayjs().subtract(7, 'day').format(DATE_FORMAT),
+  dayjs().format(DATE_FORMAT),
+])
+const xData = ref<any[]>([]);
+const yData = ref<any[]>([]);
+// 定时器引用,用于页面卸载时清理
+let timer: null | NodeJS.Timeout = null;
+/**
+ * 定时获取监测数据
+ * 
+ * @param flag 是否立即执行(默认false,延迟30秒执行)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getDscAlarmLogList();
+    if (timer) {
+      timer = null;
+    }
+    // 递归调用实现持续轮询
+    getMonitor();
+  }, flag ? 0 : 10000);
+}
 
 function getOption() {
   nextTick(() => {
     const myChart = echarts.init(line.value);
     let option = {
       grid: {
-        top: '8%',
+        top: '18%',
         left: '3%',
         bottom: '14%',
         right: '3%',
@@ -48,7 +75,7 @@ function getOption() {
         icon: 'path://M0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z',
         data: [
           {
-            name: '报警趋势',
+            name: '报警温度',
           },
         ],
       },
@@ -63,7 +90,7 @@ function getOption() {
             textStyle: {
               color: '#b3b8cc',
             },
-           
+
           },
           axisLine: {
             lineStyle: {
@@ -80,7 +107,7 @@ function getOption() {
           axisTick: {
             show: false,
           },
-          data: ['202t6-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05', '2026-01-06', '2026-01-07', '2026-01-08', '2026-01-09', '2026-01-10'],
+          data: xData.value || [],
         },
       ],
       yAxis: [
@@ -119,7 +146,7 @@ function getOption() {
       ],
       series: [
         {
-          name: '报警趋势',
+          name: '报警温度',
           type: 'line',
           smooth: false,
           showSymbol: false,
@@ -159,7 +186,7 @@ function getOption() {
               ),
             },
           },
-          data: [0.1, 0.2,0.4,  0.3, 0.7, 0.5, 0.9,0.6, 0.8, 0.5, 1],
+          data: yData.value || [],
         },
       ],
     };
@@ -170,8 +197,22 @@ function getOption() {
   });
 }
 
-onMounted(() => {
-  getOption();
+/**
+ * 获取双光谱摄像机报警记录-分页列表查询
+ * 调用 dscAlarmLogList 接口获取双光谱摄像机报警记录分页列表,
+ * 并将返回数据赋值给 warnList
+ */
+async function getDscAlarmLogList() {
+  const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000, startTime: timeRange.value?.[0], endTime: timeRange.value?.[1] });
+  if (res && res.records) {
+    xData.value = res.records.map(el => el.startTime)
+    yData.value = res.records.map(el => el.tempFlag)
+    getOption();
+  }
+}
+
+onMounted(async () => {
+  getMonitor(true);
 });
 </script>
 

+ 99 - 64
src/views/vent/monitorManager/hsqHome/components/WarnCenTop.vue

@@ -1,65 +1,72 @@
 <template>
   <div class="mixed-warn">
     <div class="list-title">
-      <span>设备详情-</span>
-      <span style="margin-left: 5px;">{{ deviceModel }}</span>
+      <span>报警详情-</span>
+      <span style="margin-left: 5px;">{{ warnData.preset || '-' }}</span>
     </div>
     <div class="list-t">
       <div class="basic-box">
         <div class="box-btn">
-          <div class="baisc-btn">
-            <div class="btn-item">确认报警</div>
-          </div>
-          <div class="baisc-btn">
-            <div class="btn-item">视频回放</div>
-          </div>
-          <div class="baisc-btn">
-            <div class="btn-item">联动处置</div>
-          </div>
-          <div class="baisc-btn">
-            <div class="btn-item">上报值班</div>
+          <div v-for="(btn,index) in actionBtns" :key="btn.key"
+            :class="['baisc-btn', { 'baisc-btn-active': activeBtn === btn.key }]" @click="changeStep(btn)">
+            <div class="btn-item">{{ btn.label }}</div>
           </div>
         </div>
-        <div class="echart-box"></div>
+        <div class="echart-box">
+           <img  :src="urlStr + '/sys/common/static/' + warnData.picName" />
+        </div>
       </div>
       <div class="basic-box">
         <basicBorder title="报警详情">
           <div class="warn-detail">
             <div class="warn-item">
-              <div class="item-label">报警级别 : </div>
-              <div class="item-val"
-                :style="{ color: warnDatas.alarmLevel == 101 ? '#00d8ff' : warnDatas.alarmLevel == 102 ? '#fcfc22' : warnDatas.alarmLevel == 103 ? '#ff7010' : warnDatas.alarmLevel == 104 ? '#df4e43' : warnDatas.alarmLevel == 201 ? '#ff0000' : '' }">
-                {{ warnDatas.alarmLevel == 101 ? '低风险' : warnDatas.alarmLevel ==
-                  102 ? '一般风险' :
-                  warnDatas.alarmLevel == 103 ? '较大风险' : warnDatas.alarmLevel == 104 ? '重大风险' : warnDatas.alarmLevel == 201 ?
-                    '报警' :
-                    '-' }}</div>
+              <div class="item-label">报警设备 : </div>
+              <div class="item-val text-val1">{{ warnData.deviceName || '-' }}</div>
             </div>
             <div class="warn-item">
-              <div class="item-label">报警时间 : </div>
-              <div class="item-val"> {{ warnDatas.starttime || '-' }}</div>
+              <div class="item-label">报警位置 : </div>
+              <div class="item-val text-val1"> {{ warnData.devicePos || '-' }}</div>
             </div>
             <div class="warn-item">
-              <div class="item-label">报警位置 : </div>
-              <div class="item-val"> {{ warnDatas.alarmName || '-' }}</div>
+              <div class="item-label">报警类型 : </div>
+              <div class="item-val text-val"> {{ warnData.eventType=='10001' ? '热源报警' : warnData.eventType=='10002' ? '闯入报警' : '-' }}</div>
+            </div>
+            <div class="warn-item">
+              <div class="item-label">红外类型 : </div>
+              <div class="item-val text-val"> {{ warnData.irModel || '-' }}</div>
             </div>
             <div class="warn-item">
-              <div class="item-label">预警设备 : </div>
-              <div class="item-val"> {{ warnDatas.deviceName || '-' }}</div>
+              <div class="item-label ">模式 : </div>
+              <!-- <div class="item-val text-val"> {{ `${warnDatas.temp}℃` || '-' }}</div> -->
+              <div class="item-val text-val"> {{ warnData.mode || '-' }}</div>
             </div>
             <div class="warn-item">
-              <div class="item-label ">预警温度 : </div>
-              <div class="item-val text-val"> {{ `${warnDatas.temp}℃` || '-' }}</div>
+              <div class="item-label">红外水平像素数量 : </div>
+              <div class="item-val text-val1"> {{ warnData.numOfIrHorPixs || '-' }}</div>
+            </div>
+            <div class="warn-item">
+              <div class="item-label">红外垂直像素数量 : </div>
+              <div class="item-val text-val1"> {{ warnData.numOfIrVerPixs || '-' }}</div>
+            </div>
+            <div class="warn-item">
+              <div class="item-label">云台水平角 : </div>
+              <div class="item-val text-val1"> {{ warnData.ptzHorAngle || '-' }}</div>
+            </div>
+            <div class="warn-item">
+              <div class="item-label">云台垂直角 : </div>
+              <div class="item-val text-val1"> {{ warnData.ptzVerAngle || '-' }}</div>
+            </div>
+             <div class="warn-item">
+              <div class="item-label">测温标记 : </div>
+              <div class="item-val text-val1"> {{ warnData.tempFlag || '-' }}</div>
             </div>
           </div>
         </basicBorder>
       </div>
     </div>
-    <div class="list-c">
-      <basicBorder title="报警地图位置">
-
-      </basicBorder>
-    </div>
+    <!-- <div class="list-c">
+      <basicBorder title="报警地图位置"></basicBorder>
+    </div> -->
     <div class="list-b">
       <basicBorder title="处理流程">
         <div class="process-flow-box">
@@ -74,8 +81,8 @@
             </div>
           </div>
           <div class="flow-step">
-            <a-steps progress-dot :current="1">
-              <a-step title="" description="" v-for="(item, index) in 5" :key="index" />
+            <a-steps progress-dot :current="currentStep">
+              <a-step title="" description="" v-for="(item, index) in picList" :key="index" />
             </a-steps>
           </div>
         </div>
@@ -85,35 +92,49 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, watch } from 'vue'
+import { ref } from 'vue'
 import basicBorder from './basicBorder.vue'
-import { getAssetURL } from '/@/utils/ui';
+import { picList, actionBtns } from '../hsqHome.data'
+import { useGlobSetting } from '/@/hooks/setting';
+import { updateProcessState } from '../hsqHome.api'
+import { useMessage } from '/@/hooks/web/useMessage';
 
-let props = defineProps({
+const props = defineProps({
   warnData: {
     type: Object,
     required: true
   }
 })
 
-let deviceModel = ref('-')
-let warnDatas = ref({}) as any
-let picList = ref([
-  { src: getAssetURL('home-container/configurable/hsq/4-1.png'), label: '1.报警触发' },
-  { src: getAssetURL('home-container/configurable/hsq/6-1.png'), label: '2.系统确认' },
-  { src: getAssetURL('home-container/configurable/hsq/6-2.png'), label: '3.通知值班' },
-  { src: getAssetURL('home-container/configurable/hsq/4-4.png'), label: '4.现场处置' },
-  { src: getAssetURL('home-container/configurable/hsq/4-5.png'), label: '5.确认解除' },
-])
-
-watch(() => props.warnData, (newVal) => {
-  let data = JSON.parse(JSON.stringify(newVal.autoContent))
-  let obj = {
-    deviceName: data.substring(data.indexOf('【') + 1, data.indexOf('】')),
-    temp: data.substring(data.lastIndexOf('【') + 1, data.lastIndexOf('】')),
+/** 当前步骤 */
+const currentStep = ref(0)
+/** 当前激活的处置按钮 */
+const activeBtn = ref('')
+const globSetting = useGlobSetting();
+const baseUploadUrl = globSetting.uploadUrl;
+const { createMessage } = useMessage();
+let urlStr = '';
+  if (import.meta.env.PROD) {
+    urlStr = VUE_APP_URL.baseUrl;
+  } else {
+    urlStr = baseUploadUrl || '';
+  }
+
+  /** 切换步骤 */
+  function changeStep(btn: any) {
+    currentStep.value = btn.key -1
+    activeBtn.value = btn.key
+    getUpdateWarnStatus()
+  }
+
+  /** 更新报警状态 */
+  async function getUpdateWarnStatus() {
+    let res = await updateProcessState({
+     id:props.warnData.id,
+     processState:currentStep.value,
+    })
+   createMessage.success(res.msg || '更新成功')
   }
-  warnDatas.value = Object.assign({}, newVal, obj)
-})
 
 </script>
 
@@ -154,7 +175,8 @@ watch(() => props.warnData, (newVal) => {
   .list-t {
     display: flex;
     justify-content: space-between;
-    height: 235px;
+    // height: 235px;
+    height: 380px;
     margin-bottom: 10px;
   }
 
@@ -185,6 +207,16 @@ watch(() => props.warnData, (newVal) => {
     border-radius: 2px;
     padding: 3px;
     cursor: pointer;
+    transition: border-color 0.2s, box-shadow 0.2s;
+
+    &.baisc-btn-active {
+      border-color: #90cff3;
+      box-shadow: 0 0 6px rgba(1, 254, 252, 0.45);
+
+      .btn-item {
+        background-color: #2484bc;
+      }
+    }
   }
 
   .btn-item {
@@ -194,17 +226,23 @@ watch(() => props.warnData, (newVal) => {
     width: 100%;
     height: 100%;
     background-color: rgba(32, 166, 169);
+    transition: background-color 0.2s;
   }
 
   .echart-box {
     height: calc(100% - 44px);
     background-color: #114268;
-    background: var(--image-box-bg7) no-repeat;
-    background-size: 100% 100%;
+    // background: var(--image-box-bg7) no-repeat;
+    // background-size: 100% 100%;
+    img {
+      width: 100%;
+      height: 100%;
+    }
   }
 
   .warn-detail {
     height: 100%;
+    overflow-y: auto;
   }
 
   .warn-item {
@@ -226,16 +264,13 @@ watch(() => props.warnData, (newVal) => {
     // font-size: 13px;
   }
 
-  .item-val {
-    // font-size: 13px;
-  }
 
   .text-val {
     color: #fc002a;
   }
 
   .text-val1 {
-    color: orange;
+    color: rgb(247, 169, 24);
   }
 
   .list-c {

+ 115 - 76
src/views/vent/monitorManager/hsqHome/components/WarnLeftTop.vue

@@ -1,11 +1,13 @@
 <!-- 报警信息左侧顶部组件:包含报警搜索、报警列表展示及详情查看功能 -->
 <template>
   <div class="search-table">
+      <div class="list-title"> 报警信息 </div>
     <!-- 搜索区域:包含搜索输入框和筛选按钮 -->
     <div class="search-area">
-      <a-input v-model:value="searchWarn" placeholder="搜索报警" size="small" />
-      <div class="search-btn">
-        <div class="btn-item">筛选</div>
+      <div class="time-range">
+        <a-range-picker v-model:value="timeRange" class="alarm-range-picker" show-time
+          value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" :placeholder="['开始时间', '结束时间']"
+          :allow-clear="false" @change="getDscAlarmLogList" />
       </div>
     </div>
     <!-- 内容区域:包含表头和表格数据 -->
@@ -16,26 +18,18 @@
       </div>
       <!-- 表格数据列表:遍历 tableData 渲染每行报警信息 -->
       <div class="table-content">
-        <div class="content-item" v-for="(item, index) in tableData" :key="index">
+        <div class="content-item" :class="{ 'content-item-active': activeIndex == index }"
+          v-for="(item, index) in tableData" :key="index">
           <div class="item-text" v-for="(ite, ind) in warnTitle" :key="ind">
-            <!-- 报警时间列 -->
-            <div v-if="ite.value == 'starttime'">{{ item[ite.value] }}</div>
-            <!-- 报警名称列 -->
-            <div v-if="ite.value == 'alarmName'">{{ item[ite.value] }}</div>
-            <!-- 报警等级列:根据不同等级码显示对应颜色和文字 -->
-            <!-- 101-低风险(蓝色) 102-一般风险(黄色) 103-较大风险(橙色) 104-重大风险(红色) 201-报警(深红色) -->
-            <div v-if="ite.value == 'alarmLevel'"
-              :style="{ color: item[ite.value] == 101 ? '#00d8ff' : item[ite.value] == 102 ? '#fcfc22' : item[ite.value] == 103 ? '#ff7010' : item[ite.value] == 104 ? '#df4e43' : item[ite.value] == 201 ? '#ff0000' : '' }">
-              {{ item[ite.value] == 101 ? '低风险' : item[ite.value] ==
-                102 ? '一般风险' :
-                item[ite.value] == 103 ? '较大风险' : item[ite.value] == 104 ? '重大风险' : item[ite.value] == 201 ? '报警' :
-                  '低风险' }}</div>
-            <!-- 处理状态列:0-未处理(红色) 其他-已处理(绿色) -->
-            <div v-if="ite.value == 'isok'" :style="{ color: item[ite.value] === 0 ? '#fd0404' : '#0eca74' }">{{
-              item[ite.value] === 0 ? '未处理' : '已处理' }}</div>
+            <!-- 设备名称列 -->
+            <div v-if="ite.value == 'deviceName'">{{ item[ite.value] }}</div>
+            <!-- 安装位置列 -->
+            <div v-if="ite.value == 'devicePos'">{{ item[ite.value] }}</div>
+            <!-- 报警类型列 -->
+            <div v-if="ite.value == 'eventType'">{{ item[ite.value] }}</div>
             <!-- 操作列:点击"详情"触发详情查看 -->
             <div v-if="ite.value == 'operation'">
-              <span :class="activeIndex == index ? 'text-look-active' : 'text-look'"
+              <span class="text-look"
                 @click="handlerDetail(item, index)">详情</span>
             </div>
           </div>
@@ -48,32 +42,71 @@
 <script setup lang="ts">
 import { onMounted, ref, watch, type PropType } from 'vue'
 import { warnTitle } from '../hsqHome.data'
+import dayjs from 'dayjs'
+import { dscAlarmLogList } from '../hsqHome.api'
 
-// 接收父组件传入的报警数据列表
-let props = defineProps({
-  tableData: {
-    type: Array as PropType<any[]>,
-    default: () => [],
-  },
-})
-
+const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
 let activeIndex = ref(0)
-// 报警搜索关键词
-let searchWarn = ref('')
+/** 时间范围:[开始时间, 结束时间],默认近 7 天至当前时间 */
+const timeRange = ref<[string, string]>([
+  dayjs().subtract(7, 'day').format(DATE_FORMAT),
+  dayjs().format(DATE_FORMAT),
+])
+// 告警列表数据
+let tableData = ref<any[]>([])
 // 定义事件:detail - 查看报警详情
 let $emit = defineEmits(['detail'])
 
+
+// 定时器引用,用于页面卸载时清理
+let timer: null | NodeJS.Timeout = null;
+/**
+ * 定时获取监测数据
+ * 
+ * @param flag 是否立即执行(默认false,延迟30秒执行)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    // 获取告警列表数据
+    await getDscAlarmLogList();
+    if (timer) {
+      timer = null;
+    }
+    // 递归调用实现持续轮询
+    getMonitor();
+  }, flag ? 0 : 10000);
+}
+
+/**
+ * 获取双光谱摄像机报警记录-分页列表查询
+ * 调用 dscAlarmLogList 接口获取双光谱摄像机报警记录分页列表,
+ * 并将返回数据赋值给 warnList
+ */
+async function getDscAlarmLogList() {
+  const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000, startTime: timeRange.value?.[0], endTime: timeRange.value?.[1] });
+  if (res && res.records) {
+    tableData.value = res.records.map(el=>{
+      return {
+        ...el,
+        // 将数字类型的报警等级映射为中文描述
+        eventTypeC: el.eventType == 10001 ? '热源报警' : el.eventType ==   10002	 ? '闯入报警' : '-'  
+      }
+    })
+    $emit('detail', tableData.value[activeIndex.value])
+  }
+}
+
 // 点击"详情"按钮,向父组件派发 detail 事件并传递当前行数据
 function handlerDetail(item: any, index: number) {
   activeIndex.value = index
   $emit('detail', item)
 }
 
-watch(() => props.tableData, (newVal) => {
-  if (newVal.length > 0) {
-    handlerDetail(newVal[0], 0)
-  }
-}, { immediate: true })
+onMounted(() => {
+  getDscAlarmLogList();
+  getMonitor(true);
+})
+
 </script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
@@ -84,6 +117,7 @@ watch(() => props.tableData, (newVal) => {
 
 /* 整体容器:定义背景图片变量及基础布局 */
 .search-table {
+   --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
   --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-6.png');
   --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-24.png');
   position: relative;
@@ -92,39 +126,62 @@ watch(() => props.tableData, (newVal) => {
   padding: 15px;
   box-sizing: border-box;
 }
+.list-title {
+    width: 177px;
+    height: 30px;
+    line-height: 22px;
+    color: #01fefc;
+    font-weight: bolder;
+    background: var(--image-box-bg) no-repeat;
+    background-size: 80% 100%;
+    margin-bottom: 5px;
+  }
+
 
 /* 搜索区域:输入框与筛选按钮水平排列 */
 .search-area {
   height: 42px;
   display: flex;
-  justify-content: space-between;
+  justify-content: center;
   align-items: center;
   margin-bottom: 5px;
 }
 
-/* 筛选按钮外框 */
-.search-btn {
-  width: 85px;
-  height: 30px;
-  border: 1px solid #01fefc;
-  border-radius: 4px;
-  padding: 3px;
-  cursor: pointer;
+.time-range {
+  width: 100%;
 }
 
-/* 筛选按钮内部填充 */
-.btn-item {
-  display: flex;
-  justify-content: center;
-  align-items: center;
+.alarm-range-picker {
   width: 100%;
-  height: 100%;
-  background-color: rgba(32, 166, 169);
+}
+
+:deep(.zxm-picker) {
+  width: 100%;
+  height: 32px;
+  border: 1px solid #3ae1ff !important;
+  background-color: rgba(16, 64, 100, 0.85) !important;
+  box-shadow: 0 0 6px rgba(58, 225, 255, 0.25);
+}
+
+:deep(.zxm-picker-input > input) {
+  color: #fff !important;
+  font-size: 14px;
+  text-align: center;
+}
+
+:deep(.zxm-picker-input > input::placeholder) {
+  color: rgba(200, 236, 255, 0.45) !important;
+}
+
+:deep(.zxm-picker-separator),
+:deep(.zxm-picker-suffix),
+:deep(.zxm-picker-clear) {
+  color: #3ae1ff !important;
 }
 
 /* 内容区域:占据搜索区域以下的所有空间 */
 .content-area {
-  height: calc(100% - 47px);
+  height: calc(100% - 82px);
 }
 
 /* 表头行:使用背景图片装饰 */
@@ -141,7 +198,6 @@ watch(() => props.tableData, (newVal) => {
 /* 表头列项:统一居中,青色文字,按列分配 flex 比例 */
 .title-item {
   display: flex;
-
   justify-content: center;
   align-items: center;
   color: #01fefc;
@@ -161,10 +217,6 @@ watch(() => props.tableData, (newVal) => {
   &:nth-child(4) {
     flex: 1;
   }
-
-  &:nth-child(5) {
-    flex: 1;
-  }
 }
 
 /* 表格内容区:可纵向滚动 */
@@ -189,6 +241,12 @@ watch(() => props.tableData, (newVal) => {
   &:nth-child(even) {
     background-color: #114268;
   }
+
+  /* 详情激活时的行选中高亮,边框色与详情按钮激活态一致 */
+  &.content-item-active {
+    background-color: rgba(36, 132, 188, 0.35) !important;
+    box-shadow: inset 0 0 0 1px #90cff3;
+  }
 }
 
 /* 数据单元格:居中对齐,按列分配 flex 比例(与表头一致) */
@@ -214,10 +272,6 @@ watch(() => props.tableData, (newVal) => {
     flex: 1;
   }
 
-  &:nth-child(5) {
-    flex: 1;
-  }
-
 }
 
 /* "详情"操作按钮样式 */
@@ -238,21 +292,6 @@ watch(() => props.tableData, (newVal) => {
   cursor: pointer;
 }
 
-/* 搜索输入框:自定义背景图片,去除默认边框 */
-.zxm-input {
-  height: 42px;
-  color: #fff;
-  background-color: transparent;
-  border: none;
-  background: var(--image-box-bg1) no-repeat;
-  background-size: 100% 100%;
-}
-
-/* 小尺寸输入框内边距 */
-.zxm-input-sm {
-  padding: 0px 20px;
-}
-
 /* 隐藏滚动条 */
 ::-webkit-scrollbar {
   display: none;

+ 33 - 35
src/views/vent/monitorManager/hsqHome/fireMonitor.vue

@@ -3,7 +3,7 @@
     <div class="basic-fire-box">
       <div class="box-top">
         <CommonBd title="设备状态总览">
-          <CustomGalleryNew :option="option" :galleryData="galleryData" ></CustomGalleryNew>
+          <CustomGalleryNew :option="option" :galleryData="galleryData"></CustomGalleryNew>
         </CommonBd>
       </div>
       <div class="box-cen">
@@ -20,26 +20,28 @@
     <div class="basic-fire-box1">
       <!-- 模型区域 -->
       <div class="model-box">
-        <dwgViewer :devices="tableData"/>
+        <dwgViewer :devices="tableData" />
       </div>
-      <div class="box1-bot">
+      <!-- <div class="box1-bot">
         <CommonBd title="快捷操作">
           <QuickAction :quick-option="quickOption" :btn-option="btnOption" :quick-data="quickData"></QuickAction>
         </CommonBd>
-      </div>
+      </div> -->
     </div>
     <div class="basic-fire-box">
       <div class="box-top1">
         <CommonBd title="报警监控">
-          <AlarmMonitor :option="alarmOption" :cardData="alarmData"></AlarmMonitor>
+          <!-- <AlarmMonitor :option="alarmOption" :cardData="alarmData"></AlarmMonitor> -->
+          <AlarmMonitor ></AlarmMonitor>
         </CommonBd>
       </div>
       <div class="box-cen1">
         <!-- <CommonBd title="区域温度统计">
           <CustomChart :chart-config="chartTempConfig" :chart-data="chartTempData"></CustomChart>
         </CommonBd> -->
-        <CommonBd title="实时温度曲线">
-          <CustomChart :chart-config="chartConfig" :chart-data="chartData"></CustomChart>
+        <CommonBd title="历史温度曲线">
+          <HistoryLine :deviceOptions="deviceOptions"></HistoryLine>
+          <!-- <CustomChart :chart-config="chartConfig" :chart-data="chartData"></CustomChart> -->
         </CommonBd>
       </div>
       <div class="box-bot1">
@@ -54,9 +56,9 @@
 import { onMounted, onUnmounted, ref, computed, nextTick, onBeforeMount, watch, reactive, inject, Ref } from 'vue';
 import CommonBd from './components/CommonBd.vue';
 import CustomGalleryNew from './components/CustomGalleryNew.vue';
-import CustomChart from './components/CustomChart.vue';
 import CommonTable from './components/CommonTable.vue';
 import AlarmMonitor from './components/AlarmMonitor.vue';
+import HistoryLine from './components/HistoryLine.vue';
 import EnvironmentMonitor from './components/EnvironmentMonitor.vue';
 import QuickAction from './components/QuickAction.vue';
 import dwgViewer from '../../cad/dwgViewerTest.vue';
@@ -73,23 +75,10 @@ let tableData = ref<any[]>([])
 let tableDataGp = ref<any[]>([])
 /** 火灾探测器表格数据 */
 let tableDataHz = ref<any[]>([])
-/** 实时温度曲线图表数据 */
-let chartData = ref<any[]>([])
-/** 报警监控卡片数据 */
-let alarmData = ref<any>({
-  dayWarn: 0,
-  warnOk: 0,
-  warnUnOk: 0
-})
-
-
-let chartTempData = [
-  { x: '1', y: 12 },
-  { x: '2', y: 44 },
-  { x: '3', y: 44 },
-  { x: '4', y: 20 },
-]
-
+// /** 实时温度曲线图表数据 */
+// let chartData = ref<any[]>([])
+/** 历史温度曲线设备选项配置 */
+let deviceOptions = ref<any[]>([])
 /** 环境监测设备选项配置 */
 let envOption = ref<any[]>([])
 /** 环境监测数据 */
@@ -127,7 +116,7 @@ function getMonitor(flag?: boolean) {
  * 调用 getManagesysDeviceNum 接口,根据当前系统ID获取设备数量统计信息
  */
 async function getManagesysDeviceNumTj() {
-  const res = await getManagesysDeviceNum({ sysId: systemId.value,deviceKinds:"duaSpeCamera" });
+  const res = await getManagesysDeviceNum({ sysId: systemId.value, deviceKinds: "duaSpeCamera" });
   galleryData.value = res || {};
 }
 /**
@@ -139,13 +128,18 @@ async function getManagesysDeviceNumTj() {
 async function getTableData() {
   const res = await monitorSystem({ type: 'all', systemID: systemId.value, devicetype: '' });
   tableData.value = [...res.deviceInfo.duaSpeCamera.datalist, ...res.deviceInfo.imgFireDet.datalist]
-  tableDataGp.value = res.deviceInfo.duaSpeCamera.datalist || [];
-  tableDataHz.value = res.deviceInfo.imgFireDet.datalist || [];
-  chartData.value = tableData.value.map(v => ({
-    pos: v.strname,
-    value: v.readData.temperature
+  tableDataGp.value = res.deviceInfo.duaSpeCamera.datalist.map(el => Object.assign({}, el, el.readData)) || [];
+  tableDataHz.value = res.deviceInfo.imgFireDet.datalist.map(el => Object.assign({}, el, el.readData)) || [];
+  // chartData.value = tableData.value.map(v => ({
+  //   pos: v.strname,
+  //   value: v.readData.temperature
+  // }))
+  deviceOptions.value = tableData.value.map(v => ({
+    label: v.strname,
+    value: v.deviceID,
+    devicetype: v.deviceType
   }))
-  console.log(chartData.value, 'chartData.value')
+  // console.log(chartData.value, 'chartData.value')
 }
 
 /**
@@ -158,7 +152,7 @@ async function getDeviceMonitor() {
     return {
       label: el.datalist[0].typeName,
       value: Object.keys(el.datalist[0].readData).filter(v => v.endsWith('val') || v.endsWith('ture'))[0],
-      unit: el.datalist[0].typeName=='温度传感器' ? '℃' : el.datalist[0].typeName=='氧气传感器' ? '%' : el.datalist[0].typeName=='CO传感器' ? 'ppm' : el.datalist[0].typeName=='瓦斯传感器' ? '%' : el.datalist[0].typeName=='烟雾传感器' ? '' : ''
+      unit: el.datalist[0].typeName == '温度传感器' ? '℃' : el.datalist[0].typeName == '氧气传感器' ? '%' : el.datalist[0].typeName == 'CO传感器' ? 'ppm' : el.datalist[0].typeName == '瓦斯传感器' ? '%' : el.datalist[0].typeName == '烟雾传感器' ? '' : ''
     }
   })
   envData = Object.assign({}, ...res.msgTxt.filter(v => v.type.indexOf('modelsensor') != -1).map(el => el.datalist[0].readData))
@@ -232,9 +226,10 @@ onUnmounted(() => {
     top: 63px;
     left: 0;
     width: 100%;
-    height: 615px;
+    // height: 615px;
+    height: calc(100% - 63px);
     border: 1px solid rgba(40, 165, 232, 0.8);
-    margin-bottom: 10px;
+    // margin-bottom: 10px;
   }
 
   .box1-bot {
@@ -250,6 +245,7 @@ onUnmounted(() => {
     margin-bottom: 10px;
 
   }
+
   .box-top1 {
     height: 360px;
     margin-bottom: 10px;
@@ -259,6 +255,7 @@ onUnmounted(() => {
     height: 310px;
     margin-bottom: 10px;
   }
+
   .box-cen1 {
     height: 220px;
     margin-bottom: 10px;
@@ -267,6 +264,7 @@ onUnmounted(() => {
   .box-bot {
     height: 335px;
   }
+
   .box-bot1 {
     height: 250px;
   }

+ 21 - 0
src/views/vent/monitorManager/hsqHome/hsqHome.api.ts

@@ -16,6 +16,9 @@ enum Api {
   getAlarmLogList = '/monitor/groupCompany/getAlarmLogList',  // 设备预警历史查询
   warningList = '/safety/managesysAlarmInfo/list',//预警条目list
   dscAlarmLogList = '/safety/dscAlarmLog/list',//设备预警历史查询
+  listdays = '/safety/ventanalyMonitorData/listdays',//详情历史曲线
+  duaSpeCameraControl = '/safety/ventanalyMonitorData/duaSpeCameraControl',//设备控制
+  updateProcessState = '/safety/dscAlarmLog/updateProcessState',//更新处理状态
   
 }
 /**
@@ -89,3 +92,21 @@ export const warningList = (params) => defHttp.get({ url: Api.warningList, param
 * @param params
 */
 export const dscAlarmLogList = (params) => defHttp.get({ url: Api.dscAlarmLogList, params });
+
+/**
+* 详情历史曲线
+* @param params
+*/
+export const listdays = (params) => defHttp.get({ url: Api.listdays, params }, { joinParamsToUrl: true });
+
+/**
+* 设备控制接口
+* @param params
+*/
+export const duaSpeCameraControl = (params) => defHttp.post({ url: Api.duaSpeCameraControl, params });
+
+/**
+* 更新处理状态接口
+* @param params
+*/
+export const updateProcessState = (params) => defHttp.post({ url: Api.updateProcessState, params });

+ 74 - 20
src/views/vent/monitorManager/hsqHome/hsqHome.data.ts

@@ -1,3 +1,4 @@
+import { getAssetURL } from '/@/utils/ui';
 /** Tab 标签项数据结构 */
 interface TabItem {
   key: string
@@ -34,17 +35,17 @@ export let option = [
 export const chartConfig: any = {
   type: 'line_area',
   grid: {
-    top: 40,
-    left: 30,
-    bottom: 14,
-    right: 30,
+    top: 25,
+    left: 25,
+    bottom: 10,
+    right: 25,
   },
-  legend: { show: false },
+  legend: { show: true },
   xAxis: [{ show: true }],
-  yAxis: [{ show: true, name: '(℃)', position: 'left' }],
+  yAxis: [{ show: true, name: '(℃)', position: 'left', nameTextStyle: { color: '#fff', fontSize: 12, lineHeight: 2, } }],
   series: [
     {
-      label: '温度',
+      label: '平均温度',
       readFrom: '',
       xprop: 'pos',
       yprop: 'value',
@@ -64,8 +65,9 @@ export let deviceColumns: any[] = [
     prop: 'netStatus',
   },
   {
-    name: '报警状态',
-    prop: 'syswarnLevel',
+    name: '温度(‌℃‌)',
+    //prop: 'syswarnLevel',
+    prop: 'temperature'
   },
 ]
 
@@ -177,25 +179,62 @@ export let dayLoginConfig: any = {
 
 //报警列表表头
 export let warnTitle = [
-  { label: '时间', value: 'starttime' },
-  { label: '位置', value: 'alarmName' },
-  { label: '级别', value: 'alarmLevel' },
-  { label: '状态', value: 'isok' },
+  { label: '设备名称', value: 'deviceName' },
+  { label: '安装位置', value: 'devicePos' },
+  { label: '报警类型', value: 'eventTypeC' },
   { label: '操作', value: 'operation' },
 ]
 
 export let alarmColumns: any[] = [
   {
-    name: '时间',
-    prop: 'starttime',
+    name: '设备名称',
+    prop: 'deviceName',
   },
   {
-    name: '位置',
-    prop: 'alarmName',
+    name: '安装位置',
+    prop: 'devicePos',
   },
   {
-    name: '报警级别',
-    prop: 'alarmLevelC',
+    name: '报警类型',
+    prop: 'eventTypeC',
+  },
+]
+export let dscAlarmLogColumns: any[] = [
+  {
+    name: '设备名称',
+    prop: 'deviceName',
+  },
+  {
+    name: '安装位置',
+    prop: 'devicePos',
+  },
+  {
+    name: '报警类型',
+    prop: 'eventTypeC',
+  },
+  {
+    name: '红外类型',
+    prop: 'irModel',
+  },
+  {
+    name: '模式',
+    prop: 'mode',
+  },
+  {
+    name: '红外水平像素',
+    prop: 'numOfIrHorPixs',
+  },
+  {
+    name: '红外垂直像素',
+    prop: 'numOfIrVerPixs',
+  },
+  {
+    name: '云台水平角',
+    prop: 'ptzHorAngle',
+  },
+  {
+    name: '云台垂直角',
+    prop: 'ptzVerAngle',
   },
 ]
 
@@ -252,7 +291,7 @@ export let videoColumns: any[] = [
   },
   {
     name: '报警类型',
-    prop: 'eventType',
+    prop: 'eventTypeC',
   },
   {
     name: '红外类型',
@@ -314,3 +353,18 @@ export const lensControls: LensControl[] = [
     icon: `<svg viewBox="0 0 24 24" width="18" height="18"><circle cx="12" cy="12" r="8" fill="none" stroke="#fff" stroke-width="1.4"/><path d="M12 4 L14.5 10.5 L21 12 L14.5 13.5 L12 20 L9.5 13.5 L3 12 L9.5 10.5 Z" fill="none" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/></svg>`,
   },
 ]
+
+export let picList = [
+  { src: getAssetURL('home-container/configurable/hsq/4-1.png'), label: '1.报警触发' },
+  { src: getAssetURL('home-container/configurable/hsq/6-1.png'), label: '2.系统确认' },
+  { src: getAssetURL('home-container/configurable/hsq/6-2.png'), label: '3.通知值班' },
+  { src: getAssetURL('home-container/configurable/hsq/4-4.png'), label: '4.现场处置' },
+  { src: getAssetURL('home-container/configurable/hsq/4-5.png'), label: '5.确认解除' },
+]
+/** 处置流程按钮 */
+export const actionBtns = [
+  { key: 2, label: '系统确认' },
+  { key: 3, label: '通知值班' },
+  { key: 4, label: '现场处置' },
+  { key: 5, label: '确认解除' },
+]

+ 8 - 2
src/views/vent/monitorManager/hsqHome/videoPlayer.vue

@@ -16,7 +16,7 @@
     <div class="right-box">
       <!-- 右上:设备画面组件 -->
       <div class="basic-top-right">
-        <DeviceView />
+        <DeviceView :deviceId="deviceId" />
       </div>
       <!-- 右下:设备告警列表组件 -->
       <div class="basic-bottom-right">
@@ -58,6 +58,8 @@ const treeData = ref<GroupNode[]>([])
 const deviceOptions = ref<any[]>([])
 // 双光谱摄像机报警记录-分页列表查询
 const warnList = ref<any[]>([])
+// 当前选中的设备ID
+const deviceId = ref('')
 /** 定时获取监测数据定时器 */
 let timer: null | NodeJS.Timeout = null;
 
@@ -127,6 +129,7 @@ async function getTableData() {
     deviceOptions.value = data.map(d => ({
       value: d.deviceID,
       label: d.strname,
+      deviceType:d.deviceType
     }))
   }
 
@@ -140,7 +143,10 @@ async function getTableData() {
 async function getDscAlarmLogList() {
   const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000 });
   if (res && res.records) {
-    warnList.value = res.records
+    warnList.value = res.records.map(el => ({
+      ...el,
+      eventTypeC: el.eventType === 10001 ? '热源报警' : el.eventType==10002 ? '闯入报警' : '',
+    }))
   }
 }
 

+ 22 - 41
src/views/vent/monitorManager/hsqHome/warnMonitor.vue

@@ -5,7 +5,7 @@
     <div class="warn-top-box">
       <!-- 左侧面板:告警列表 -->
       <div class="baisc-box">
-        <WarnLeftTop :tableData="tableData" @detail="handlerDetail"></WarnLeftTop>
+        <WarnLeftTop  @detail="handlerDetail"></WarnLeftTop>
       </div>
       <!-- 中间面板:告警详情展示 -->
       <div class="baisc-box">
@@ -30,40 +30,21 @@ import WarnRightTop from './components/WarnRightTop.vue'
 import WarnBottom from './components/WarnBottom.vue'
 import { autoLogList, managesysList } from './hsqHome.api'
 
-// 告警列表数据
-let tableData = ref<any[]>([])
+
 // 当前选中的告警详情数据
 let warnData = ref({})
-// 系统ID,用于筛选数据
-let systemId = ref('')
+// // 系统ID,用于筛选数据
+// let systemId = ref('')
 
-// 定时器引用,用于页面卸载时清理
-let timer: null | NodeJS.Timeout = null;
-/**
- * 定时获取监测数据
- * 
- * @param flag 是否立即执行(默认false,延迟30秒执行)
- */
-function getMonitor(flag?: boolean) {
-  timer = setTimeout(async () => {
-    // 获取告警列表数据
-    await getWarnList();
-    if (timer) {
-      timer = null;
-    }
-    // 递归调用实现持续轮询
-    getMonitor();
-  }, flag ? 0 : 30000);
-}
 
-/**
- * 获取告警列表数据
- * 根据systemId筛选告警记录,最多返回100条
- */
-async function getWarnList() {
-  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: systemId.value });
-  tableData.value = res.records || []
-}
+// /**
+//  * 获取告警列表数据
+//  * 根据systemId筛选告警记录,最多返回100条
+//  */
+// async function getWarnList() {
+//   let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: systemId.value });
+//   tableData.value = res.records || []
+// }
 
 /**
  * 处理告警详情点击事件
@@ -79,23 +60,23 @@ function handlerDetail(item: any) {
  * 获取系统列表并设置默认系统ID
  * 用于初始化时获取systemId,供后续接口调用使用
  */
-async function getManagesysList() {
-  const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
-  if (res && res.records) {
-    systemId.value = res.records[0].id
-  }
-}
+// async function getManagesysList() {
+//   const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
+//   if (res && res.records) {
+//     systemId.value = res.records[0].id
+//   }
+// }
 
 // 页面加载时初始化数据并启动定时轮询
 onMounted(async() => {
-   await getManagesysList();
-   getMonitor(true);
+  //  await getManagesysList();
+   
 });
 
 // 页面卸载时清理定时器,防止内存泄漏
 onUnmounted(() => {
-  timer = null
-  clearTimeout(timer);
+  // timer = null
+  // clearTimeout(timer);
 });
 </script>