Przeglądaj źródła

红沙泉露天煤矿火灾监测预警系统-控制功能接口对接提交

lxh 7 godzin temu
rodzic
commit
fdee51f1e3
22 zmienionych plików z 2120 dodań i 517 usunięć
  1. BIN
      src/assets/images/home-container/configurable/hsq/1959931786226a884ecc792d3b7174bc.png
  2. BIN
      src/assets/images/home-container/configurable/hsq/device-item-active-no-hline.png
  3. BIN
      src/assets/images/home-container/configurable/hsq/device-item-active-v2.png
  4. BIN
      src/assets/images/home-container/configurable/hsq/device-item-active.png
  5. BIN
      src/assets/images/home-container/configurable/hsq/device-list-selected-ref.png
  6. 19 2
      src/views/vent/deviceManager/tableColumns/index.vue
  7. 20 6
      src/views/vent/monitorManager/hsqHome/components/AlarmDay.vue
  8. 22 6
      src/views/vent/monitorManager/hsqHome/components/AlarmMonth.vue
  9. 16 14
      src/views/vent/monitorManager/hsqHome/components/AlarmPoint.vue
  10. 537 106
      src/views/vent/monitorManager/hsqHome/components/DeviceCenter.vue
  11. 330 71
      src/views/vent/monitorManager/hsqHome/components/DeviceControl.vue
  12. 69 49
      src/views/vent/monitorManager/hsqHome/components/DeviceLeft.vue
  13. 192 103
      src/views/vent/monitorManager/hsqHome/components/DeviceRight.vue
  14. 750 97
      src/views/vent/monitorManager/hsqHome/components/DeviceTree.vue
  15. 15 9
      src/views/vent/monitorManager/hsqHome/components/DeviceView.vue
  16. 49 5
      src/views/vent/monitorManager/hsqHome/components/HistoryFilterTree.vue
  17. 45 0
      src/views/vent/monitorManager/hsqHome/components/type.ts
  18. 20 24
      src/views/vent/monitorManager/hsqHome/deviceManger.vue
  19. 23 6
      src/views/vent/monitorManager/hsqHome/historyReport.vue
  20. 8 1
      src/views/vent/monitorManager/hsqHome/hsqHome.api.ts
  21. 3 3
      src/views/vent/monitorManager/hsqHome/hsqHome.data.ts
  22. 2 15
      src/views/vent/monitorManager/hsqHome/videoPlayer.vue

BIN
src/assets/images/home-container/configurable/hsq/1959931786226a884ecc792d3b7174bc.png


BIN
src/assets/images/home-container/configurable/hsq/device-item-active-no-hline.png


BIN
src/assets/images/home-container/configurable/hsq/device-item-active-v2.png


BIN
src/assets/images/home-container/configurable/hsq/device-item-active.png


BIN
src/assets/images/home-container/configurable/hsq/device-list-selected-ref.png


+ 19 - 2
src/views/vent/deviceManager/tableColumns/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="device-manager-box">
+  <div class="device-manager-box table-columns-page">
     <NormalTable
       :columns="columns"
       :searchFormSchema="searchFormSchema"
@@ -24,4 +24,21 @@
   import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './tableColumns.api';
 </script>
 
-<style scoped></style>
+<style lang="less">
+/* 仅本页:取消搜索区 v-form(.vent-form)多余滚动条,不影响弹窗内表单 */
+.table-columns-page {
+  .vent-form.table-form,
+  .table-form.vent-form {
+    height: auto !important;
+    max-height: none !important;
+    overflow: visible !important;
+  }
+
+  .vent-form.table-form::-webkit-scrollbar,
+  .table-form.vent-form::-webkit-scrollbar {
+    display: none !important;
+    width: 0 !important;
+    height: 0 !important;
+  }
+}
+</style>

+ 20 - 6
src/views/vent/monitorManager/hsqHome/components/AlarmDay.vue

@@ -16,17 +16,31 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { computed, type PropType } from 'vue'
+import dayjs from 'dayjs'
 import commonEchart, { type CommonBarSeriesItem } from './common/commonEchart.vue'
+import type { AlarmDayCountItem } from './type'
 
-/** 近 7 日日期(模拟) */
-const xAxisData = ref<string[]>(['09-11', '09-12', '09-13', '09-14', '09-15', '09-16', '09-17'])
+const props = defineProps({
+  /** getAlarmStat.result.dayCountArray */
+  chartData: {
+    type: Array as PropType<AlarmDayCountItem[]>,
+    default: () => [],
+  },
+})
+
+/** 日统计横轴:YYYY-MM-DD → MM-DD */
+const xAxisData = computed(() =>
+  (props.chartData || []).map((item) => {
+    const t = String(item.time || '')
+    return dayjs(t).isValid() ? dayjs(t).format('MM-DD') : t
+  }),
+)
 
