liujintao пре 1 месец
родитељ
комит
c97f2d369b

+ 143 - 77
src/views/settings/audioVideo/components/video/index.vue

@@ -7,15 +7,20 @@
         <span class="form-item__label">Resolution:</span>
         <div class="form-item__control">
           <el-select v-model="mainStream.MainResolution">
-            <el-option v-for="item in mainResolutionOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in mainResolutionOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">Encoding Format:</span>
         <div class="form-item__control">
-          <el-select v-model="mainStream.encodeType">
-            <el-option v-for="item in encodeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+          <el-select v-model="mainStream.MainPayloadType" disabled>
+            <el-option v-for="item in encodeTypeOptions" :key="item.value" :label="item.label" :value="item.value"/>
           </el-select>
         </div>
       </div>
@@ -23,14 +28,24 @@
         <span class="form-item__label">Frame Rate:</span>
         <div class="form-item__control">
           <el-select v-model="mainStream.MainFps">
-            <el-option v-for="item in frameRateOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in frameRateOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">Bitrate:</span>
         <div class="form-item__control">
-          <el-input-number v-model="mainStream.MainBitRate" :min="1024" :max="3072" :controls="false" />
+          <el-input-number
+              v-model="mainStream.MainBitRate"
+              :min="1024"
+              :max="3072"
+              :controls="false"
+          />
           <span class="form-item__hint">(1024,3072)</span>
         </div>
       </div>
@@ -38,14 +53,24 @@
         <span class="form-item__label">Bitrate Control:</span>
         <div class="form-item__control">
           <el-select v-model="mainStream.MainEcdFmat">
-            <el-option v-for="item in bitRateControlOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in bitRateControlOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">I-Frame Interval:</span>
         <div class="form-item__control">
-          <el-input-number v-model="mainStream.MainFrameTime" :min="100" :max="200" :controls="false" />
+          <el-input-number
+              v-model="mainStream.MainFrameTime"
+              :min="100"
+              :max="200"
+              :controls="false"
+          />
           <span class="form-item__hint">(100,200)</span>
         </div>
       </div>
@@ -58,15 +83,20 @@
         <span class="form-item__label">Resolution:</span>
         <div class="form-item__control">
           <el-select v-model="subStream.SecondResolution">
-            <el-option v-for="item in subResolutionOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in subResolutionOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">Encoding Format:</span>
         <div class="form-item__control">
-          <el-select v-model="subStream.encodeType">
-            <el-option v-for="item in encodeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+          <el-select v-model="subStream.SecondPayloadType" disabled>
+            <el-option v-for="item in encodeTypeOptions" :key="item.value" :label="item.label" :value="item.value"/>
           </el-select>
         </div>
       </div>
@@ -74,14 +104,24 @@
         <span class="form-item__label">Frame Rate:</span>
         <div class="form-item__control">
           <el-select v-model="subStream.SecondFps">
-            <el-option v-for="item in frameRateOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in frameRateOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">Bitrate:</span>
         <div class="form-item__control">
-          <el-input-number v-model="subStream.SecondBitRate" :min="1024" :max="3072" :controls="false" />
+          <el-input-number
+              v-model="subStream.SecondBitRate"
+              :min="1024"
+              :max="3072"
+              :controls="false"
+          />
           <span class="form-item__hint">(1024,3072)</span>
         </div>
       </div>
@@ -89,14 +129,24 @@
         <span class="form-item__label">Bitrate Control:</span>
         <div class="form-item__control">
           <el-select v-model="subStream.SecondEcdFmat">
-            <el-option v-for="item in bitRateControlOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option
+                v-for="item in bitRateControlOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
           </el-select>
         </div>
       </div>
       <div class="form-item">
         <span class="form-item__label">I-Frame Interval:</span>
         <div class="form-item__control">
-          <el-input-number v-model="subStream.SecondFrameTime" :min="100" :max="200" :controls="false" />
+          <el-input-number
+              v-model="subStream.SecondFrameTime"
+              :min="100"
+              :max="200"
+              :controls="false"
+          />
           <span class="form-item__hint">(100,200)</span>
         </div>
       </div>
@@ -112,24 +162,29 @@
     <!-- 底部按钮 -->
     <div class="btn-wrapper">
       <el-button round class="btn-reset" @click="resetDefaults">Restore Defaults</el-button>
-      <el-button round type="primary" class="btn-save" @click="saveParams">Save</el-button>
+      <el-button round type="primary" class="btn-save" @click="saveParams" v-loading="loading">Save</el-button>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { getVideoEncodePara, putVideoEncodePara, cameraReset } from '@/api/setting'
-import { useUserStore } from '@/stores/modules/user'
-import { useRouter } from 'vue-router'
+import {reactive, ref, onMounted} from 'vue'
+import {ElMessage, ElMessageBox} from 'element-plus'
+import {
+  getVideoEncodePara,
+  putVideoEncodePara,
+  cameraReset
+} from '@/api/setting'
+import {useUserStore} from '@/stores/modules/user'
+import {useRouter} from 'vue-router'
 
 const router = useRouter()
+const loading = ref(false)
 
 // 主码流参数
 const mainStream = reactive({
   MainResolution: 5,
-  encodeType: 0,
+  MainPayloadType: 96,
   MainFps: 25,
   MainBitRate: 3072,
   MainEcdFmat: 0,
@@ -139,7 +194,7 @@ const mainStream = reactive({
 // 子码流参数
 const subStream = reactive({
   SecondResolution: 0,
-  encodeType: 0,
+  SecondPayloadType: 96,
   SecondFps: 25,
   SecondBitRate: 3072,
   SecondEcdFmat: 0,
@@ -149,67 +204,63 @@ const subStream = reactive({
 // 实时模式
 const realtimeMode = ref(false)
 
-
 // 主码流分辨率选项
 const mainResolutionOptions = [
-  { value: 5, label: '2880*1620 (5M)' },
-  { value: 4, label: '2560*1440 (4M)' },
-  { value: 3, label: '2304*1296 (3M)' },
-  { value: 2, label: '1920*1080 (1080P)' },
-  { value: 1, label: '1280*720 (720P)' },
+  {value: 5, label: '2880*1620 (5M)'},
+  {value: 4, label: '2560*1440 (4M)'},
+  {value: 3, label: '2304*1296 (3M)'},
+  {value: 2, label: '1920*1080 (1080P)'},
+  {value: 1, label: '1280*720 (720P)'}
   // { value: 0, label: '640*360' },
 ]
 
 // 子码流分辨率选项
-const subResolutionOptions = [
-  { value: 0, label: '640*360' },
-]
+const subResolutionOptions = [{value: 0, label: '640*360'}]
 
 // 编码格式选项
 const encodeTypeOptions = [
-  { value: 0, label: 'H264' },
-  // { value: 1, label: 'H265' }
+  {value: 96, label: 'H264'},
+  {value: 265, label: 'H265'}
 ]
 
 // 帧率选项
 const frameRateOptions = [
-  { value: 10, label: '10' },
-  { value: 11, label: '11' },
-  { value: 12, label: '12' },
-  { value: 13, label: '13' },
-  { value: 14, label: '14' },
-  { value: 15, label: '15' },
-  { value: 16, label: '16' },
-  { value: 17, label: '17' },
-  { value: 18, label: '18' },
-  { value: 19, label: '19' },
-  { value: 20, label: '20' },
-  { value: 21, label: '21' },
-  { value: 22, label: '22' },
-  { value: 23, label: '23' },
-  { value: 24, label: '24' },
-  { value: 25, label: '25' },
+  {value: 10, label: '10'},
+  {value: 11, label: '11'},
+  {value: 12, label: '12'},
+  {value: 13, label: '13'},
+  {value: 14, label: '14'},
+  {value: 15, label: '15'},
+  {value: 16, label: '16'},
+  {value: 17, label: '17'},
+  {value: 18, label: '18'},
+  {value: 19, label: '19'},
+  {value: 20, label: '20'},
+  {value: 21, label: '21'},
+  {value: 22, label: '22'},
+  {value: 23, label: '23'},
+  {value: 24, label: '24'},
+  {value: 25, label: '25'}
 ]
 
 // 码率控制选项
 const bitRateControlOptions = [
-  { value: 0, label: 'AVBR' },
-  { value: 1, label: 'CBR' }
+  {value: 0, label: 'AVBR'},
+  {value: 1, label: 'CBR'}
 ]
 
-
 // 恢复默认设置
 async function resetDefaults() {
   // 主码流
   mainStream.MainResolution = 5
-  mainStream.encodeType = 0
+  mainStream.MainPayloadType = 96
   mainStream.MainFps = 25
   mainStream.MainBitRate = 3072
   mainStream.MainEcdFmat = 0
   mainStream.MainFrameTime = 150
   // 子码流
   subStream.SecondResolution = 0
-  subStream.encodeType = 0
+  subStream.SecondPayloadType = 96
   subStream.SecondFps = 25
   subStream.SecondBitRate = 3072
   subStream.SecondEcdFmat = 0
@@ -227,19 +278,27 @@ async function fetchParams() {
     if (res.data) {
       const d = res.data
       // 主码流
-      if (d.MainResolution !== undefined) mainStream.MainResolution = d.MainResolution
-      // if (d.mainEncodeType !== undefined) mainStream.encodeType = d.mainEncodeType
+      if (d.MainResolution !== undefined)
+        mainStream.MainResolution = d.MainResolution
+      if (d.MainPayloadType !== undefined)
+        mainStream.MainPayloadType = d.MainPayloadType
       if (d.MainFps !== undefined) mainStream.MainFps = d.MainFps
       if (d.MainBitRate !== undefined) mainStream.MainBitRate = d.MainBitRate
       if (d.MainEcdFmat !== undefined) mainStream.MainEcdFmat = d.MainEcdFmat
-      if (d.MainFrameTime !== undefined) mainStream.MainFrameTime = d.MainFrameTime
+      if (d.MainFrameTime !== undefined)
+        mainStream.MainFrameTime = d.MainFrameTime
       // // 子码流
-      if (d.SecondResolution !== undefined) subStream.SecondResolution = d.SecondResolution
-      // if (d.subEncodeType !== undefined) subStream.encodeType = d.subEncodeType
+      if (d.SecondResolution !== undefined)
+        subStream.SecondResolution = d.SecondResolution
+      if (d.SecondPayloadType !== undefined)
+        subStream.SecondPayloadType = d.SecondPayloadType
       if (d.SecondFps !== undefined) subStream.SecondFps = d.SecondFps
-      if (d.SecondBitRate !== undefined) subStream.SecondBitRate = d.SecondBitRate
-      if (d.SecondEcdFmat !== undefined) subStream.SecondEcdFmat = d.SecondEcdFmat
-      if (d.SecondFrameTime !== undefined) subStream.SecondFrameTime = d.SecondFrameTime
+      if (d.SecondBitRate !== undefined)
+        subStream.SecondBitRate = d.SecondBitRate
+      if (d.SecondEcdFmat !== undefined)
+        subStream.SecondEcdFmat = d.SecondEcdFmat
+      if (d.SecondFrameTime !== undefined)
+        subStream.SecondFrameTime = d.SecondFrameTime
       // // 实时模式
       // if (d.realtimeMode !== undefined) realtimeMode.value = !!d.realtimeMode
     }
@@ -253,14 +312,14 @@ async function saveParams() {
   try {
     // 保存前提示用户需要重启才能生效
     await ElMessageBox.confirm(
-      'Changing the video settings will restart the device.',
-      'Note',
-      {
-        confirmButtonText: 'OK',
-        cancelButtonText: 'Cancel',
-        type: 'warning',
-        confirmButtonClass: 'el-button--danger'
-      }
+        'Changing the video settings will restart the device.',
+        'Note',
+        {
+          confirmButtonText: 'OK',
+          cancelButtonText: 'Cancel',
+          type: 'warning',
+          confirmButtonClass: 'el-button--danger'
+        }
     )
   } catch {
     // 用户取消,不执行保存
@@ -270,24 +329,29 @@ async function saveParams() {
   try {
     const data = {
       MainResolution: mainStream.MainResolution,
+      MainPayloadType: mainStream.MainPayloadType,
       MainFps: mainStream.MainFps,
       MainBitRate: mainStream.MainBitRate,
       MainEcdFmat: mainStream.MainEcdFmat,
       MainFrameTime: mainStream.MainFrameTime,
       SecondResolution: subStream.SecondResolution,
+      SecondPayloadType: subStream.SecondPayloadType,
       SecondFps: subStream.SecondFps,
       SecondBitRate: subStream.SecondBitRate,
       SecondEcdFmat: subStream.SecondEcdFmat,
-      SecondFrameTime: subStream.SecondFrameTime,
+      SecondFrameTime: subStream.SecondFrameTime
     }
+    loading.value = true
     const res = await putVideoEncodePara(data)
     if (res.data === 'ok\n') {
-      await cameraReset({ reboot: 1 })   // 保存成功后执行重启
+      loading.value = false
       ElMessage.success('Operation successful. Please wait a moment...')
+      await cameraReset({reboot: 1}) // 保存成功后执行重启
       useUserStore().logout()
       router.push('/login')
     }
   } catch (e) {
+    loading.value = false
     ElMessage.warning('Save Failed')
   }
 }
@@ -381,17 +445,19 @@ onMounted(() => {
   padding-top: 20px;
 
   .btn-reset {
-    border-color: #409EFF;
-    color: #409EFF;
+    border-color: #409eff;
+    color: #409eff;
 
     &:hover {
-      background-color: #409EFF;
+      background-color: #409eff;
       color: #fff;
     }
   }
 
-  .btn-save {
-    min-width: 100px;
+  :deep(.el-button.is-loading) {
+    .el-loading-mask {
+      border-radius: 20px;
+    }
   }
 }
 </style>

+ 2 - 0
src/views/settings/imageDisplay/components/image/index.vue

@@ -214,6 +214,8 @@ async function resetDefaults() {
   await saveParams()
 }
 
+defineExpose({ myVideoRef })
+
 onMounted(() => {
   fetchImagePara()
 })

+ 57 - 27
src/views/settings/imageDisplay/components/osd/index.vue

@@ -3,24 +3,24 @@
     <!-- 左侧:视频预览 + OSD拖拽 -->
     <div class="osd-settings__left">
       <div class="osd-settings__video" ref="videoWrapRef">
-        <MyVideo ref="myVideoRef" :drag-flag="false" />
+        <MyVideo ref="myVideoRef" :drag-flag="false"/>
         <!-- OSD叠加层:用于拖拽定位 -->
         <div class="osd-overlay" ref="overlayRef">
           <!-- 时间OSD -->
           <div
-            v-if="form.EnTime"
-            class="osd-tag osd-tag--time"
-            :style="osdTimeStyle"
-            @mousedown.prevent="startDragOsd($event, 'time')"
+              v-if="form.EnTime"
+              class="osd-tag osd-tag--time"
+              :style="osdTimeStyle"
+              @mousedown.prevent="startDragOsd($event, 'time')"
           >
             {{ currentTime }}
           </div>
           <!-- 名称OSD -->
           <div
-            v-if="form.EnName"
-            class="osd-tag osd-tag--name"
-            :style="osdNameStyle"
-            @mousedown.prevent="startDragOsd($event, 'name')"
+              v-if="form.EnName"
+              class="osd-tag osd-tag--name"
+              :style="osdNameStyle"
+              @mousedown.prevent="startDragOsd($event, 'name')"
           >
             {{ form.OsdName }}
           </div>
@@ -46,7 +46,10 @@
             <div class="form-item__control name-control">
               <span class="name-text">{{ form.OsdName }}</span>
               <el-button link type="primary" @click="showEditName = true">
-                <el-icon><Edit /></el-icon> Modify
+                <el-icon>
+                  <Edit/>
+                </el-icon>
+                Modify
               </el-button>
             </div>
           </div>
@@ -112,7 +115,7 @@
 
     <!-- 修改名称弹窗 -->
     <el-dialog v-model="showEditName" title="Modify Name" width="360px" :close-on-click-modal="false">
-      <el-input v-model="editNameValue" maxlength="32" placeholder="Please enter a name" />
+      <el-input v-model="editNameValue" maxlength="32" placeholder="Please enter a name"/>
       <template #footer>
         <el-button @click="showEditName = false">Cancel</el-button>
         <el-button type="primary" @click="confirmEditName">Confirm</el-button>
@@ -122,13 +125,14 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
-import { ElMessage } from 'element-plus'
-import { Edit } from '@element-plus/icons-vue'
+import {ref, reactive, computed, onMounted, onUnmounted} from 'vue'
+import {ElMessage} from 'element-plus'
+import {Edit} from '@element-plus/icons-vue'
 import MyVideo from '@/components/myVideo.vue'
-import { getOsdPara, putOsdPara } from '@/api/setting'
+import {getOsdPara, putOsdPara, GetTimePara} from '@/api/setting'
 
 const myVideoRef = ref<InstanceType<typeof MyVideo> | null>(null)
+const timeFormat = ref(0)
 
 // 表单数据
 const form = reactive({
@@ -167,7 +171,7 @@ const currentTime2 = ref('')
 
 let timeTimer: ReturnType<typeof setInterval> | null = null
 
-function updateTime() {
+function updateTime(timeFormat: number) {
   const now = new Date()
   const y = now.getFullYear()
   const M = String(now.getMonth() + 1).padStart(2, '0')
@@ -177,8 +181,16 @@ function updateTime() {
   const h = String(now.getHours()).padStart(2, '0')
   const m = String(now.getMinutes()).padStart(2, '0')
   const s = String(now.getSeconds()).padStart(2, '0')
-  currentTime.value = `${M}-${d}-${y}\u00A0\u00A0\u00A0${h}:${m}:${s}\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${w}`
-  currentTime2.value = `${M}-${d}-${y} ${h}:${m}:${s} ${w}`
+  if (timeFormat === 0) {
+    currentTime.value = `${y}-${M}-${d}\u00A0\u00A0\u00A0${h}:${m}:${s}\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${w}`
+    currentTime2.value = `${y}-${M}-${d} ${h}:${m}:${s} ${w}`
+  } else if (timeFormat === 1) {
+    currentTime.value = `${M}-${d}-${y}\u00A0\u00A0\u00A0${h}:${m}:${s}\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${w}`
+    currentTime2.value = `${M}-${d}-${y} ${h}:${m}:${s} ${w}`
+  } else {
+    currentTime.value = `${d}-${M}-${y}\u00A0\u00A0\u00A0${h}:${m}:${s}\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${w}`
+    currentTime2.value = `${d}-${M}-${y} ${h}:${m}:${s} ${w}`
+  }
 }
 
 // OSD拖拽相关
@@ -282,8 +294,16 @@ async function saveOsd() {
   }
 }
 
-onMounted(() => {
-  updateTime()
+async function GetTime() {
+  const res = await GetTimePara()
+  if (res.data) {
+    timeFormat.value = res.data.timeFormat
+  }
+  updateTime(timeFormat.value)
+}
+
+onMounted( () => {
+  GetTime()
 //   timeTimer = setInterval(updateTime, 1000)
   fetchOsd()
 })
@@ -317,26 +337,36 @@ onUnmounted(() => {
 
     :deep(.preview-container) {
       position: absolute;
-      top: 0; left: 0;
-      width: 100%; height: 100%;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
     }
+
     :deep(.video_container) {
       position: absolute;
-      top: 0; left: 0;
-      width: 100%; height: 100%;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
     }
+
     :deep(#player) {
-      width: 100%; height: 100%;
+      width: 100%;
+      height: 100%;
       object-fit: contain;
     }
+
     :deep(.video-control) {
       display: none;
     }
 
     .osd-overlay {
       position: absolute;
-      top: 0; left: 0;
-      width: 100%; height: 100%;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
       z-index: 10;
       pointer-events: none;
     }

+ 88 - 4
src/views/settings/imageDisplay/index.vue

@@ -1,25 +1,42 @@
 <template>
   <div class="settings-container">
+    <div v-if="showChannelSelector" class="channel-selector-bar">
+      <span class="channel-label">Channel</span>
+      <el-select
+        v-model="selectedChn"
+        size="small"
+        @change="handleChnChange"
+      >
+        <el-option
+          v-for="ch in channelOptions"
+          :key="ch.value"
+          :label="ch.label"
+          :value="ch.value"
+        />
+      </el-select>
+    </div>
     <el-tabs v-model="activeName" class="tabs">
       <el-tab-pane v-if="isTabVisible('Image_Display', 'Image')" label="Image" name="first">
-        <Image v-if="activeName === 'first'" />
+        <Image v-if="activeName === 'first'" ref="imageRef" />
       </el-tab-pane>
       <el-tab-pane v-if="isTabVisible('Image_Display', 'OSD_Settings')" label="OSD Settings" name="second">
-        <OSD v-if="activeName === 'second'" />
+        <OSD v-if="activeName === 'second'" ref="osdRef" />
       </el-tab-pane>
       <el-tab-pane v-if="isTabVisible('Image_Display', 'Privacy_Masking')" label="Privacy Masking " name="third">
-        <PrivacyMasking v-if="activeName === 'third'" />
+        <PrivacyMasking v-if="activeName === 'third'" ref="privacyMaskingRef" />
       </el-tab-pane>
     </el-tabs>
   </div>
 </template>
 
 <script setup lang="ts">
-import {ref} from "vue";
+import { ref, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
 import Image from './components/image/index.vue'
 import OSD from './components/osd/index.vue'
 import PrivacyMasking from './components/privacyMasking/index.vue'
 import { usePermissionStore } from '@/stores/modules/permission'
+import { getDeviceNum, putDeviceNum } from '@/api/ptz'
 
 const { isTabVisible } = usePermissionStore()
 
@@ -31,6 +48,55 @@ const tabOrder = [
 const firstVisible = tabOrder.find(t => isTabVisible('Image_Display', t.key))
 const activeName = ref(firstVisible?.name || 'first')
 
+const showChannelSelector = ref(false)
+const selectedChn = ref(0)
+const channelOptions = [
+  { label: 'Fixed Camera', value: 0 },
+  { label: 'PTZ Camera', value: 1 }
+]
+
+const imageRef = ref()
+const osdRef = ref()
+const privacyMaskingRef = ref()
+
+const fetchDeviceInfo = async () => {
+  try {
+    const res = await getDeviceNum()
+     // const res = {"data":{"LensNumber":1,"ChnNumber":0}}
+    const lensNumber = res?.data?.LensNumber ?? 1
+    const chnNumber = res?.data?.ChnNumber ?? 0
+    showChannelSelector.value = lensNumber >= 2
+    selectedChn.value = chnNumber
+  } catch (error) {
+    console.error('Failed to get device info:', error)
+  }
+}
+
+const refreshCurrentVideo = () => {
+  const refs: any[] = [imageRef, osdRef, privacyMaskingRef]
+  for (const compRef of refs) {
+    if (compRef.value?.myVideoRef?.refreshWebSocket) {
+      compRef.value.myVideoRef.refreshWebSocket()
+      return
+    }
+  }
+}
+
+const handleChnChange = async (val: number) => {
+  try {
+    await putDeviceNum({ ChnNumber: val })
+    await fetchDeviceInfo()
+    refreshCurrentVideo()
+  } catch (error) {
+    console.error('Failed to switch channel:', error)
+    ElMessage.error('Failed to switch channel, please try again')
+    await fetchDeviceInfo()
+  }
+}
+
+onMounted(() => {
+  fetchDeviceInfo()
+})
 </script>
 
 
@@ -40,4 +106,22 @@ const activeName = ref(firstVisible?.name || 'first')
   flex-direction: column;
   padding: 20px;
 }
+
+.channel-selector-bar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  width: 200px;
+  margin-bottom: 12px;
+  padding: 8px 12px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .channel-label {
+    font-size: 13px;
+    font-weight: 500;
+    color: #606266;
+  }
+}
 </style>