-/** 日报警数量(模拟) */
-const series = ref<CommonBarSeriesItem[]>([
+const series = computed<CommonBarSeriesItem[]>(() => [
   {
     name: '报警数量',
-    data: [6, 9, 4, 12, 8, 15, 11],
+    data: (props.chartData || []).map((item) => Number(item.count) || 0),
   },
 ])
 </script>

+ 22 - 6
src/views/vent/monitorManager/hsqHome/components/AlarmMonth.vue

@@ -16,17 +16,33 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { computed, type PropType } from 'vue'
+import dayjs from 'dayjs'
 import commonEchart, { type CommonBarSeriesItem } from './common/commonEchart.vue'
+import type { AlarmMonCountItem } from './type'
 
-/** 近 6 个月份(模拟) */
-const xAxisData = ref<string[]>(['4月', '5月', '6月', '7月', '8月', '9月'])
+const props = defineProps({
+  /** getAlarmStat.result.monCountArray */
+  chartData: {
+    type: Array as PropType<AlarmMonCountItem[]>,
+    default: () => [],
+  },
+})
+
+/** 月统计横轴:YYYY-MM → M月 */
+const xAxisData = computed(() =>
+  (props.chartData || []).map((item) => {
+    const t = String(item.time || '')
+    if (dayjs(t).isValid()) return `${dayjs(t).month() + 1}月`
+    const m = t.match(/-(\d{1,2})$/)
+    return m ? `${Number(m[1])}月` : t
+  }),
+)
 
-/** 月报警数量(模拟) */
-const series = ref<CommonBarSeriesItem[]>([
+const series = computed<CommonBarSeriesItem[]>(() => [
   {
     name: '报警数量',
-    data: [86, 102, 74, 128, 95, 113],
+    data: (props.chartData || []).map((item) => Number(item.count) || 0),
   },
 ])
 </script>

+ 16 - 14
src/views/vent/monitorManager/hsqHome/components/AlarmPoint.vue

@@ -16,25 +16,27 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { computed, type PropType } from 'vue'
 import commonEchart, { type CommonBarSeriesItem } from './common/commonEchart.vue'
+import type { AlarmDevCountItem } from './type'
 
-/** 点位类目(模拟) */
-const xAxisData = ref<string[]>([
-  '煤厂北侧',
-  '煤厂西侧',
-  '煤场东侧',
-  '煤场西侧',
-  '采场北侧',
-  '采场西侧1',
-  '采场西侧2',
-])
+const props = defineProps({
+  /** getAlarmStat.result.devCountArray */
+  chartData: {
+    type: Array as PropType<AlarmDevCountItem[]>,
+    default: () => [],
+  },
+})
+
+/** 点位轴:优先安装位置,其次设备名称 */
+const xAxisData = computed(() =>
+  (props.chartData || []).map((item) => String(item.devName || '')),
+)
 
-/** 点位报警数量(模拟) */
-const series = ref<CommonBarSeriesItem[]>([
+const series = computed<CommonBarSeriesItem[]>(() => [
   {
     name: '报警数量',
-    data: [18, 12, 26, 9, 15, 21, 7],
+    data: (props.chartData || []).map((item) => Number(item.count) || 0),
   },
 ])
 </script>

+ 537 - 106
src/views/vent/monitorManager/hsqHome/components/DeviceCenter.vue

@@ -107,188 +107,188 @@
         <div class="temp-warn">
           <div class="warn-item">
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.findFire"></a-checkbox>
               <div style="margin-right: 10px;">热源功能 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.findMove"></a-checkbox>
               <div style="margin-right: 10px;">闯入功能 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.fireTrace"></a-checkbox>
               <div style="margin-right: 10px;">热源跟踪 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.findDxm"></a-checkbox>
               <div style="margin-right: 10px;">低小慢 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.camZoomCapture"></a-checkbox>
               <div style="margin-right: 10px;">高清推倍抓拍 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.fireMeasureTemp"></a-checkbox>
               <div style="margin-right: 10px;">热源测温 </div>
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">告警阈值 : </div>
-              <a-input style="width: 150px;" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input style="width: 150px;" v-model:value="formData.fireMinThreshold" placeholder="请输入" size="small" />
               <span style="margin: 0 12px;">~</span>
-              <a-input style="width: 150px;" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input style="width: 150px;" v-model:value="formData.fireMaxThreshold" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">告警面积矢量 : </div>
-              <a-input style="width: 150px;" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input style="width: 150px;" v-model:value="formData.fireMinArea" placeholder="请输入" size="small" />
               <span style="margin: 0 11px;">~</span>
-              <a-input style="width: 150px;" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input style="width: 150px;" v-model:value="formData.fireMaxArea" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">辐射量平衡系数 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.radiation" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">材质发射率 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.emissivity" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">环境温度 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.environmentTemp" placeholder="请输入" size="small" />
             </div>
           </div>
 
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">灰度值偏值 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.grayOffset" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">多热源区分阈值 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.fireRadius" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">动态屏蔽灵敏度 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.shakeRadius" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">报警最小静止时长 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.stillDuration" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">半圈时长 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.ptzSemiCircleDuration" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">停留时长 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.stayDuration" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">低小慢尾长 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.dxmLength" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">低小慢范围 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.dxmRadius" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">设备号 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.deviceId" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">设备名称 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.deviceName" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">红外IP : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.ip" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">子网掩码 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.subnetMask" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">网关 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.gateway" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">通讯端口号 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.cmdPort" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">告警端口号 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.alarmPort" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">横向视场角 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.horFov" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">纵向视场角: </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.verFov" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">主码分辨率: </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.majorStreamReso" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
               <div class="item-label1">子码分辨率 : </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.minorStreamReso" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">服务器IP: </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.srvCenterIp" placeholder="请输入" size="small" />
             </div>
             <div class="item-basic">
               <div class="item-label1">服务器端口: </div>
-              <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.srvCenterPort" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.showMaxTemp"></a-checkbox>
               <div style="margin-right: 10px;">显示最高温 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.showMinTemp"></a-checkbox>
               <div style="margin-right: 10px;">显示最低温 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.showAvgTemp"></a-checkbox>
               <div style="margin-right: 10px;">显示平均温 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.showRunMode"></a-checkbox>
               <div style="margin-right: 10px;">显示运行模式 </div>
             </div>
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.showRunStatus"></a-checkbox>
               <div style="margin-right: 10px;">显示运行状态 </div>
             </div>
           </div>
           <div class="warn-item">
             <div class="item-basic">
-              <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+              <a-checkbox v-model:checked="formData.enableCenterService"></a-checkbox>
               <div>开启中心服务</div>
             </div>
           </div>
           <div class="btn-box">
-            <div class="baisc-btn">获取</div>
-            <div class="baisc-btn">
+            <div class="baisc-btn" :class="{ disabled: sysCfgLoading }" @click="onFetchSysCfg">获取</div>
+            <div class="baisc-btn" :class="{ disabled: sysCfgLoading }" @click="onSetSysCfg">
               <div class="btn-item">设置</div>
             </div>
           </div>
@@ -331,93 +331,411 @@
       </basicBorder>
 
     </div>
-    <div class="basic-box2">
+    <!-- <div class="basic-box2">
       <basicBorder title="【操作】">
         <div class="btn-box">
           <div class="baisc-btn-save">重启前端</div>
-          <div class="baisc-btn-save">
+          <div class="baisc-btn-save" @click="onRestoreDefaultCfg">
             <div class="btn-item">恢复前端默认配置</div>
           </div>
           <div class="baisc-btn-save">保存前端配置</div>
         </div>
       </basicBorder>
 
-    </div>
+    </div> -->
+
+    <!-- 未选设备提示弹窗 -->
+    <Teleport to="body">
+      <div v-if="deviceTipVisible" class="device-tip-mask" @click.self="closeDeviceTip">
+        <div class="device-tip-panel">
+          <div class="device-tip-frame">
+            <div class="device-tip-title-tab">提示</div>
+            <div class="device-tip-body">请选择设备!</div>
+            <div class="device-tip-footer">
+              <button type="button" class="device-tip-btn" @click="closeDeviceTip">确定</button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </Teleport>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onUnmounted, reactive, ref, watch, onMounted } from 'vue'
+import { reactive, ref, watch } from 'vue'
 import basicBorder from './basicBorder.vue'
-import { SvgIcon } from '/@/components/Icon';
-import { autoLogList, dscAlarmLogList } from '../hsqHome.api.js'
-import { dscAlarmLogColumns } from '../hsqHome.data.js'
-import dayjs from 'dayjs';
+import {hsqComControl, getSysCfg, setSysCfg, managesysList, monitorSystem } from '../hsqHome.api'
+import { useMessage } from '/@/hooks/web/useMessage'
+
+/** 前端配置表单(与 ZC_SYSTEM_CFG_S 字段对应) */
+interface SysCfgForm {
+  findFire: boolean
+  findMove: boolean
+  fireTrace: boolean
+  findDxm: boolean
+  camZoomCapture: boolean
+  fireMeasureTemp: boolean
+  fireMinThreshold: string | number
+  fireMaxThreshold: string | number
+  fireMinArea: string | number
+  fireMaxArea: string | number
+  radiation: string | number
+  emissivity: string | number
+  environmentTemp: string | number
+  grayOffset: string | number
+  fireRadius: string | number
+  shakeRadius: string | number
+  stillDuration: string | number
+  ptzSemiCircleDuration: string | number
+  stayDuration: string | number
+  dxmLength: string | number
+  dxmRadius: string | number
+  deviceId: string | number
+  deviceName: string
+  ip: string
+  subnetMask: string
+  gateway: string
+  cmdPort: string | number
+  alarmPort: string | number
+  horFov: string | number
+  verFov: string | number
+  majorStreamReso: string | number
+  minorStreamReso: string | number
+  srvCenterIp: string
+  srvCenterPort: string | number
+  showMaxTemp: boolean
+  showMinTemp: boolean
+  showAvgTemp: boolean
+  showRunMode: boolean
+  showRunStatus: boolean
+  enableCenterService: boolean
+  /** 版本区占位字段(未改版本获取逻辑) */
+  ld: string
+}
 
+const BOOL_KEYS = [
+  'findFire',
+  'findMove',
+  'fireTrace',
+  'findDxm',
+  'camZoomCapture',
+  'fireMeasureTemp',
+  'showMaxTemp',
+  'showMinTemp',
+  'showAvgTemp',
+  'showRunMode',
+  'showRunStatus',
+  'enableCenterService',
+] as const
+
+const NUMBER_KEYS = [
+  'fireMinThreshold',
+  'fireMaxThreshold',
+  'fireMinArea',
+  'fireMaxArea',
+  'radiation',
+  'emissivity',
+  'environmentTemp',
+  'grayOffset',
+  'fireRadius',
+  'shakeRadius',
+  'stillDuration',
+  'ptzSemiCircleDuration',
+  'stayDuration',
+  'dxmLength',
+  'dxmRadius',
+  'deviceId',
+  'cmdPort',
+  'alarmPort',
+  'horFov',
+  'verFov',
+  'majorStreamReso',
+  'minorStreamReso',
+  'srvCenterPort',
+] as const
+
+const STRING_KEYS = [
+  'deviceName',
+  'ip',
+  'subnetMask',
+  'gateway',
+  'srvCenterIp',
+] as const
+
+function createEmptyForm(): SysCfgForm {
+  return {
+    findFire: false,
+    findMove: false,
+    fireTrace: false,
+    findDxm: false,
+    camZoomCapture: false,
+    fireMeasureTemp: false,
+    fireMinThreshold: '',
+    fireMaxThreshold: '',
+    fireMinArea: '',
+    fireMaxArea: '',
+    radiation: '',
+    emissivity: '',
+    environmentTemp: '',
+    grayOffset: '',
+    fireRadius: '',
+    shakeRadius: '',
+    stillDuration: '',
+    ptzSemiCircleDuration: '',
+    stayDuration: '',
+    dxmLength: '',
+    dxmRadius: '',
+    deviceId: '',
+    deviceName: '',
+    ip: '',
+    subnetMask: '',
+    gateway: '',
+    cmdPort: '',
+    alarmPort: '',
+    horFov: '',
+    verFov: '',
+    majorStreamReso: '',
+    minorStreamReso: '',
+    srvCenterIp: '',
+    srvCenterPort: '',
+    showMaxTemp: false,
+    showMinTemp: false,
+    showAvgTemp: false,
+    showRunMode: false,
+    showRunStatus: false,
+    enableCenterService: false,
+    ld: '',
+  }
+}
 
-const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
-let props = defineProps({
+const props = defineProps({
   detailData: {
     type: Object,
-    default: () => { },
+    default: () => ({}),
   },
 })
 
-let detailDatas = ref<any>({})
-let tableData = ref<any[]>([])
-/** 时间范围:[开始时间, 结束时间],默认当天 0 点至当前时间 */
-const timeRange = ref<[string, string]>([
-  dayjs().startOf('day').format(DATE_FORMAT),
-  dayjs().format(DATE_FORMAT),
-])
-let formData = ref<any>({})
-
-// https获取监测数据
-let timer: null | NodeJS.Timeout = null;
-
-/**
- * 定时获取监测数据
- * 通过定时器循环调用数据获取方法,更新表格数据
- *
- * @param flag - 是否立即执行(首次调用时传true跳过延时)
- */
-function getMonitor(flag?: boolean) {
-  timer = setTimeout(async () => {
-    await getDscAlarmLogList();
-    if (timer) {
-      timer = null;
+const { createMessage } = useMessage()
+const formData = reactive<SysCfgForm>(createEmptyForm())
+/** 最近一次 getSysCfg 原始配置,设置时合并未展示字段 */
+const rawSysCfg = ref<Record<string, any>>({})
+const currentDeviceId = ref('')
+const sysCfgLoading = ref(false)
+/** 未选设备提示 */
+const deviceTipVisible = ref(false)
+/** 列表「恢复默认配置」时跳过一次 detailData watch,避免重复请求 */
+const skipDetailWatchOnce = ref(false)
+
+function closeDeviceTip() {
+  deviceTipVisible.value = false
+}
+
+function toBool(value: unknown) {
+  if (typeof value === 'boolean') return value
+  const num = Number(value)
+  if (Number.isFinite(num)) return num !== 0
+  return Boolean(value)
+}
+
+function toDisplayValue(value: unknown) {
+  if (value == null) return ''
+  return value as string | number
+}
+
+function toIntFlag(checked: boolean) {
+  return checked ? 1 : 0
+}
+
+function toNumberOrKeep(value: string | number) {
+  if (value === '' || value == null) return value
+  const num = Number(value)
+  return Number.isFinite(num) ? num : value
+}
+
+/** 解析 getSysCfg 返回对象 */
+function normalizeSysCfg(res: Record<string, any> | null | undefined) {
+  if (!res || typeof res !== 'object') return {}
+  if (res.data && typeof res.data === 'object' && !Array.isArray(res.data)) {
+    return { ...res.data }
+  }
+  return { ...res }
+}
+
+/** 将配置回填到表单 */
+function applyCfgToForm(cfg: Record<string, any>) {
+  const next = createEmptyForm()
+  BOOL_KEYS.forEach((key) => {
+    if (key === 'enableCenterService') {
+      next.enableCenterService = toBool(
+        cfg.enableCenterService ?? cfg.openCenterService ?? cfg.centerServiceEnable ?? 0,
+      )
+      return
     }
-    getMonitor();
-  }, flag ? 0 : 10000);
+    next[key] = toBool(cfg[key])
+  })
+  NUMBER_KEYS.forEach((key) => {
+    next[key] = toDisplayValue(cfg[key])
+  })
+  STRING_KEYS.forEach((key) => {
+    next[key] = cfg[key] != null ? String(cfg[key]) : ''
+  })
+  Object.assign(formData, next)
 }
 
-/**
- * 获取双光谱摄像机报警记录-分页列表查询
- * 调用 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,
-      eventTypeC: el.eventType == '10001' ? '热源报警' : el.eventType == '10002' ? '闯入报警' : '-',
-    }))
+/** 由表单生成 setSysCfg 的 data 载荷(仅表单字段) */
+function buildSysCfgPayload() {
+  const payload: Record<string, any> = {}
+  BOOL_KEYS.forEach((key) => {
+    if (key === 'enableCenterService') {
+      payload.enableCenterService = toIntFlag(formData.enableCenterService)
+      return
+    }
+    payload[key] = toIntFlag(formData[key])
+  })
+  NUMBER_KEYS.forEach((key) => {
+    payload[key] = toNumberOrKeep(formData[key])
+  })
+  STRING_KEYS.forEach((key) => {
+    payload[key] = formData[key] ?? ''
+  })
+  return payload
+}
+
+/** 从设备对象解析 SDK deviceid */
+function resolveDeviceIdFromDevice(device: Record<string, any> | null | undefined) {
+  if (!device) return ''
+  const raw =
+    device.deviceID ??
+    device.deviceid ??
+    device.deviceId ??
+    device.devId ??
+    device.id ??
+    ''
+  return raw != null && String(raw).trim() !== '' ? String(raw).trim() : ''
+}
+
+/** 解析当前可用设备 ID:优先 props,其次列表首台设备 */
+async function ensureDeviceId() {
+  const fromProp = resolveDeviceIdFromDevice(props.detailData as Record<string, any>)
+  if (fromProp) {
+    currentDeviceId.value = fromProp
+    return fromProp
+  }
+  if (currentDeviceId.value) return currentDeviceId.value
+
+  try {
+    const sysRes = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' })
+    const systemId = sysRes?.records?.[0]?.id
+    if (!systemId) return ''
+    const res = await monitorSystem({ type: 'all', systemID: systemId, devicetype: '' })
+    const list = res?.deviceInfo?.duaSpeCamera?.datalist || []
+    const firstId = resolveDeviceIdFromDevice(list[0])
+    currentDeviceId.value = firstId
+    return firstId
+  } catch (e) {
+    console.error('解析设备 ID 失败', e)
+    return ''
+  }
+}
+
+/** 调用 getSysCfg 并回显 */
+async function fetchSysCfg(showTip = false) {
+  if (sysCfgLoading.value) return
+  const deviceid = await ensureDeviceId()
+  if (!deviceid) {
+    createMessage.warning('未找到可用设备,无法获取前端配置')
+    return
+  }
+
+  sysCfgLoading.value = true
+  try {
+    const res = await getSysCfg({
+      deviceid,
+      paramcode: 'getSysCfg',
+      noCheckPassword: true,
+    })
+    const cfg = normalizeSysCfg(res as Record<string, any>)
+    rawSysCfg.value = cfg
+    applyCfgToForm(cfg)
+    if (showTip) createMessage.success('获取前端配置成功')
+  } catch (e) {
+    console.error('获取前端配置失败', e)
+    createMessage.error('获取前端配置失败')
+  } finally {
+    sysCfgLoading.value = false
   }
 }
 
-watch(() => props.detailData, (newVal, oldVal) => {
-  detailDatas.value = Object.assign({}, newVal.readData, newVal,)
-}, { immediate: true })
+/** 点击获取 */
+function onFetchSysCfg() {
+  fetchSysCfg(true)
+}
+
+/** 点击设置:先 setSysCfg,成功后再 getSysCfg 刷新 */
+async function onSetSysCfg() {
+  if (sysCfgLoading.value) return
+  const deviceid = await ensureDeviceId()
+  if (!deviceid) {
+    createMessage.warning('未找到可用设备,无法设置前端配置')
+    return
+  }
+
+  sysCfgLoading.value = true
+  try {
+    const data = buildSysCfgPayload()
+    await hsqComControl({
+      deviceid,
+      paramcode: 'setSysCfg',
+      noCheckPassword: true,
+      data,
+    })
+    createMessage.success('设置前端配置成功')
+    sysCfgLoading.value = false
+    await fetchSysCfg(false)
+  } catch (e) {
+    console.error('设置前端配置失败', e)
+    createMessage.error('设置前端配置失败')
+    sysCfgLoading.value = false
+  }
+}
 
-onMounted(() => {
-  getMonitor(true);
-});
-onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
+/** 恢复前端默认配置:未选设备提示;已选则重新 getSysCfg 回填重置 */
+function onRestoreDefaultCfg(device?: Record<string, any>) {
+  if (sysCfgLoading.value) return
+  const deviceid = resolveDeviceIdFromDevice(
+    (device || props.detailData) as Record<string, any>,
+  )
+  if (!deviceid) {
+    deviceTipVisible.value = true
+    return
+  }
+  if (device) {
+    skipDetailWatchOnce.value = true
   }
-});
+  currentDeviceId.value = deviceid
+  fetchSysCfg(true)
+}
+
+/** 点击设备列表「详情」后由父组件更新 detailData,再拉取前端配置 */
+watch(
+  () => props.detailData,
+  (val) => {
+    const id = resolveDeviceIdFromDevice(val as Record<string, any>)
+    if (!id) return
+    if (skipDetailWatchOnce.value) {
+      skipDetailWatchOnce.value = false
+      currentDeviceId.value = id
+      return
+    }
+    currentDeviceId.value = id
+    fetchSysCfg(false)
+  },
+  { deep: true },
+)
+
+defineExpose({
+  onRestoreDefaultCfg,
+})
 </script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
@@ -510,19 +828,19 @@ onUnmounted(() => {
 
   .basic-box {
     position: relative;
-    height: 480px;
+    height: 575px;
     margin-bottom: 10px;
   }
 
   .basic-box1 {
     // height: calc(100% - 230px);
     height: 125px;
-    margin-bottom: 10px;
+    // margin-bottom: 10px;
   }
 
-  .basic-box2 {
-    height: 85px;
-  }
+  // .basic-box2 {
+  //   height: 85px;
+  // }
 
   .temp-warn {
     position: relative;
@@ -537,7 +855,7 @@ onUnmounted(() => {
     align-items: center;
     height: 32px;
     background: linear-gradient(to right, #134c77, transparent);
-    margin-bottom: 10px;
+    margin-bottom: 9px;
     padding: 0px 10px;
     box-sizing: border-box;
 
@@ -597,6 +915,12 @@ onUnmounted(() => {
     border-radius: 4px;
     padding: 3px;
     cursor: pointer;
+
+    &.disabled {
+      opacity: 0.55;
+      cursor: not-allowed;
+      pointer-events: none;
+    }
   }
 
   .baisc-btn-save {
@@ -869,4 +1193,111 @@ onUnmounted(() => {
     display: none;
   }
 }
+
+/* 未选设备提示弹窗(风格对齐视频预览提示) */
+.device-tip-mask {
+  position: fixed;
+  inset: 0;
+  z-index: 2200;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 8, 20, 0.55);
+}
+
+.device-tip-panel {
+  width: min(360px, calc(100vw - 48px));
+}
+
+.device-tip-frame {
+  position: relative;
+  padding: 36px 20px 18px;
+  box-sizing: border-box;
+  background: linear-gradient(180deg, rgba(6, 28, 48, 0.98) 0%, rgba(4, 16, 32, 0.98) 100%);
+  border: 1px solid rgba(0, 210, 255, 0.85);
+  box-shadow:
+    0 0 0 2px rgba(8, 40, 70, 0.95),
+    0 0 22px rgba(0, 200, 255, 0.35),
+    inset 0 0 0 1px rgba(0, 180, 255, 0.35);
+  clip-path: polygon(
+    10px 0,
+    calc(100% - 10px) 0,
+    100% 10px,
+    100% calc(100% - 10px),
+    calc(100% - 10px) 100%,
+    10px 100%,
+    0 calc(100% - 10px),
+    0 10px
+  );
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.device-tip-title-tab {
+  position: absolute;
+  top: -1px;
+  left: 50%;
+  transform: translateX(-50%);
+  min-width: 112px;
+  height: 28px;
+  padding: 0 28px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 14px;
+  font-weight: 600;
+  letter-spacing: 2px;
+  background: linear-gradient(180deg, #2ad4e8 0%, #1498b8 55%, #0c6f8a 100%);
+  clip-path: polygon(12px 0, calc(100% - 12px) 0, 100% 100%, 0 100%);
+  box-shadow: 0 0 10px rgba(0, 200, 255, 0.45);
+  z-index: 2;
+}
+
+.device-tip-body {
+  min-height: 72px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #e8f4ff;
+  font-size: 16px;
+  letter-spacing: 1px;
+  padding: 12px 8px 8px;
+  text-align: center;
+}
+
+.device-tip-footer {
+  display: flex;
+  justify-content: center;
+  padding: 4px 0 4px;
+}
+
+.device-tip-btn {
+  min-width: 96px;
+  height: 32px;
+  padding: 0 22px;
+  border: 1px solid rgba(0, 210, 255, 0.75);
+  border-radius: 2px;
+  background: linear-gradient(180deg, rgba(20, 100, 140, 0.85) 0%, rgba(10, 60, 95, 0.9) 100%);
+  color: #fff;
+  font-size: 14px;
+  letter-spacing: 2px;
+  cursor: pointer;
+  box-shadow: 0 0 8px rgba(0, 183, 255, 0.25);
+  transition: color 0.2s, border-color 0.2s, background 0.2s, box-shadow 0.2s;
+
+  &:hover,
+  &:focus {
+    border-color: #01fefc;
+    color: #01fefc;
+    background: rgba(0, 90, 140, 0.55);
+    box-shadow: 0 0 10px rgba(1, 254, 252, 0.35);
+    outline: none;
+  }
+
+  &:active {
+    transform: translateY(1px);
+  }
+}
 </style>

+ 330 - 71
src/views/vent/monitorManager/hsqHome/components/DeviceControl.vue

@@ -202,13 +202,17 @@
       </div>
     </div>
 
-    <!-- 未选摄像头提示弹窗 -->
+    <!-- 未选摄像头提示弹窗(风格对齐报警弹窗) -->
     <Teleport to="body">
       <div v-if="deviceTipVisible" class="device-tip-mask" @click.self="closeDeviceTip">
         <div class="device-tip-panel">
-          <div class="device-tip-header">提示</div>
-          <div class="device-tip-body">请选择设备!</div>
-          <a-button class="device-tip-btn" @click="closeDeviceTip">确定</a-button>
+          <div class="device-tip-frame">
+            <div class="device-tip-title-tab">提示</div>
+            <div class="device-tip-body">请选择设备!</div>
+            <div class="device-tip-footer">
+              <button type="button" class="device-tip-btn" @click="closeDeviceTip">确定</button>
+            </div>
+          </div>
         </div>
       </div>
     </Teleport>
@@ -348,12 +352,12 @@ const COLOR_MODE_CODE: Record<string, number> = {
   红热: 6,
   蓝红色: 7,
 }
-/** 方向键与 PTZ 指令映射 */
-const PTZ_CMD_MAP: Record<string, string> = {
-  up: 'PTZ_UP',
-  down: 'PTZ_DOWN',
-  left: 'PTZ_LEFT',
-  right: 'PTZ_RIGHT',
+/** 方向键与 PTZ 指令映射(手动轮盘) */
+const PTZ_DIR_CMD_MAP: Record<string, number> = {
+  up: 1,
+  down: 2,
+  left: 3,
+  right: 4,
 }
 /** 方向轮盘按钮 */
 const directionButtons: DirectionButtonItem[] = [
@@ -367,8 +371,11 @@ const footerCheckItems: FooterCheckItem[] = [
   { key: 'irZoom', label: '红外变倍' },
   { key: 'irControl', label: '红外控制' },
 ]
-/** 镜头本地调节类型 */
-const LOCAL_LENS_TYPES = new Set(['zoom', 'focus', 'iris'])
+/** 焦距 / 焦点 / 光圈 / 方向按住方向,用于松开时区分对应停止指令 */
+const lastZoomAction = ref<'plus' | 'minus' | null>(null)
+const lastFocusAction = ref<'plus' | 'minus' | null>(null)
+const lastIrisAction = ref<'plus' | 'minus' | null>(null)
+const lastPanAction = ref<'up' | 'down' | 'left' | 'right' | null>(null)
 
 const { createMessage } = useMessage()
 
@@ -427,28 +434,154 @@ function closeDeviceTip() {
   deviceTipVisible.value = false
 }
 
-/** 发送云台方向控制 */
-function sendPtzDirection(value: string) {
-  const deviceid = currentDevice.value
-  if (value === 'stop') {
-    sendAction({
+/**
+ * 手动模式方向轮盘:调用 hsqComControl
+ * 上=1 / 下=2 / 左=3 / 右=4
+ * 按下 stop=0,speed=界面速度;松开 stop=1,speed=1;type=可见光2/热成像1
+ */
+async function sendPtzDirection(value: string) {
+  const deviceid = getControlDeviceId()
+  if (!deviceid) {
+    if (value !== 'stop') deviceTipVisible.value = true
+    return
+  }
+
+  let cmd: number | null = null
+  let stop = 0
+  let speedVal = speed.value
+
+  if (value === 'up' || value === 'down' || value === 'left' || value === 'right') {
+    lastPanAction.value = value
+    cmd = PTZ_DIR_CMD_MAP[value]
+    stop = 0
+    speedVal = speed.value
+  } else if (value === 'stop') {
+    if (lastPanAction.value) {
+      cmd = PTZ_DIR_CMD_MAP[lastPanAction.value]
+    }
+    stop = 1
+    speedVal = 1
+    lastPanAction.value = null
+  }
+
+  if (cmd == null) return
+
+  try {
+    const res = await hsqComControl({
       deviceid,
       paramcode: 'ptzControl',
       noCheckPassword: true,
-      stop: 1,
-      speed: 0,
+      cmd,
+      stop,
+      speed: speedVal,
+      type: resolveChannelType(),
     })
+    console.log(res, '方向轮盘控制---')
+  } catch (e) {
+    console.error('方向轮盘控制失败', e)
+    if (value !== 'stop') createMessage.error('云台控制失败')
+  }
+}
+
+/** 根据选中摄像头名称判断通道类型:热成像=1,可见光=2 */
+function resolveChannelType() {
+  const name = String(props.selectedCamera?.name || '')
+  if (name.includes('热成像')) return 1
+  if (name.includes('可见光')) return 2
+  // 无明确标识时默认按可见光
+  return 2
+}
+
+interface LensPtzCmdOption {
+  /** 按下 plus 时的 cmd */
+  plusCmd: number
+  /** 按下 minus 时的 cmd */
+  minusCmd: number
+  /** 记录按住方向的 ref */
+  lastAction: typeof lastZoomAction
+  logLabel: string
+}
+
+/**
+ * 镜头 PTZ 控制(焦距 / 焦点):调用 hsqComControl
+ * 按下:cmd=对应指令, stop=0, speed=界面速度, type=可见光2/热成像1
+ * 松开:cmd=对应指令, stop=1, speed=1, type 同上
+ */
+async function sendLensPtzControl(value: string, option: LensPtzCmdOption) {
+  const deviceid = getControlDeviceId()
+  if (!deviceid) {
+    if (value !== 'stop') deviceTipVisible.value = true
     return
   }
-  const cmd = PTZ_CMD_MAP[value]
-  if (!cmd) return
-  sendAction({
-    deviceid,
-    paramcode: 'ptzControl',
-    noCheckPassword: true,
-    cmd,
-    stop: 0,
-    speed: speed.value,
+
+  let cmd: number | null = null
+  let stop = 0
+  let speedVal = speed.value
+
+  if (value === 'plus') {
+    option.lastAction.value = 'plus'
+    cmd = option.plusCmd
+    stop = 0
+    speedVal = speed.value
+  } else if (value === 'minus') {
+    option.lastAction.value = 'minus'
+    cmd = option.minusCmd
+    stop = 0
+    speedVal = speed.value
+  } else if (value === 'stop') {
+    if (option.lastAction.value === 'plus') {
+      cmd = option.plusCmd
+    } else if (option.lastAction.value === 'minus') {
+      cmd = option.minusCmd
+    }
+    stop = 1
+    speedVal = 1
+    option.lastAction.value = null
+  }
+
+  if (cmd == null) return
+
+  try {
+    const res = await hsqComControl({
+      deviceid,
+      paramcode: 'ptzControl',
+      noCheckPassword: true,
+      cmd,
+      stop,
+      speed: speedVal,
+      type: resolveChannelType(),
+    })
+    console.log(res, `${option.logLabel}---`)
+  } catch (e) {
+    console.error(`${option.logLabel}失败`, e)
+    if (value !== 'stop') createMessage.error(`${option.logLabel}失败`)
+  }
+}
+
+function sendZoomControl(value: string) {
+  return sendLensPtzControl(value, {
+    plusCmd: 9,
+    minusCmd: 10,
+    lastAction: lastZoomAction,
+    logLabel: '焦距控制',
+  })
+}
+
+function sendFocusControl(value: string) {
+  return sendLensPtzControl(value, {
+    plusCmd: 13,
+    minusCmd: 14,
+    lastAction: lastFocusAction,
+    logLabel: '焦点控制',
+  })
+}
+
+function sendIrisControl(value: string) {
+  return sendLensPtzControl(value, {
+    plusCmd: 11,
+    minusCmd: 12,
+    lastAction: lastIrisAction,
+    logLabel: '光圈控制',
   })
 }
 
@@ -457,20 +590,58 @@ function emitAction(type: string, value: string) {
   // 非手动扫描模式下禁止方向轮盘操作
   if (type === 'pan' && isDirectionPadDisabled.value) return
 
-  // 云台焦距 / 焦点 / 光圈:不调接口,仅校验选中摄像头后抛事件给画面区处理
-  if (activeTab.value === 'ptz' && LOCAL_LENS_TYPES.has(type)) {
+  // 手动模式方向轮盘:未选设备提示;已选则走 hsqComControl
+  if (type === 'pan') {
     if (!props.selectedCamera) {
       if (value !== 'stop') deviceTipVisible.value = true
       return
     }
+    sendPtzDirection(value)
     emit('action', { type, value })
     return
   }
 
-  emit('action', { type, value })
-  if (value === 'up' || value === 'down' || value === 'left' || value === 'right' || value === 'stop') {
-    sendPtzDirection(value)
+  // 云台焦距 / 焦点 / 光圈:走 hsqComControl,不再本地调节画面
+  if (activeTab.value === 'ptz' && (type === 'zoom' || type === 'focus' || type === 'iris')) {
+    if (!props.selectedCamera) {
+      if (value !== 'stop') deviceTipVisible.value = true
+      return
+    }
+    if (type === 'zoom') sendZoomControl(value)
+    else if (type === 'focus') sendFocusControl(value)
+    else sendIrisControl(value)
+    emit('action', { type, value })
+    return
   }
+
+  emit('action', { type, value })
+}
+
+/** 从选中摄像头解析设备 ID */
+function resolveSelectedCameraDeviceId(camera: Record<string, any> | null | undefined) {
+  if (!camera) return ''
+  const raw = camera.deviceid ?? camera.deviceId ?? camera.deviceID ?? camera.devId ?? ''
+  return raw != null ? String(raw).trim() : ''
+}
+
+/** 按设备 ID 在控制设备下拉中匹配对应项 */
+function findControlDeviceOption(deviceId: string) {
+  if (!deviceId) return null
+  return (
+    props.deviceOptions.find((item) => {
+      const optionId = item?.value ?? item?.deviceID ?? item?.deviceId ?? item?.deviceid ?? ''
+      return String(optionId).trim() === deviceId
+    }) || null
+  )
+}
+
+/** 选中摄像头时,将控制设备下拉同步为同一设备 */
+function syncControlDeviceByCamera() {
+  const deviceId = resolveSelectedCameraDeviceId(props.selectedCamera)
+  const matched = findControlDeviceOption(deviceId)
+  if (!matched) return
+  currentDevice.value = matched.value
+  currentDeviceType.value = matched.deviceType
 }
 
 /** 选择设备并触发 deviceChange 事件 */
@@ -541,14 +712,34 @@ async function switchCfgMode(option: CfgModeSwitchOption) {
   }
 }
 
-/** 切换扫描模式:调用 setRunModeCfg,成功后提示 */
+/** 切换扫描模式:手动仅本地切换;其它选项需已选设备后调用 setRunModeCfg */
 function switchScanMode(value: string) {
+  if (value === scanMode.value) {
+    openMenu.value = ''
+    return
+  }
+
+  // 手动:不发起接口请求,仅更新本地状态(方向轮盘可操作)
+  if (value === '手动') {
+    scanMode.value = value
+    emit('scanModeChange', value)
+    openMenu.value = ''
+    return
+  }
+
+  // 非手动:未选中摄像头设备则提示,不切换模式、不请求接口
+  if (!props.selectedCamera) {
+    deviceTipVisible.value = true
+    openMenu.value = ''
+    return
+  }
+
   return switchCfgMode({
     value,
     current: scanMode,
     emitChange: (v) => emit('scanModeChange', v),
     request: (deviceid, code) =>
-      setRunModeCfg({
+    hsqComControl({
         deviceid,
         paramcode: 'setRunModeCfg',
         noCheckPassword: true,
@@ -561,14 +752,34 @@ function switchScanMode(value: string) {
   })
 }
 
-/** 切换调色模式:调用 setPesudoColor,成功后提示 */
+/** 是否选中了摄像头列表后四列(热成像区域,下标 4~7) */
+function isLastFourCameraSelected() {
+  const camera = props.selectedCamera
+  if (!camera) return false
+  const index = Number(camera.index)
+  return Number.isFinite(index) && index >= 4 && index <= 7
+}
+
+/** 切换调色模式:仅后四列摄像头可选;未满足则提示,已选中则调用 setPesudoColor */
 function switchColorMode(value: string) {
+  if (value === colorMode.value) {
+    openMenu.value = ''
+    return
+  }
+
+  // 未选中,或选中的不是摄像头列表后四列 → 提示请选择设备
+  if (!isLastFourCameraSelected()) {
+    deviceTipVisible.value = true
+    openMenu.value = ''
+    return
+  }
+
   return switchCfgMode({
     value,
     current: colorMode,
     emitChange: (v) => emit('colorModeChange', v),
     request: (deviceid, code) =>
-      setPesudoColor({
+    hsqComControl({
         deviceid,
         paramcode: 'setPesudoColor',
         noCheckPassword: true,
@@ -612,14 +823,32 @@ watch([electronicDefog, opticalDefog, fogIntensity, lightIntensity], () => {
 watch(
   () => props.deviceOptions,
   (newVal) => {
-    if (newVal.length > 0) {
-      currentDevice.value = newVal[0].value
-      currentDeviceType.value = newVal[0].deviceType
+    if (!newVal.length) return
+    const matched = findControlDeviceOption(resolveSelectedCameraDeviceId(props.selectedCamera))
+    if (matched) {
+      currentDevice.value = matched.value
+      currentDeviceType.value = matched.deviceType
+      return
+    }
+    const current = newVal.find((item) => item.value === currentDevice.value)
+    if (current) {
+      currentDeviceType.value = current.deviceType
+      return
     }
+    currentDevice.value = newVal[0].value
+    currentDeviceType.value = newVal[0].deviceType
   },
   { immediate: true },
 )
 
+/** 画面区选中摄像头后,控制设备下拉按设备 ID 联动 */
+watch(
+  () => props.selectedCamera,
+  () => {
+    syncControlDeviceByCamera()
+  },
+)
+
 /** 挂载时注册全局点击关闭事件 */
 onMounted(() => {
   document.addEventListener('click', onDocClick)
@@ -1649,75 +1878,105 @@ onUnmounted(() => {
   position: fixed;
   inset: 0;
   z-index: 2200;
-  background: rgba(0, 10, 24, 0.55);
   display: flex;
   align-items: center;
   justify-content: center;
+  background: rgba(0, 8, 20, 0.55);
 }
 
 .device-tip-panel {
-  position: relative;
   width: min(360px, calc(100vw - 48px));
-  min-height: 180px;
-  margin-top: 18px;
-  padding: 42px 24px 24px;
+}
+
+.device-tip-frame {
+  position: relative;
+  padding: 36px 20px 18px;
   box-sizing: border-box;
-  background: linear-gradient(180deg, rgba(10, 36, 62, 0.98) 0%, rgba(4, 18, 36, 0.98) 100%);
-  border: 1px solid rgba(1, 254, 252, 0.75);
-  border-radius: 4px;
+  background: linear-gradient(180deg, rgba(6, 28, 48, 0.98) 0%, rgba(4, 16, 32, 0.98) 100%);
+  border: 1px solid rgba(0, 210, 255, 0.85);
   box-shadow:
-    0 0 18px rgba(1, 254, 252, 0.28),
-    inset 0 0 20px rgba(0, 120, 180, 0.12);
+    0 0 0 2px rgba(8, 40, 70, 0.95),
+    0 0 22px rgba(0, 200, 255, 0.35),
+    inset 0 0 0 1px rgba(0, 180, 255, 0.35);
+  clip-path: polygon(
+    10px 0,
+    calc(100% - 10px) 0,
+    100% 10px,
+    100% calc(100% - 10px),
+    calc(100% - 10px) 100%,
+    10px 100%,
+    0 calc(100% - 10px),
+    0 10px
+  );
   display: flex;
   flex-direction: column;
   align-items: center;
 }
 
-.device-tip-header {
+.device-tip-title-tab {
   position: absolute;
-  top: -16px;
+  top: -1px;
   left: 50%;
   transform: translateX(-50%);
-  min-width: 96px;
-  height: 32px;
-  padding: 0 22px;
+  min-width: 112px;
+  height: 28px;
+  padding: 0 28px;
   display: flex;
   align-items: center;
   justify-content: center;
   color: #fff;
-  font-size: 15px;
+  font-size: 14px;
+  font-weight: 600;
   letter-spacing: 2px;
-  background: linear-gradient(180deg, rgba(0, 210, 255, 0.95) 0%, rgba(0, 120, 180, 0.55) 100%);
-  border: 1px solid rgba(1, 254, 252, 0.9);
-  clip-path: polygon(10px 0, calc(100% - 10px) 0, 100% 100%, 0 100%);
+  background: linear-gradient(180deg, #2ad4e8 0%, #1498b8 55%, #0c6f8a 100%);
+  clip-path: polygon(12px 0, calc(100% - 12px) 0, 100% 100%, 0 100%);
+  box-shadow: 0 0 10px rgba(0, 200, 255, 0.45);
+  z-index: 2;
 }
 
 .device-tip-body {
-  flex: 1;
+  min-height: 72px;
   display: flex;
   align-items: center;
   justify-content: center;
   color: #e8f4ff;
   font-size: 16px;
   letter-spacing: 1px;
+  padding: 12px 8px 8px;
+  text-align: center;
+}
+
+.device-tip-footer {
+  display: flex;
+  justify-content: center;
+  padding: 4px 0 4px;
 }
 
 .device-tip-btn {
-  min-width: 96px !important;
-  height: 32px !important;
-  margin-top: 8px;
-  border: 1px solid rgba(1, 254, 252, 0.75) !important;
-  background: transparent !important;
-  color: #fff !important;
-  font-size: 14px !important;
+  min-width: 96px;
+  height: 32px;
+  padding: 0 22px;
+  border: 1px solid rgba(0, 210, 255, 0.75);
+  border-radius: 2px;
+  background: linear-gradient(180deg, rgba(20, 100, 140, 0.85) 0%, rgba(10, 60, 95, 0.9) 100%);
+  color: #fff;
+  font-size: 14px;
   letter-spacing: 2px;
-  box-shadow: none !important;
+  cursor: pointer;
+  box-shadow: 0 0 8px rgba(0, 183, 255, 0.25);
+  transition: color 0.2s, border-color 0.2s, background 0.2s, box-shadow 0.2s;
 
   &:hover,
   &:focus {
-    border-color: #01fefc !important;
-    color: #01fefc !important;
-    background: rgba(1, 254, 252, 0.08) !important;
+    border-color: #01fefc;
+    color: #01fefc;
+    background: rgba(0, 90, 140, 0.55);
+    box-shadow: 0 0 10px rgba(1, 254, 252, 0.35);
+    outline: none;
+  }
+
+  &:active {
+    transform: translateY(1px);
   }
 }
 </style>

+ 69 - 49
src/views/vent/monitorManager/hsqHome/components/DeviceLeft.vue

@@ -16,7 +16,11 @@
           </div>
         </div>
         <div class="content-box">
-          <div class="basic-device" v-for="(item, index) in filteredListData" :key="index">
+          <div
+            :class="['basic-device', { 'basic-device-active': active == index }]"
+            v-for="(item, index) in filteredListData"
+            :key="index"
+          >
             <div class="icon-left">
               <!-- 状态指示圆点:根据报警级别显示不同颜色(绿色正常/红色报警) -->
               <div :class="item.netStatus ? 'icon-item' : 'icon-item-warn'"></div>
@@ -25,12 +29,10 @@
             <div class="basic-title">
               <!-- 设备序列号显示 -->
               <div class="title-text">{{ item.strserno || '-' }}</div>
-              <!-- 操作按钮组:详情、配置、重启三种操作 -->
+              <!-- 操作按钮:详情 / 恢复默认配置 -->
               <div class="title-btn">
-                <div :class="active == index ? 'btn-active' : 'btn'" @click="handlerClick('detail', item, index)">详情
-                </div>
-                <div class="btn" @click="handlerClick('config', item, index)">配置</div>
-                <div class="btn" @click="handlerClick('restart', item, index)">重启</div>
+                <div :class="active == index ? 'btn-active' : 'btn'" @click="handlerClick('config', item, index)">详情</div>
+                <div class="btn" @click="handlerClick('restoreDefault', item, index)">恢复默认配置</div>
               </div>
             </div>
             <!-- 设备详细信息区域:2x2网格布局展示关键信息 -->
@@ -59,7 +61,7 @@
 import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
 import { managesysList, monitorSystem } from '../hsqHome.api.js'
 
-let active = ref(0)
+let active = ref(-1)
 /** 响应式数据:当前场景/系统的唯一标识ID,用于API请求参数 */
 const systemId = ref('')
 
@@ -136,15 +138,12 @@ function changeNav(item: any, index: number) {
 }
 
 /**
- * 设备操作按钮点击事件处理函数
- * 统一处理详情、配置、重启三种操作按钮的点击事件
+ * 设备配置按钮点击事件处理函数
  * 将操作类型和设备数据封装成事件对象传递给父组件
  *
- * @param type - 操作类型字符串
- *   - 'detail': 查看设备详细信息
- *   - 'config': 查看或修改设备配置
- *   - 'restart': 执行设备重启操作
+ * @param type - 操作类型字符串(当前仅 'config')
  * @param item - 被操作的目标设备完整数据对象
+ * @param index - 设备在列表中的索引
  */
 function handlerClick(type: string, item: any, index: number) {
   // 触发details事件,将操作类型和设备数据传递给父组件(deviceManger.vue)
@@ -172,16 +171,11 @@ async function getData() {
 
   // 统计并记录设备总数量,用于标题栏显示
   count.value = sourceList.value.length;
-  // 初始化时,默认选中第一个设备
-  if (sourceList.value.length > 0) {
-    active.value = 0
-    handlerClick('detail', sourceList.value[0], 0)
-  }
 }
 
 /** 搜索条件变化时重置选中项,避免索引越界 */
 watch(searchData, () => {
-  active.value = 0
+  active.value = -1
 })
 
 /**
@@ -251,6 +245,8 @@ onUnmounted(() => {
   --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-2.png');
   /* 设备卡片背景图 */
   --image-box-bg3: url('@/assets/images/home-container/configurable/hsq/2-7.png');
+  /* 设备卡片选中态背景图(青蓝科技框) */
+  --image-box-bg3-active: url('@/assets/images/home-container/configurable/hsq/device-item-active.png');
   /* 状态指示器外框背景图 */
   --image-box-bg4: url('@/assets/images/home-container/configurable/hsq/2-8.png');
   /* 设备标题区域背景图 */
@@ -377,6 +373,24 @@ onUnmounted(() => {
     background-size: 100% 100%;
     margin-bottom: 10px;
     /* 卡片之间的垂直间距 */
+    transition: background 0.2s ease, box-shadow 0.2s ease;
+
+    /* 选中态:使用设计稿青蓝科技框背景图 */
+    &.basic-device-active {
+      background: var(--image-box-bg3-active) center / 100% 100% no-repeat;
+      box-shadow: 0 0 12px rgba(0, 220, 255, 0.32);
+      border: none;
+
+      /* 背景图已含顶部装饰线与切角,取消 title 额外装饰,避免叠线 */
+      .basic-title {
+        background: none;
+      }
+
+      /* 背景图左上已有圆环,取消原状态外框,仅保留状态圆点 */
+      .icon-left {
+        background: none;
+      }
+    }
 
     /* 左上角状态指示器外框容器 */
     .icon-left {
@@ -448,46 +462,52 @@ onUnmounted(() => {
     /* 操作按钮组容器 */
     .title-btn {
       height: 100%;
-      /* 高度撑满父容器 */
       display: flex;
-      /* 弹性布局:按钮水平排列 */
       align-items: center;
-      /* 垂直居中 */
+      gap: 4px;
+      flex-shrink: 0;
     }
 
-    /* 单个操作按钮样式(详情/配置/重启共用) */
-    .btn {
-      width: 40px;
-      /* 固定宽度 */
-      display: flex;
-      /* 弹性布局 */
+    /* 详情 / 恢复默认配置:对齐设计稿扁平蓝底白字按钮 */
+    .btn,
+    .btn-active {
+      min-width: 48px;
+      height: 22px;
+      padding: 0 10px;
+      box-sizing: border-box;
+      display: inline-flex;
       justify-content: center;
-      /* 文字水平居中 */
       align-items: center;
-      /* 文字垂直居中 */
-      background-color: #1e7db4;
-      /* 深蓝色按钮背景 */
-      margin: 0px 2px;
-      /* 按钮之间的水平间距 */
+      margin: 0;
+      border: 1px solid #36c7ff;
+      border-radius: 0;
+      background: #267eb5;
+      color: #ffffff;
+      font-size: 12px;
+      letter-spacing: 0.5px;
+      white-space: nowrap;
       cursor: pointer;
-      /* 鼠标指针:提示可点击 */
+      box-shadow: none;
+      transition: color 0.2s, border-color 0.2s, background 0.2s;
+
+      &:hover {
+        color: #ffffff;
+        border-color: #01fefc;
+        background: #2f96d0;
+        box-shadow: none;
+      }
+
+      &:active {
+        background: #1f6a99;
+      }
     }
 
+    /* 选中项上的详情按钮:保持同风格,略提亮 */
     .btn-active {
-      width: 40px;
-      /* 固定宽度 */
-      display: flex;
-      /* 弹性布局 */
-      justify-content: center;
-      /* 文字水平居中 */
-      align-items: center;
-      /* 文字垂直居中 */
-      margin: 0px 2px;
-      /* 按钮之间的水平间距 */
-      cursor: pointer;
-      background-color: #1e7db4;
-      /* 深蓝色按钮背景 */
-      border: 1px solid #90cff3;
+      color: #ffffff;
+      border-color: #01fefc;
+      background: #2a8fc4;
+      box-shadow: none;
     }
 
     /* 设备详细信息区域:2x2网格布局展示四项关键数据 */

+ 192 - 103
src/views/vent/monitorManager/hsqHome/components/DeviceRight.vue

@@ -5,125 +5,131 @@
     </div>
     <div class="list-content">
       <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
+          :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="basic-box">
         <basicBorder title="【设备管理】">
           <div class="temp-warn">
             <div class="warn-item">
               <div class="item-label">设备号 : </div>
-              <a-input class="item-input" v-model:value="formData.strinstallpos" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.deviceNo" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">设备名称 : </div>
-              <a-input class="item-input" v-model:value="formData.strserno" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.deviceName" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">分组 : </div>
-              <a-input class="item-input" v-model:value="formData.manufacturer" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.groupName" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">设备IP : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_east" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.ip" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">通讯端口 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.cmdPort" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-basic">
-                <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+                <a-checkbox v-model:checked="formData.hasInfrared"></a-checkbox>
                 <div style="margin-right: 8px;">是否带红外 </div>
               </div>
               <div class="item-basic">
-                <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
+                <a-checkbox v-model:checked="formData.hasHd"></a-checkbox>
                 <div style="margin-right: 8px;">是否高清 </div>
               </div>
               <div class="item-basic">
-                <a-checkbox v-model:checked="formData.yjgb"></a-checkbox>
-                <div >是否带云台 </div>
+                <a-checkbox v-model:checked="formData.hasPtz"></a-checkbox>
+                <div>是否带云台 </div>
               </div>
             </div>
             <div class="warn-item">
               <div class="item-label">红外IP : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irIp" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外端口 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irPort" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外类型 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irType" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外用户名 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irUser" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外密码 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irPassword" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外流地址 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irStreamUrl" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">红外子码流地址 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.irSubStreamUrl" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">水平视场角 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.horFov" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">垂直视场角 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.verFov" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清IP : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdIp" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清端口 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdPort" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清类型 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdType" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清用户名 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdUser" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清密码 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdPassword" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清流地址 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdStreamUrl" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">高清子码流地址 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.hdSubStreamUrl" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">安装高度 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.installHeight" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">正北偏角 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.northDeclination" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">经度 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.longitude" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
               <div class="item-label">纬度 : </div>
-              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
+              <a-input class="item-input" v-model:value="formData.latitude" placeholder="请输入" size="small" />
             </div>
             <div class="btn-box">
               <div class="baisc-btn">恢复默认</div>
@@ -134,57 +140,6 @@
           </div>
         </basicBorder>
       </div>
-      <!-- <div class="basic-box1">
-        <basicBorder title="【图像参数配置】">
-          <div class="temp-warn">
-            <div class="warn-item">
-              <div class="item-basic">
-                <div class="item-label">亮度 : </div>
-                <a-input class="item-input" v-model:value="formData.ld" placeholder="请输入" size="small" />
-              </div>
-              <div class="item-basic">
-                <div class="item-label">对比度 : </div>
-                <a-input class="item-input" v-model:value="formData.dbd" placeholder="请输入" size="small" />
-              </div>
-            </div>
-            <div class="warn-item">
-              <div class="item-basic">
-                <div class="item-label">饱和度 : </div>
-                <a-input class="item-input" v-model:value="formData.bhd" placeholder="请输入" size="small" />
-              </div>
-              <div class="item-basic">
-                <div class="item-label">锐度 : </div>
-                <a-input class="item-input" v-model:value="formData.sd" placeholder="请输入" size="small" />
-              </div>
-            </div>
-            <div class="warn-item">
-              <div class="item-basic">
-                <div class="item-label">色温 : </div>
-                <a-input class="item-input" v-model:value="formData.sw" placeholder="请输入" size="small" />
-              </div>
-              <div class="item-basic">
-                <div class="item-label">增益 : </div>
-                <a-input class="item-input" v-model:value="formData.zy" placeholder="请输入" size="small" />
-              </div>
-            </div>
-            <div class="warn-item">
-              <div class="item-basic">
-                <div class="item-label">曝光 : </div>
-                <a-input class="item-input" v-model:value="formData.bg" placeholder="请输入" size="small" />
-              </div>
-              <div class="item-basic">
-                <div class="item-label">白平衡 : </div>
-                <a-input class="item-input" v-model:value="formData.bph" placeholder="请输入" size="small" />
-              </div>
-            </div>
-          </div>
-        </basicBorder>
-      </div> -->
-      <!-- <div class="basic-box1">
-        <basicBorder title="【摄像头配置】">
-        </basicBorder>
-      </div> -->
-
     </div>
   </div>
 </template>
@@ -193,30 +148,170 @@
 import { reactive, ref, watch } from 'vue'
 import basicBorder from './basicBorder.vue'
 
-let props = defineProps({
+/** 设备配置表单字段 */
+interface DeviceConfigForm {
+  deviceNo: string
+  deviceName: string
+  groupName: string
+  ip: string
+  cmdPort: string
+  hasInfrared: boolean
+  hasHd: boolean
+  hasPtz: boolean
+  irIp: string
+  irPort: string
+  irType: string
+  irUser: string
+  irPassword: string
+  irStreamUrl: string
+  irSubStreamUrl: string
+  horFov: string
+  verFov: string
+  hdIp: string
+  hdPort: string
+  hdType: string
+  hdUser: string
+  hdPassword: string
+  hdStreamUrl: string
+  hdSubStreamUrl: string
+  installHeight: string
+  northDeclination: string
+  longitude: string
+  latitude: string
+}
+
+const props = defineProps({
   detailData: {
     type: Object,
-    default: () => { },
+    default: () => ({}),
   },
 })
 
-//当前激活nav选项
-let activeIndex = ref(0)
-let navList = ref<any[]>([
-  { label: '光谱摄像机', value: '0' },
-])
+const activeIndex = ref(0)
+const navList = ref<any[]>([{ label: '光谱摄像机', value: '0' }])
 
-let formData = ref<any>({})
+function createEmptyForm(): DeviceConfigForm {
+  return {
+    deviceNo: '',
+    deviceName: '',
+    groupName: '',
+    ip: '',
+    cmdPort: '',
+    hasInfrared: false,
+    hasHd: false,
+    hasPtz: false,
+    irIp: '',
+    irPort: '',
+    irType: '',
+    irUser: '',
+    irPassword: '',
+    irStreamUrl: '',
+    irSubStreamUrl: '',
+    horFov: '',
+    verFov: '',
+    hdIp: '',
+    hdPort: '',
+    hdType: '',
+    hdUser: '',
+    hdPassword: '',
+    hdStreamUrl: '',
+    hdSubStreamUrl: '',
+    installHeight: '',
+    northDeclination: '',
+    longitude: '',
+    latitude: '',
+  }
+}
+
+const formData = reactive<DeviceConfigForm>(createEmptyForm())
 
-//nav选项切换
-function changeNav(item, index) {
+function changeNav(_item: any, index: number) {
   activeIndex.value = index
 }
 
-watch(() => props.detailData, (newVal, oldVal) => {
-  console.log(newVal, '9900')
-  formData.value = Object.assign({}, newVal.readData, newVal,)
-}, { immediate: true })
+/** 从多个候选字段中取第一个有效值 */
+function pickValue(source: Record<string, any>, keys: string[]) {
+  for (const key of keys) {
+    const value = source[key]
+    if (value !== undefined && value !== null && value !== '') return value
+  }
+  return ''
+}
+
+function toText(value: unknown) {
+  if (value === undefined || value === null) return ''
+  return String(value)
+}
+
+function toBool(value: unknown) {
+  if (typeof value === 'boolean') return value
+  if (typeof value === 'string') {
+    const text = value.trim().toLowerCase()
+    if (['1', 'true', 'yes', 'y'].includes(text)) return true
+    if (['0', 'false', 'no', 'n', ''].includes(text)) return false
+  }
+  const num = Number(value)
+  if (Number.isFinite(num)) return num !== 0
+  return Boolean(value)
+}
+
+/** 将列表选中设备数据回填到设备配置表单 */
+function applyDetailToForm(detail: Record<string, any> | null | undefined) {
+  const empty = createEmptyForm()
+  if (!detail || typeof detail !== 'object' || !Object.keys(detail).length) {
+    Object.assign(formData, empty)
+    return
+  }
+
+  const readData =
+    detail.readData && typeof detail.readData === 'object' ? (detail.readData as Record<string, any>) : {}
+  const source = { ...readData, ...detail }
+
+  Object.assign(formData, {
+    deviceNo: toText(
+      pickValue(source, ['deviceNo', 'strserno', 'deviceID', 'deviceId', 'deviceid', 'id']),
+    ),
+    deviceName: toText(pickValue(source, ['deviceName', 'strname', 'name'])),
+    groupName: toText(pickValue(source, ['groupName', 'group', 'manufacturer', 'typeName'])),
+    ip: toText(pickValue(source, ['ip', 'strip', 'deviceIp'])),
+    cmdPort: toText(pickValue(source, ['cmdPort', 'port', 'commPort', 'alarmPort'])),
+    hasInfrared: toBool(pickValue(source, ['hasInfrared', 'irEnable', 'isInfrared', 'yjgb'])),
+    hasHd: toBool(pickValue(source, ['hasHd', 'hdEnable', 'isHd', 'isHD'])),
+    hasPtz: toBool(pickValue(source, ['hasPtz', 'ptzEnable', 'isPtz'])),
+    irIp: toText(pickValue(source, ['irIp', 'infraredIp', 'ir_ip'])),
+    irPort: toText(pickValue(source, ['irPort', 'infraredPort', 'ir_port'])),
+    irType: toText(pickValue(source, ['irType', 'infraredType', 'ir_type'])),
+    irUser: toText(pickValue(source, ['irUser', 'irUsername', 'infraredUser', 'ir_user'])),
+    irPassword: toText(pickValue(source, ['irPassword', 'infraredPassword', 'ir_password'])),
+    irStreamUrl: toText(pickValue(source, ['irStreamUrl', 'infraredStreamUrl', 'irUrl', 'ir_url'])),
+    irSubStreamUrl: toText(
+      pickValue(source, ['irSubStreamUrl', 'infraredSubStreamUrl', 'irSubUrl', 'ir_sub_url']),
+    ),
+    horFov: toText(pickValue(source, ['horFov', 'hor_fov'])),
+    verFov: toText(pickValue(source, ['verFov', 'ver_fov'])),
+    hdIp: toText(pickValue(source, ['hdIp', 'visibleIp', 'hd_ip'])),
+    hdPort: toText(pickValue(source, ['hdPort', 'visiblePort', 'hd_port'])),
+    hdType: toText(pickValue(source, ['hdType', 'visibleType', 'hd_type'])),
+    hdUser: toText(pickValue(source, ['hdUser', 'hdUsername', 'visibleUser', 'hd_user'])),
+    hdPassword: toText(pickValue(source, ['hdPassword', 'visiblePassword', 'hd_password'])),
+    hdStreamUrl: toText(pickValue(source, ['hdStreamUrl', 'visibleStreamUrl', 'hdUrl', 'hd_url'])),
+    hdSubStreamUrl: toText(
+      pickValue(source, ['hdSubStreamUrl', 'visibleSubStreamUrl', 'hdSubUrl', 'hd_sub_url']),
+    ),
+    installHeight: toText(pickValue(source, ['installHeight', 'height', 'install_height'])),
+    northDeclination: toText(pickValue(source, ['northDeclination', 'northAngle', 'north_declination'])),
+    longitude: toText(pickValue(source, ['longitude', 'alarm_east', 'lng', 'lon'])),
+    latitude: toText(pickValue(source, ['latitude', 'alarm_north', 'lat'])),
+  })
+}
+
+watch(
+  () => props.detailData,
+  (newVal) => {
+    applyDetailToForm(newVal as Record<string, any>)
+  },
+  { immediate: true, deep: true },
+)
 </script>
 
 <style lang="less" scoped>
@@ -282,13 +377,12 @@ watch(() => props.detailData, (newVal, oldVal) => {
   .basic-box {
     height: calc(100% - 47px);
     margin-bottom: 10px;
+    overflow: auto;
   }
 
   .basic-box1 {
-    // height: 210px;
     height: 430px;
     margin-bottom: 10px;
-
   }
 
   .temp-warn {
@@ -323,8 +417,6 @@ watch(() => props.detailData, (newVal, oldVal) => {
 
   .item-basic {
     display: flex;
-    // flex: 1;
-    // justify-content: space-between;
     align-items: center;
   }
 
@@ -340,9 +432,6 @@ watch(() => props.detailData, (newVal, oldVal) => {
     justify-content: center;
     align-items: center;
     gap: 10px;
-    // position: absolute;
-    // right: 16px;
-    // bottom: 18px;
   }
 
   .baisc-btn {

Plik diff jest za duży
+ 750 - 97
src/views/vent/monitorManager/hsqHome/components/DeviceTree.vue


+ 15 - 9
src/views/vent/monitorManager/hsqHome/components/DeviceView.vue

@@ -682,8 +682,10 @@ defineExpose({
 ::-webkit-scrollbar {
     display: none;
   }
+</style>
 
-/* 最新热源 / 最新闯入 弹窗(风格对齐报警自动弹窗) */
+<style lang="less">
+/* 最新热源 / 最新闯入 弹窗(Teleport 到 body,非 scoped,对齐报警自动弹窗) */
 .latest-alarm-mask {
   position: fixed;
   inset: 0;
@@ -798,33 +800,37 @@ defineExpose({
   align-items: center;
   justify-content: center;
   color: rgba(200, 220, 235, 0.55);
-  font-size: 14px;
+  font-size: 13px;
 }
 
 .latest-alarm-overlay {
   position: absolute;
-  color: #fff;
+  color: #ff2a2a;
   font-size: 13px;
+  font-weight: 600;
   text-shadow: 0 0 4px rgba(0, 0, 0, 0.85);
   pointer-events: none;
 
   &.bottom-right {
-    right: 10px;
+    right: 12px;
     bottom: 10px;
   }
 }
 
 .latest-alarm-footer {
   display: flex;
-  flex-wrap: wrap;
   align-items: center;
-  gap: 8px;
-  padding: 10px 8px 12px;
-  color: #cfe9ff;
+  flex-wrap: wrap;
+  gap: 6px 8px;
+  min-height: 36px;
+  padding: 8px 12px 10px;
+  color: #e8f4ff;
   font-size: 13px;
+  background: rgba(2, 12, 24, 0.95);
+  border-top: 1px solid rgba(0, 183, 255, 0.25);
 }
 
 .latest-alarm-sep {
-  color: rgba(1, 254, 252, 0.55);
+  color: rgba(180, 210, 230, 0.55);
 }
 </style>

+ 49 - 5
src/views/vent/monitorManager/hsqHome/components/HistoryFilterTree.vue

@@ -83,8 +83,10 @@ import type {
   HistoryFilterDeviceNode,
   HistoryFilterGroupNode,
   HistoryFilterPayload,
+  AlarmStatResult,
 } from './type'
-import { managesysList, monitorSystem } from '../hsqHome.api'
+import { managesysList, monitorSystem, getAlarmStat } from '../hsqHome.api'
+import { useMessage } from '/@/hooks/web/useMessage'
 
 const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
 
@@ -97,10 +99,14 @@ const props = defineProps({
 })
 
 const emit = defineEmits<{
-  (e: 'search', payload: HistoryFilterPayload): void
+  (e: 'search', payload: HistoryFilterPayload, result: AlarmStatResult): void
   (e: 'export', payload: HistoryFilterPayload): void
 }>()
 
+const { createMessage } = useMessage()
+/** 检索请求中,避免重复点击 */
+const searching = ref(false)
+
 /** 时间范围:[开始时间, 结束时间],默认近 7 天至当前 */
 const timeRange = ref<[string, string]>([
   dayjs().subtract(7, 'day').startOf('day').format(DATE_FORMAT),
@@ -245,16 +251,54 @@ function buildPayload(): HistoryFilterPayload {
   }
 }
 
+/** 解析 getAlarmStat 返回:整体数据从 result 取(兼容 defHttp 已解包) */
+function normalizeAlarmStatResult(res: any): AlarmStatResult {
+  const data = res?.result ?? res ?? {}
+  return {
+    dayCountArray: Array.isArray(data.dayCountArray) ? data.dayCountArray : [],
+    devCountArray: Array.isArray(data.devCountArray) ? data.devCountArray : [],
+    monCountArray: Array.isArray(data.monCountArray) ? data.monCountArray : [],
+  }
+}
+
 function onSearch() {
-  emit('search', buildPayload())
+  void handleSearch()
+}
+
+/** 检索:未勾选设备提示;已勾选则按界面时间调用 getAlarmStat */
+async function handleSearch(options?: { silent?: boolean }) {
+  const payload = buildPayload()
+  if (!payload.deviceIds.length) {
+    if (!options?.silent) createMessage.warning('请勾选设备')
+    return
+  }
+  if (searching.value) return
+  searching.value = true
+  try {
+    const res = await getAlarmStat({
+      deviceIdList: payload.deviceIds,
+      startTime: payload.startTime,
+      endTime: payload.endTime,
+    })
+    console.log(res, '报警统计 getAlarmStat')
+    const result = normalizeAlarmStatResult(res)
+    emit('search', payload, result)
+  } catch (e) {
+    console.error('获取报警统计失败', e)
+    if (!options?.silent) createMessage.error('获取报警统计失败')
+  } finally {
+    searching.value = false
+  }
 }
 
 function onExport() {
   emit('export', buildPayload())
 }
 
-onMounted(() => {
-  loadDeviceTree()
+/** 页面初始化:拉设备树后自动 getAlarmStat 回填图表 */
+onMounted(async () => {
+  await loadDeviceTree()
+  await handleSearch({ silent: true })
 })
 </script>
 

+ 45 - 0
src/views/vent/monitorManager/hsqHome/components/type.ts

@@ -102,7 +102,10 @@ export interface RecordFileItem {
 
 /** 报警自动弹窗展示数据 */
 export interface AutoAlarmPopupData {
+  /** 兼容旧单图字段(取 imageUrls[0]) */
   imageUrl: string
+  /** 报警图片列表(支持 picName / picName2 等多图) */
+  imageUrls: string[]
   maxTemp: string
   overlayTime: string
   deviceLabel: string
@@ -110,6 +113,8 @@ export interface AutoAlarmPopupData {
   alarmDevice: string
   alarmType: string
   hotspotValue: string
+  /** 推送原文(如 msgTxt) */
+  msgTxt: string
 }
 
 /** 历史筛选树子节点 */
@@ -136,3 +141,43 @@ export interface HistoryFilterPayload {
   endTime: string
   deviceIds: string[]
 }
+
+/** 报警数量日统计项 */
+export interface AlarmDayCountItem {
+  count: number
+  time: string
+}
+
+/** 报警数量点位统计项 */
+export interface AlarmDevCountItem {
+  count: number
+  devName: string
+  devPos: string
+}
+
+/** 报警数量月统计项 */
+export interface AlarmMonCountItem {
+  count: number
+  time: string
+}
+
+/** getAlarmStat 返回的 result 结构 */
+export interface AlarmStatResult {
+  dayCountArray: AlarmDayCountItem[]
+  devCountArray: AlarmDevCountItem[]
+  monCountArray: AlarmMonCountItem[]
+}
+
+/** 抓拍图片记录(picVideoList.records) */
+export interface PicVideoRecord {
+  id: string
+  deviceId: string
+  deviceName: string
+  devicePos: string
+  /** 可见光路径 */
+  path1: string
+  /** 红外路径 */
+  path2: string
+  createTime: string
+  type: number
+}

+ 20 - 24
src/views/vent/monitorManager/hsqHome/deviceManger.vue

@@ -9,7 +9,7 @@
     <!-- 中间区域:设备详细信息展示 -->
     <div class="basic-box">
       <!-- DeviceCenter组件:接收并展示选中设备的详细数据 -->
-      <DeviceCenter :detailData="detailData"></DeviceCenter>
+      <DeviceCenter ref="deviceCenterRef" :detailData="detailData"></DeviceCenter>
     </div>
     <!-- 右侧区域:设备配置信息展示 -->
     <div class="basic-box">
@@ -32,34 +32,30 @@ const strserno = ref<any>({})
 // 响应式数据:存储选中设备的详细信息,传递给DeviceCenter组件展示
 const detailData = ref<any>({})
 
-// 响应式数据:存储设备的配置信息,传递给DeviceRight组件展示(包括普通配置和重启配置)
+// 响应式数据:存储设备的配置信息,传递给DeviceRight组件展示
 const configData = ref<any>({})
-
+/** 前端配置区引用(供列表「恢复默认配置」调用) */
+const deviceCenterRef = ref<InstanceType<typeof DeviceCenter> | null>(null)
 
 /**
- * 处理设备详情事件回调函数
- * @param item - 包含type和value的事件对象
- *   - type: 事件类型 ('detail' | 'config' | 'restart')
- *   - value: 对应的详细数据
- * 根据不同的类型将数据分配到对应的响应式变量中
+ * 处理设备列表操作事件回调
+ * @param item - 包含 type 和 value 的事件对象
+ *   - type: 事件类型('config' | 'restoreDefault')
+ *   - value: 对应设备数据
  */
 function handlerDetail(item: any) {
-  console.log(item, '详情--handlerDetail')
-  switch (item.type) {
-    // 处理设备详情类型:更新中间区域的设备详细数据
-    case 'detail':
-      detailData.value = item.value
-      break
-    // 处理配置详情类型:更新右侧区域的配置数据
-    case 'config':
-      configData.value = item.value
-      break
-    // 处理重启配置类型:更新右侧区域的配置数据(用于重启操作)
-    case 'restart':
-      configData.value = item.value
-      break
-    default:
-    // 其他 cases 待添加
+  console.log(item, '配置--handlerDetail')
+  if (item.type === 'config') {
+    configData.value = item.value
+    // 同步给前端配置区并触发 getSysCfg(每次点击详情都刷新)
+    detailData.value = { ...(item.value || {}) }
+    return
+  }
+  if (item.type === 'restoreDefault') {
+    configData.value = item.value
+    // 先按该设备恢复配置,再同步 detailData(避免 watch 重复请求)
+    deviceCenterRef.value?.onRestoreDefaultCfg?.(item.value)
+    detailData.value = { ...(item.value || {}) }
   }
 }
 

+ 23 - 6
src/views/vent/monitorManager/hsqHome/historyReport.vue

@@ -1,18 +1,18 @@
 <template>
   <div class="history-report">
     <div class="left-box">
-      <HistoryFilterTree></HistoryFilterTree>
+      <HistoryFilterTree @search="onHistorySearch"></HistoryFilterTree>
     </div>
     <div class="right-box">
       <div class="right-box-top">
-        <AlarmPoint></AlarmPoint>
+        <AlarmPoint :chart-data="alarmStat.devCountArray"></AlarmPoint>
       </div>
       <div class="right-box-bottom">
         <div class="right-box-bottom-left">
-          <AlarmDay></AlarmDay>
+          <AlarmDay :chart-data="alarmStat.dayCountArray"></AlarmDay>
         </div>
         <div class="right-box-bottom-right">
-          <AlarmMonth></AlarmMonth>
+          <AlarmMonth :chart-data="alarmStat.monCountArray"></AlarmMonth>
         </div>
       </div>
     </div>
@@ -20,13 +20,30 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { reactive } from 'vue'
 import HistoryFilterTree from './components/HistoryFilterTree.vue'
 import AlarmPoint from './components/AlarmPoint.vue'
 import AlarmDay from './components/AlarmDay.vue'
 import AlarmMonth from './components/AlarmMonth.vue'
+import type {
+  AlarmStatResult,
+  HistoryFilterPayload,
+} from './components/type'
 
- </script>
+/** 报警统计图表数据(来自 getAlarmStat.result) */
+const alarmStat = reactive<AlarmStatResult>({
+  dayCountArray: [],
+  devCountArray: [],
+  monCountArray: [],
+})
+
+/** 检索成功后回填三类统计到对应图表 */
+function onHistorySearch(_payload: HistoryFilterPayload, result: AlarmStatResult) {
+  alarmStat.dayCountArray = result?.dayCountArray || []
+  alarmStat.devCountArray = result?.devCountArray || []
+  alarmStat.monCountArray = result?.monCountArray || []
+}
+</script>
 
 <style lang="less" scoped>
 @import '/@/design/theme.less';

+ 8 - 1
src/views/vent/monitorManager/hsqHome/hsqHome.api.ts

@@ -28,6 +28,7 @@ enum Api {
   setRunModeCfg = '/api/zc/device/setRunModeCfg',//设置云台运行模式配置
   setPesudoColor = '/api/zc/device/setPesudoColor',//设置伪彩色
   getAlarmStat = '/safety/dscAlarmLog/getAlarmStat',//获取报警统计信息-数据曲线
+  picVideoList = '/safety/dscAlarmLog/picVideoList',//双光谱摄像机抓拍和录像记录-分页列表查询
 }
 /**
 * 日志列表接口
@@ -173,4 +174,10 @@ export const setPesudoColor = (params) => defHttp.post({ url: Api.setPesudoColor
 * 获取报警统计信息-数据曲线接口
 * @param params
 */
-export const getAlarmStat = (params) => defHttp.post({ url: Api.getAlarmStat, params });
+export const getAlarmStat = (params) => defHttp.post({ url: Api.getAlarmStat, params });
+
+/**
+* 获取报警统计信息-数据曲线接口
+* @param params
+*/
+export const picVideoList= (params) => defHttp.get({ url: Api.picVideoList, params }, { joinParamsToUrl: true });

+ 3 - 3
src/views/vent/monitorManager/hsqHome/hsqHome.data.ts

@@ -403,12 +403,12 @@ export let titleList =[
 { label: '时间', value: 'updateTime' },
 ]
 
-/** 角度默认值(刷新时恢复) */
+/** 角度默认值(刷新时恢复):水平 0~360,垂直 0~45 */
 export const ANGLE_DEFAULT: AngleState = {
 hMin: 0,
 hMax: 360,
-vMin: 90,
-vMax: 180,
+vMin: 0,
+vMax: 45,
 }
 
 export const angleFields: AngleField[] = [

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

@@ -122,25 +122,12 @@ function onCameraSelect(camera: any) {
   }
 }
 
-/** 云台焦距 / 焦点 / 光圈:不调接口,直接作用于当前选中画面 */
+/** 云台焦距 / 焦点 / 光圈已改走设备接口,此处不再本地调节画面 */
 function onPtzAction(payload: { type: string; value: string }) {
   if (!payload?.type || payload.value === 'stop') return
-  if (payload.type === 'zoom') {
-    if (payload.value === 'plus') deviceViewRef.value?.zoomSelectedView?.(1)
-    else if (payload.value === 'minus') deviceViewRef.value?.zoomSelectedView?.(-1)
+  if (payload.type === 'zoom' || payload.type === 'focus' || payload.type === 'iris') {
     return
   }
-  if (payload.type === 'focus') {
-    // plus=焦点前调(更清晰),minus=焦点后调(更模糊)
-    if (payload.value === 'plus') deviceViewRef.value?.focusSelectedView?.(-1)
-    else if (payload.value === 'minus') deviceViewRef.value?.focusSelectedView?.(1)
-    return
-  }
-  if (payload.type === 'iris') {
-    // plus=光圈扩大(更亮),minus=光圈缩小(更暗)
-    if (payload.value === 'plus') deviceViewRef.value?.irisSelectedView?.(1)
-    else if (payload.value === 'minus') deviceViewRef.value?.irisSelectedView?.(-1)
-  }
 }
 
 /**

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików