liujintao 1 mesiac pred
rodič
commit
8bb0ef77ca
27 zmenil súbory, kde vykonal 764 pridanie a 432 odobranie
  1. 2 1
      components.d.ts
  2. 10 0
      src/api/remote.ts
  3. 183 136
      src/api/setting.ts
  4. 8 0
      src/api/types/setting.ts
  5. 4 0
      src/utils/request.ts
  6. 1 1
      src/views/login/index.vue
  7. 31 12
      src/views/remoteViewing/index.vue
  8. 34 4
      src/views/settings/alarmSettings/components/humanDetection/index.vue
  9. 37 4
      src/views/settings/alarmSettings/components/intrusionDetection/index.vue
  10. 34 4
      src/views/settings/alarmSettings/components/lineCrossingDetection/index.vue
  11. 34 5
      src/views/settings/alarmSettings/components/motionDetection/index.vue
  12. 13 13
      src/views/settings/audioVideo/components/nightVisionIlluminator/index.vue
  13. 1 1
      src/views/settings/audioVideo/components/video/index.vue
  14. 9 5
      src/views/settings/imageDisplay/components/image/index.vue
  15. 8 4
      src/views/settings/imageDisplay/components/osd/index.vue
  16. 28 5
      src/views/settings/imageDisplay/components/privacyMasking/index.vue
  17. 3 3
      src/views/settings/imageDisplay/index.vue
  18. 2 3
      src/views/settings/netSettings/components/networkDiagnostics/index.vue
  19. 97 69
      src/views/settings/systemSettings/components/cameraInfo/index.vue
  20. 29 29
      src/views/settings/systemSettings/components/passwordManagement/index.vue
  21. 30 29
      src/views/settings/systemSettings/components/systemMaintenance/index.vue
  22. 68 17
      src/views/settings/systemSettings/components/systemUpgrade/index.vue
  23. 49 45
      src/views/settings/systemSettings/components/timeSettings/index.vue
  24. 4 4
      src/views/settings/systemSettings/index.vue
  25. 22 23
      src/views/standardProtocol/components/onvif/index.vue
  26. 21 13
      src/views/standardProtocol/components/rtsp/index.vue
  27. 2 2
      src/views/standardProtocol/index.vue

+ 2 - 1
components.d.ts

@@ -8,10 +8,11 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     DefenseTimeSchedule: typeof import('./src/components/DefenseTimeSchedule/index.vue')['default']
-    ElAlert: typeof import('element-plus/es')['ElAlert']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
+    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElForm: typeof import('element-plus/es')['ElForm']

+ 10 - 0
src/api/remote.ts

@@ -0,0 +1,10 @@
+import { request } from '@/utils/request'
+
+
+// 远程查看
+export function getRemoteView() {
+    return request<any>({
+        url: `/API/V1.0/Device/RemoteView`,
+        method: 'get'
+    })
+}

+ 183 - 136
src/api/setting.ts

@@ -1,218 +1,265 @@
-import { request } from '@/utils/request'
+import {request} from '@/utils/request'
 import type * as Setting from './types/setting'
-import type { TimeParaData } from './types/setting'
+import type {TimeParaData} from './types/setting'
 
 export function getUserSettingApi(NIC: number) {
-  return request<Setting.GetSettingResponseData>({
-    url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
-    method: 'get'
-  })
+    return request({
+        url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
+        method: 'get'
+    })
 }
 
 export function putUserSettingApi(
-  NIC: number,
-  data: Setting.UpdateSettingRequestData
+    NIC: number,
+    data: Setting.UpdateSettingRequestData
 ) {
-  return request({
-    url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
-    method: 'put',
-    data
-  })
+    return request({
+        url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
+        method: 'put',
+        data
+    })
 }
 
 //查询/设置  系统时间
 export function GetTimePara() {
-  return request<{ data: Setting.TimeParaResponse }>({
-    url: `API/V1.0/System/TimePara`,
-    method: 'get'
-  })
+    return request<{ data: Setting.TimeParaResponse }>({
+        url: `API/V1.0/System/TimePara`,
+        method: 'get'
+    })
 }
 
 
 export function PutTimePara(data: TimeParaData) {
-  return request<{ data: string }>({
-    url: `API/V1.0/System/TimePara`,
-    method: 'put',
-    data
-  })
+    return request<{ data: string }>({
+        url: `API/V1.0/System/TimePara`,
+        method: 'put',
+        data
+    })
 }
 
 
 export function cameraReset(data: any) {
-  return request({
-    url: `/API/V1.0/Device/ResetReboot`,
-    method: 'PUT',
-    data
-  })
+    return request({
+        url: `/API/V1.0/Device/ResetReboot`,
+        method: 'PUT',
+        data
+    })
 }
 
 export function cameraResetGet() {
-  return request({
-    url: `/API/V1.0/Device/ResetReboot`,
-    method: 'get',
-  })
+    return request({
+        url: `/API/V1.0/Device/ResetReboot`,
+        method: 'get',
+    })
 }
 
 export function getCameraVolume() {
-  return request({
-    url: `/API/V1.0/Audio/AudioPara`,
-    method: 'get'
-  })
+    return request({
+        url: `/API/V1.0/Audio/AudioPara`,
+        method: 'get'
+    })
 }
 
 
 export function cameraVolume(data: any) {
-  return request({
-    url: `/API/V1.0/Audio/AudioPara`,
-    method: 'put',
-    data
-  })
-}
-
-
-export function getCameraAlarm() {
-  return request({
-    url: `/API/V1.0/Alarm/AlarmPara`,
-    method: 'get'
-  })
+    return request({
+        url: `/API/V1.0/Audio/AudioPara`,
+        method: 'put',
+        data
+    })
 }
 
 
 export function cameraAlarm(data: any) {
-  return request({
-    url: `/API/V1.0/Alarm/AlarmPara`,
-    method: 'put',
-    data
-  })
-}
-
-
-export function getCameraNightMode() {
-  return request({
-    url: `/API/V1.0/Video/NightPara`,
-    method: 'get'
-  })
-}
-
-export function cameraNightMode(data: any) {
-  return request({
-    url: `/API/V1.0/Video/NightPara`,
-    method: 'put',
-    data
-  })
+    return request({
+        url: `/API/V1.0/Alarm/AlarmPara`,
+        method: 'put',
+        data
+    })
 }
 
 
 export function getCameraDeviceInfo() {
-  return request({
-    url: `/API/V1.0/DeviceInfo`,
-    method: 'get'
-  })
+    return request<{ data: Setting.CameraDeviceInfo }>({
+        url: `/API/V1.0/DeviceInfo`,
+        method: 'get'
+    })
 }
 
-// export function cameraDeviceInfo(data: any) {
-//   return request({
-//     url: `/API/V1.0/DeviceInfo`,
-//     method: 'put',
-//     data
-//   })
-// }
-
 
 export function cameraResetPassword(data: any) {
-  return request({
-    url: `/API/V1.0/Device/ResetPassword`,
-    method: 'put',
-    data
-  })
+    return request({
+        url: `/API/V1.0/Device/ResetPassword`,
+        method: 'put',
+        data
+    })
 }
 
 // 图像参数
 export function getImagePara() {
-  return request<any>({
-    url: `/API/V1.0/Video/ImagePara`,
-    method: 'get'
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/Image`,
+        method: 'get'
+    })
 }
 
 export function putImagePara(data: any) {
-  return request<any>({
-    url: `/API/V1.0/Video/ImagePara`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/Image`,
+        method: 'put',
+        data
+    })
 }
 
 // 隐私遮挡
 export function getPrivacyMask() {
-  return request<any>({
-    url: `/API/V1.0/Video/PrivacyMask`,
-    method: 'get'
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/PrivacyMask`,
+        method: 'get'
+    })
 }
 
 export function putPrivacyMask(data: any) {
-  return request<any>({
-    url: `/API/V1.0/Video/PrivacyMask`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/PrivacyMask`,
+        method: 'put',
+        data
+    })
 }
 
 // OSD设置
 export function getOsdPara() {
-  return request<any>({
-    url: `/API/V1.0/Video/OsdPara`,
-    method: 'get'
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/OsdPosiTion`,
+        method: 'get'
+    })
 }
 
 export function putOsdPara(data: any) {
-  return request<any>({
-    url: `/API/V1.0/Video/OsdPara`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/OsdPosiTion`,
+        method: 'put',
+        data
+    })
 }
 
 // 视频编码参数
 export function getVideoEncodePara() {
-  return request<any>({
-    url: `/API/V1.0/Video/EncodePara`,
-    method: 'get'
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/BitStream`,
+        method: 'get'
+    })
 }
 
 export function putVideoEncodePara(data: any) {
-  return request<any>({
-    url: `/API/V1.0/Video/EncodePara`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/BitStream`,
+        method: 'put',
+        data
+    })
 }
 
 // 夜视补光参数
 export function getNightVisionIlluminator() {
-  return request<any>({
-    url: `/API/V1.0/Video/NightVisionPara`,
-    method: 'get'
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/NightPara`,
+        method: 'get'
+    })
 }
 
 export function putNightVisionIlluminator(data: any) {
-  return request<any>({
-    url: `/API/V1.0/Video/NightVisionPara`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Video/NightPara`,
+        method: 'put',
+        data
+    })
 }
 
 // 网络诊断(Ping)
 export function networkDiagnostics(data: { DomainName: string }) {
-  return request<any>({
-    url: `/API/V1.0/Network/NetworkDiag`,
-    method: 'put',
-    data
-  })
+    return request<any>({
+        url: `/API/V1.0/Network/NetworkDiag`,
+        method: 'put',
+        data
+    })
+}
+
+// 系统升级
+export function systemUpgrade(data:any) {
+    return request<any>({
+        url: `/API/V1.0/Device/UpDate`,
+        method: 'post',
+        data: data,
+        timeout: 300000 // 5分钟超时,固件上传需要更长时间
+    })
+}
+
+
+// 移动检测
+export function getMotionDetection() {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/MotionDet`,
+        method: 'get'
+    })
+}
+
+export function putMotionDetection(data:any) {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/MotionDet`,
+        method: 'put',
+        data
+    })
 }
 
+// 人形侦测
+export function getHumanDetection() {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/HumanDet`,
+        method: 'get'
+    })
+}
+
+export function putHumanDetection(data:any) {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/HumanDet`,
+        method: 'put',
+        data
+    })
+}
+
+//区域侦测
+export function getIntrusionDetection() {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/RegionDet`,
+        method: 'get'
+    })
+}
+
+
+export function putIntrusionDetection(data:any) {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/RegionDet`,
+        method: 'put',
+        data
+    })
+}
+
+//越界侦测
+export function getLineCrossingDetection() {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/CrossLineDet`,
+        method: 'get'
+    })
+}
+
+export function putLineCrossingDetection(data:any) {
+    return request<any>({
+        url: `/API/V1.0/Aidetect/CrossLineDet`,
+        method: 'put',
+        data
+    })
+}
+
+
+
+

+ 8 - 0
src/api/types/setting.ts

@@ -1,3 +1,11 @@
+/** 摄像头设备信息 */
+export interface CameraDeviceInfo {
+  DeviceName: string
+  SoftwareVer: string
+  HardwareVer: string
+  MacAddr: string
+}
+
 export interface UpdateSettingRequestData {
   NIC: number
   enableDHCP?: number

+ 4 - 0
src/utils/request.ts

@@ -34,6 +34,10 @@ function createRequest(service: AxiosInstance) {
     }
     // 将默认配置 defaultConfig 和传入的自定义配置 config 进行合并成为 mergeConfig
     const mergeConfig = merge(defaultConfig, config)
+    // 当 data 为 FormData 时,删除 Content-Type,让浏览器自动设置(含 boundary)
+    if (mergeConfig.data instanceof FormData && mergeConfig.headers) {
+      (mergeConfig.headers as Record<string, any>)['Content-Type'] = undefined
+    }
     return service(mergeConfig)
   }
 }

+ 1 - 1
src/views/login/index.vue

@@ -222,7 +222,7 @@ const handleLogin = () => {
   }
 
   .login-card {
-    width: 480px;
+    width: 500px;
     border-radius: 12px;
     box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
     background-color: #fff;

+ 31 - 12
src/views/remoteViewing/index.vue

@@ -12,7 +12,7 @@
         <div class="status-content">
           <label>Status</label>
           <span class="status-badge" :class="{ 'online': isOnline, 'offline': !isOnline }">
-            {{ deviceStatus }}
+            {{ OnlineStatus ? 'Online' : 'Offline' }}
           </span>
         </div>
         <button
@@ -29,7 +29,7 @@
       <div class="info-item">
         <label>Device Serial Number</label>
         <div class="code-display">
-          <span>{{ deviceCode }}</span>
+          <span>{{ DeviceSN }}</span>
           <button class="copy-btn" @click="copyDeviceSN">Copy</button>
         </div>
       </div>
@@ -38,7 +38,7 @@
       <div class="info-item">
         <label>QR Code</label>
         <div class="qrcode-box">
-          <QrcodeVue :value="qrCodeValue" :size="150" />
+          <QrcodeVue :value="DeviceSN" :size="150" />
         </div>
       </div>
 
@@ -65,21 +65,40 @@
 import { ref, onMounted } from 'vue'
 import { Refresh } from '@element-plus/icons-vue'
 import QrcodeVue from 'qrcode.vue'
+import {getRemoteView} from '@/api/remote'
+import {ElMessage} from 'element-plus'
+
+const OnlineStatus = ref(false) // true:在线 false:不在线
+const isOnline = ref(false)
+const DeviceSN = ref('')
 
-const deviceStatus = ref('Online')
-const isOnline = ref(true)
 const isRefreshing = ref(false)
 const showNotification = ref(false)
 const notificationMessage = ref('')
-const deviceCode = ref('0314153000122700030900')
-const qrCodeValue = ref('0314153000122700030900')
+
+
+function getRemoteViewInfo() {
+  getRemoteView().then(resp => {
+    if (resp.data) {
+      OnlineStatus.value = resp.data.OnlineStatus
+      isOnline.value = resp.data.OnlineStatus
+      DeviceSN.value = resp.data.DeviceSN
+    } else {
+      ElMessage.error('Failed to get remote view info')
+    }
+  })
+}
 
 const refreshStatus = async () => {
   isRefreshing.value = true
   try {
-    await new Promise(resolve => setTimeout(resolve, 1500))
-    isOnline.value = true
-    deviceStatus.value = 'Online'
+    // 使用 Promise 包装 setTimeout 以确保正确的异步处理
+    await new Promise(resolve => {
+      setTimeout(async () => {
+        await getRemoteViewInfo()
+        resolve()
+      }, 1000)
+    })
     notificationMessage.value = 'Device status refreshed'
     showNotification.value = true
     setTimeout(() => {
@@ -98,7 +117,7 @@ const refreshStatus = async () => {
 
 const copyDeviceSN = async () => {
   try {
-    await navigator.clipboard.writeText(deviceCode.value)
+    await navigator.clipboard.writeText(DeviceSN.value)
     notificationMessage.value = 'Copied to clipboard'
     showNotification.value = true
     setTimeout(() => {
@@ -114,7 +133,7 @@ const copyDeviceSN = async () => {
 }
 
 onMounted(() => {
-  // API call location
+  getRemoteViewInfo()
 })
 </script>
 

+ 34 - 4
src/views/settings/alarmSettings/components/humanDetection/index.vue

@@ -23,7 +23,7 @@
       <el-checkbox v-model="form.AudioAlarm">Sound Alarm</el-checkbox>
     </div>
 
-    <el-button type="primary" round @click="handleSave">Save</el-button>
+    <el-button type="primary" :loading="loading" round @click="handleSave">Save</el-button>
   </div>
 </template>
 
@@ -32,6 +32,9 @@ import {reactive, computed} from 'vue'
 import {ElMessage} from 'element-plus'
 import DefenseTimeSchedule from '@/components/DefenseTimeSchedule/index.vue'
 import type {DailyArmMasks} from '@/components/DefenseTimeSchedule/index.vue'
+import {getHumanDetection, getMotionDetection, putHumanDetection} from '@/api/setting'
+
+const loading = ref(false)
 
 const form = reactive<{
   Enable: boolean
@@ -48,6 +51,20 @@ const form = reactive<{
   AudioAlarm: false
 })
 
+function getHumanDetectionInfo() {
+  getHumanDetection().then(resp => {
+    form.Enable = resp.data.Enable
+    form.Sensitivity = resp.data.Sensitivity
+    form.schedule = resp.data.DailyArmMasks
+    form.AudioAlarm = resp.data.AudioAlarm
+  })
+}
+
+
+onMounted(() => {
+  getHumanDetectionInfo()
+})
+
 // 灵敏度文字映射
 const sensitivityLabel = computed(() => {
   const val = form.Sensitivity
@@ -57,15 +74,28 @@ const sensitivityLabel = computed(() => {
 })
 
 function handleSave() {
-  // TODO: 调用接口保存配置
   const payload = {
     Enable: form.Enable,
     Sensitivity: form.Sensitivity,
     DailyArmMasks: {...form.schedule},
     AudioAlarm: form.AudioAlarm
   }
-  console.log('Save motion detection config:', payload)
-  ElMessage.success('Saved successfully')
+  try {
+    loading.value = true
+    putHumanDetection(payload).then(resp => {
+      if (resp.data === 'ok\n') {
+        ElMessage.success('Saved successfully')
+        getHumanDetectionInfo()
+      } else {
+        ElMessage.error("Save failed")
+      }
+    })
+  } catch (e) {
+    console.error(e)
+    ElMessage.error("Save failed")
+  } finally {
+    loading.value = false
+  }
 }
 </script>
 

+ 37 - 4
src/views/settings/alarmSettings/components/intrusionDetection/index.vue

@@ -38,7 +38,7 @@
       <el-checkbox v-model="form.AudioAlarmLeave">Sound Alarm on Leave</el-checkbox>
     </div>
 
-    <el-button type="primary" round @click="handleSave">Save</el-button>
+    <el-button type="primary" :loading="loading" round @click="handleSave">Save</el-button>
   </div>
 </template>
 
@@ -47,6 +47,9 @@ import {reactive, computed} from 'vue'
 import {ElMessage} from 'element-plus'
 import DefenseTimeSchedule from '@/components/DefenseTimeSchedule/index.vue'
 import type {DailyArmMasks} from '@/components/DefenseTimeSchedule/index.vue'
+import {getIntrusionDetection, getMotionDetection, putIntrusionDetection} from '@/api/setting'
+
+const loading = ref(false)
 
 const form = reactive<{
   Enable: boolean
@@ -83,8 +86,24 @@ const sensitivityLabel = computed(() => {
   return 'High'
 })
 
+function getIntrusionDetectionInfo() {
+  getIntrusionDetection().then(resp => {
+    form.Enable = resp.data.Enable
+    form.Sensitivity = resp.data.Sensitivity
+    form.TriggerMode = resp.data.TriggerMode
+    form.LightFlashEn = resp.data.LightFlashEn
+    form.schedule = resp.data.DailyArmMasks
+    form.AudioAlarmEnter = resp.data.AudioAlarmEnter
+    form.AudioAlarmLeave = resp.data.AudioAlarmLeave
+  })
+}
+
+
+onMounted(() => {
+  getIntrusionDetectionInfo()
+})
+
 function handleSave() {
-  // TODO: 调用接口保存配置
   const payload = {
     Enable: form.Enable,
     Sensitivity: form.Sensitivity,
@@ -94,8 +113,22 @@ function handleSave() {
     LightFlashEn: form.LightFlashEn,
     TriggerMode: form.TriggerMode,
   }
-  console.log('Save motion detection config:', payload)
-  ElMessage.success('Saved successfully')
+  try {
+    loading.value = true
+    putIntrusionDetection(payload).then(resp => {
+      if (resp.data === 'ok\n') {
+        ElMessage.success('Saved successfully')
+        getIntrusionDetectionInfo()
+      } else {
+        ElMessage.error("Save failed")
+      }
+    })
+  } catch (e) {
+    console.error(e)
+    ElMessage.error("Save failed")
+  } finally {
+    loading.value = false
+  }
 }
 </script>
 

+ 34 - 4
src/views/settings/alarmSettings/components/lineCrossingDetection/index.vue

@@ -28,7 +28,7 @@
       <el-checkbox v-model="form.AudioAlarmBA">Sound Alarm on B→A</el-checkbox>
     </div>
 
-    <el-button type="primary" round @click="handleSave">Save</el-button>
+    <el-button type="primary" :loading="loading" round @click="handleSave">Save</el-button>
   </div>
 </template>
 
@@ -37,6 +37,9 @@ import {reactive, computed} from 'vue'
 import {ElMessage} from 'element-plus'
 import DefenseTimeSchedule from '@/components/DefenseTimeSchedule/index.vue'
 import type {DailyArmMasks} from '@/components/DefenseTimeSchedule/index.vue'
+import {getLineCrossingDetection, putLineCrossingDetection} from '@/api/setting'
+
+const loading = ref(false)
 
 const form = reactive<{
   Enable: boolean
@@ -57,6 +60,22 @@ const form = reactive<{
   AudioAlarmBA: false,
 })
 
+function getLineCrossingDetectionInfo() {
+  getLineCrossingDetection().then(resp => {
+    form.Enable = resp.data.Enable
+    form.Sensitivity = resp.data.Sensitivity
+    form.LightFlashEn = resp.data.LightFlashEn
+    form.schedule = resp.data.DailyArmMasks
+    form.AudioAlarmAB = resp.data.AudioAlarmAB
+    form.AudioAlarmBA = resp.data.AudioAlarmBA
+  })
+}
+
+
+onMounted(() => {
+  getLineCrossingDetectionInfo()
+})
+
 // 灵敏度文字映射
 const sensitivityLabel = computed(() => {
   const val = form.Sensitivity
@@ -66,7 +85,6 @@ const sensitivityLabel = computed(() => {
 })
 
 function handleSave() {
-  // TODO: 调用接口保存配置
   const payload = {
     Enable: form.Enable,
     Sensitivity: form.Sensitivity,
@@ -75,8 +93,20 @@ function handleSave() {
     AudioAlarmBA: form.AudioAlarmBA,
     LightFlashEn: form.LightFlashEn,
   }
-  console.log('Save motion detection config:', payload)
-  ElMessage.success('Saved successfully')
+  loading.value = true
+  putLineCrossingDetection(payload).then(resp => {
+    if (resp.data === 'ok\n') {
+      ElMessage.success('Saved successfully')
+      getLineCrossingDetectionInfo()
+    } else {
+      ElMessage.error("Save failed")
+    }
+  }).catch(e => {
+    console.error(e)
+    ElMessage.error("Save failed")
+  }).finally(() => {
+    loading.value = false
+  })
 }
 </script>
 

+ 34 - 5
src/views/settings/alarmSettings/components/motionDetection/index.vue

@@ -23,7 +23,7 @@
       <el-checkbox v-model="form.AudioAlarm">Sound Alarm</el-checkbox>
     </div>
 
-    <el-button type="primary" round @click="handleSave">Save</el-button>
+    <el-button type="primary" :loading="loading" round @click="handleSave">Save</el-button>
   </div>
 </template>
 
@@ -32,6 +32,9 @@ import {reactive, computed} from 'vue'
 import {ElMessage} from 'element-plus'
 import DefenseTimeSchedule from '@/components/DefenseTimeSchedule/index.vue'
 import type {DailyArmMasks} from '@/components/DefenseTimeSchedule/index.vue'
+import {getMotionDetection, putMotionDetection} from '@/api/setting'
+
+const loading = ref(false)
 
 const form = reactive<{
   Enable: boolean
@@ -48,6 +51,20 @@ const form = reactive<{
   AudioAlarm: false
 })
 
+function getMotionDetectionInfo() {
+  getMotionDetection().then(resp => {
+    form.Enable = resp.data.Enable
+    form.Sensitivity = resp.data.Sensitivity
+    form.schedule = resp.data.DailyArmMasks
+    form.AudioAlarm = resp.data.AudioAlarm
+  })
+}
+
+
+onMounted(() => {
+  getMotionDetectionInfo()
+})
+
 // 灵敏度文字映射
 const sensitivityLabel = computed(() => {
   const val = form.Sensitivity
@@ -56,16 +73,28 @@ const sensitivityLabel = computed(() => {
   return 'High'
 })
 
-function handleSave() {
-  // TODO: 调用接口保存配置
+async function handleSave() {
   const payload = {
     Enable: form.Enable,
     Sensitivity: form.Sensitivity,
     DailyArmMasks: {...form.schedule},
     AudioAlarm: form.AudioAlarm
   }
-  console.log('Save motion detection config:', payload)
-  ElMessage.success('Saved successfully')
+  try {
+    loading.value = true
+    const resp = await putMotionDetection(payload)
+    if (resp.data === 'ok\n') {
+      ElMessage.success('Saved successfully')
+      getMotionDetectionInfo()
+    } else {
+      ElMessage.error("Save failed")
+    }
+  } catch (e) {
+    console.error(e)
+    ElMessage.error("Save failed")
+  } finally {
+    loading.value = false
+  }
 }
 </script>
 

+ 13 - 13
src/views/settings/audioVideo/components/nightVisionIlluminator/index.vue

@@ -50,12 +50,12 @@
               <div class="form-item__control slider-control">
                 <!-- <span class="slider-min">0</span> -->
                 <el-slider
-                  v-model="form.FlOnSens"
+                  v-model="form.FIOnSens"
                   :min="0"
                   :max="100"
                   :show-tooltip="false"
                 />
-                <span class="slider-value">{{ form.FlOnSens }}</span>
+                <span class="slider-value">{{ form.FIOnSens }}</span>
                 <!-- <span class="slider-max">100</span> -->
               </div>
             </div>
@@ -66,12 +66,12 @@
               <div class="form-item__control slider-control">
                 <!-- <span class="slider-min">0</span> -->
                 <el-slider
-                  v-model="form.FlOffSens "
+                  v-model="form.FIOffSens "
                   :min="0"
                   :max="100"
                   :show-tooltip="false"
                 />
-                <span class="slider-value">{{ form.FlOffSens  }}</span>
+                <span class="slider-value">{{ form.FIOffSens  }}</span>
                 <!-- <span class="slider-max">100</span> -->
               </div>
             </div>
@@ -140,8 +140,8 @@ const form = reactive({
   NightMode: 0,        // 补光方式: 0-全彩夜视 1-黑白夜视 2-智能夜视
   AutoLightEn: false,   // 自动调节亮度
   PwmLight: 50,     // 灯光亮度
-  FlOnSens: 50,  // 开灯灵敏度
-  FlOffSens : 50, // 关灯灵敏度
+  FIOnSens: 50,  // 开灯灵敏度
+  FIOffSens : 50, // 关灯灵敏度
   ColorfulMode: 0,      // 夜视模式
   lightOnHour: 0,       // 开灯时间-时
   lightOnMinute: 0,     // 开灯时间-分
@@ -165,8 +165,8 @@ async function resetDefaults() {
   form.NightMode = 0
   form.AutoLightEn = false
   form.PwmLight = 50
-  form.FlOnSens = 50
-  form.FlOffSens  = 50
+  form.FIOnSens = 50
+  form.FIOffSens  = 50
   form.ColorfulMode = 0
   form.lightOnHour = 0
   form.lightOnMinute = 0
@@ -184,8 +184,8 @@ async function fetchParams() {
       if (d.NightMode !== undefined) form.NightMode = Number(d.NightMode)
       if (d.AutoLightEn !== undefined) form.AutoLightEn = !!d.AutoLightEn
       if (d.PwmLight !== undefined) form.PwmLight = Number(d.PwmLight)
-      if (d.FlOnSens !== undefined) form.FlOnSens = Number(d.FlOnSens)
-      if (d.FlOffSens  !== undefined) form.FlOffSens  = Number(d.FlOffSens )
+      if (d.FIOnSens !== undefined) form.FIOnSens = Number(d.FIOnSens)
+      if (d.FIOffSens  !== undefined) form.FIOffSens  = Number(d.FIOffSens )
       if (d.ColorfulMode !== undefined) form.ColorfulMode = Number(d.ColorfulMode)
       // 解析 LightOnTime / LightOffTime 嵌套格式
       if (d.LightOnTime) {
@@ -207,10 +207,10 @@ async function saveParams() {
   try {
     const data = {
       NightMode: form.NightMode,
-      AutoLightEn: form.AutoLightEn ? 1 : 0,
+      AutoLightEn: form.AutoLightEn,
       PwmLight: form.PwmLight,
-      FlOnSens: form.FlOnSens,
-      FlOffSens : form.FlOffSens ,
+      FIOnSens: form.FIOnSens,
+      FIOffSens : form.FIOffSens ,
       ColorfulMode: form.ColorfulMode,
       LightOnTime: { tm_hour: form.lightOnHour, tm_min: form.lightOnMinute },
       LightOffTime: { tm_hour: form.lightOffHour, tm_min: form.lightOffMinute }

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

@@ -297,7 +297,7 @@ onMounted(() => {
 .form-item {
   display: flex;
   align-items: center;
-  margin-bottom: 16px;
+  margin-bottom: 10px;
 
   &__label {
     flex: 0 0 120px;

+ 9 - 5
src/views/settings/imageDisplay/components/image/index.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="image-settings">
     <!-- 左侧视频预览 -->
-    <div class="image-settings__video">
-      <MyVideo :drag-flag="false" />
-    </div>
+  <div class="image-settings__video">
+    <MyVideo ref="myVideoRef" :drag-flag="false" />
+  </div>
 
     <!-- 右侧设置面板 -->
     <div class="image-settings__panel">
@@ -104,6 +104,9 @@ import { ElMessage } from 'element-plus'
 import MyVideo from '@/components/myVideo.vue'
 import { getImagePara, putImagePara } from '@/api/setting'
 
+// 视频组件引用
+const myVideoRef = ref<InstanceType<typeof MyVideo> | null>(null)
+
 // 基本参数
 const basicParams = reactive({
   CscLuma: 50,        // 亮度 
@@ -186,9 +189,10 @@ async function saveParams() {
     }
     const res = await putImagePara(data)
     if (res.data === 'ok\n') {
-      ElMessage.success('设置成功')
+      // ElMessage.success('设置成功')
     }
   } catch (e) {
+    console.log(e)
     ElMessage.warning('设置失败')
   }
 }
@@ -327,4 +331,4 @@ onMounted(() => {
     }
   }
 }
-</style>
+</style>

+ 8 - 4
src/views/settings/imageDisplay/components/osd/index.vue

@@ -3,7 +3,7 @@
     <!-- 左侧:视频预览 + OSD拖拽 -->
     <div class="osd-settings__left">
       <div class="osd-settings__video" ref="videoWrapRef">
-        <MyVideo :drag-flag="false" />
+        <MyVideo ref="myVideoRef" :drag-flag="false" />
         <!-- OSD叠加层:用于拖拽定位 -->
         <div class="osd-overlay" ref="overlayRef">
           <!-- 时间OSD -->
@@ -128,6 +128,8 @@ import { Edit } from '@element-plus/icons-vue'
 import MyVideo from '@/components/myVideo.vue'
 import { getOsdPara, putOsdPara } from '@/api/setting'
 
+const myVideoRef = ref<InstanceType<typeof MyVideo> | null>(null)
+
 // 表单数据
 const form = reactive({
   EnName: true,
@@ -269,6 +271,7 @@ async function saveOsd() {
     }
     const res = await putOsdPara(data)
     if (res.data === 'ok\n') {
+      myVideoRef.value?.refreshWebSocket()
       ElMessage.success('Saved successfully')
     }
   } catch {
@@ -349,8 +352,9 @@ onUnmounted(() => {
   pointer-events: auto;
   cursor: move;
   padding: 2px 6px;
-  font-size: 12px;
-  color: #fff;
+  font-size: 16px;
+  font-weight: 500;
+  color: #ed0c0c;
   white-space: nowrap;
   user-select: none;
   border: 1px solid transparent;
@@ -449,4 +453,4 @@ onUnmounted(() => {
     min-width: 80px;
   }
 }
-</style>
+</style>

+ 28 - 5
src/views/settings/imageDisplay/components/privacyMasking/index.vue

@@ -3,7 +3,7 @@
     <!-- 左侧:视频 + 底部提示 -->
     <div class="privacy-masking__left">
       <div class="privacy-masking__video">
-        <MyVideo :drag-flag="false" />
+        <MyVideo ref="myVideoRef" :drag-flag="false" />
         <canvas
           ref="canvasRef"
           class="mask-canvas"
@@ -62,10 +62,12 @@
 
 <script setup lang="ts">
 import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import MyVideo from '@/components/myVideo.vue'
 import { getPrivacyMask, putPrivacyMask } from '@/api/setting'
 
+const myVideoRef = ref<InstanceType<typeof MyVideo> | null>(null)
+
 // 遮挡矩形(canvas 像素坐标)
 interface MaskRect {
   x: number
@@ -93,12 +95,26 @@ let dragOffsetY = 0
 let tempRect: MaskRect | null = null
 
 // 颜色映射:颜色名 -> 接口十六进制值
-const COLOR_MAP: Record<string, string> = {
+const COLOR_MAP = {
   black: 0x000000,
   red: 0xFF0000
 }
 
-function setColor(color: 'black' | 'red') {
+async function setColor(color: 'black' | 'red') {
+  // 如果遮罩已满且切换了颜色,询问用户是否清空重新绘制
+  if (masks.length >= MAX_MASKS && color !== maskColor.value) {
+    try {
+      await ElMessageBox.confirm(
+        'All 5 mask regions are in use. Switching color will clear all existing masks so you can redraw. Continue?',
+        'Confirm',
+        { confirmButtonText: 'OK', cancelButtonText: 'Cancel', type: 'warning' }
+      )
+      masks.splice(0, masks.length)
+    } catch {
+      // 用户取消,不做任何操作
+      return
+    }
+  }
   maskColor.value = color
   drawAll()
 }
@@ -186,8 +202,14 @@ function onMouseUp() {
 }
 
 function clearAllMasks() {
+  const count = masks.length
+  if (count === 0) {
+    ElMessage.info('No mask regions to clear')
+    return
+  }
   masks.splice(0, masks.length)
   drawAll()
+  ElMessage.success(`Cleared ${count} mask region${count > 1 ? 's' : ''}`)
 }
 
 function drawAll(extra?: MaskRect) {
@@ -270,6 +292,7 @@ async function saveMasks() {
     }
     const res = await putPrivacyMask(data)
     if (res.data === 'ok\n') {
+      // myVideoRef.value?.refreshWebSocket()
       ElMessage.success('Saved successfully')
     }
   } catch {
@@ -282,7 +305,7 @@ async function fetchMasks() {
     const res = await getPrivacyMask()
     if (res.data) {
       const d = res.data
-      enableMask.value = d.CoverEnable === 1
+      enableMask.value = !! d.CoverEnable
       if (Array.isArray(d.CoverList) && d.CoverList.length > 0) {
         // 从第一个区域读取颜色
         maskColor.value = colorFromHex(d.CoverList[0].CoverColor)

+ 3 - 3
src/views/settings/imageDisplay/index.vue

@@ -2,13 +2,13 @@
   <div class="settings-container">
     <el-tabs v-model="activeName" class="tabs">
       <el-tab-pane label="Image" name="first">
-        <Image />
+        <Image v-if="activeName === 'first'" />
       </el-tab-pane>
       <el-tab-pane label="OSD Settings" name="second">
-        <OSD />
+        <OSD v-if="activeName === 'second'" />
       </el-tab-pane>
       <el-tab-pane label="Privacy Masking " name="third">
-        <PrivacyMasking />
+        <PrivacyMasking v-if="activeName === 'third'" />
       </el-tab-pane>
     </el-tabs>
   </div>

+ 2 - 3
src/views/settings/netSettings/components/networkDiagnostics/index.vue

@@ -75,9 +75,8 @@ const handlePing = async () => {
   loading.value = true
   result.value = ''
   try {
-    // const res = await networkDiagnostics({ DomainName: host })
-    // result.value = res.data?.NetworkStatus || ''
-    result.value = "PINGwww.baidu.com(183.240.99.224):56databytes\n64bytesfrom183.240.99.224:seq=0ttl=53time=12.822ms\n64bytesfrom183.240.99.224:seq=1ttl=53time=13.056ms\n64bytesfrom183.240.99.224:seq=2ttl=53time=12.501ms\n64bytesfrom183.240.99.224:seq=3ttl=53time=12.116ms\n64bytesfrom183.240.99.224:seq=4ttl=53time=12.488ms\n\n---www.baidu.compingstatistics---\n5packetstransmitted,5packetsreceived,00x62eacketloss\nround-tripmin/avg/max=12.116/12.596/13.056ms\n"
+    const res = await networkDiagnostics({ DomainName: host })
+    result.value = res.data?.NetworkStatus || ''
   } catch {
     ElMessage.error('Diagnosis failed. Please check your network connection.')
   } finally {

+ 97 - 69
src/views/settings/systemSettings/components/cameraInfo/index.vue

@@ -1,89 +1,117 @@
 <template>
-  <div class="setting-container">
-    <div class="content">
-      <el-form
-        ref="formRef"
-        v-loading="loading"
-        label-position="left"
-        label-width="130px"
-        :model="formData"
-      >
-        <el-form-item label="Device Name" prop="DeviceName">
-          <el-input
-            v-model="formData.DeviceName"
-            class="el-input"
-            :disabled="true"
-            type="text"
-          />
-        </el-form-item>
-        <el-form-item label="Software Version" prop="SoftwareVer">
-          <el-input
-            v-model="formData.SoftwareVer"
-            :disabled="true"
-            type="text"
-          />
-        </el-form-item>
-        <el-form-item label="Hardware Version" prop="HardwareVer">
-          <el-input
-            v-model="formData.HardwareVer"
-            :disabled="true"
-            type="text"
-          />
-        </el-form-item>
-        <el-form-item label="MAC Address" prop="MacAddr">
-          <el-input
-            v-model="formData.MacAddr"
-            :disabled="true"
-            type="text"
-          />
-        </el-form-item>
-
-      </el-form>
+  <div class="camera-info">
+    <div class="section" v-loading="loading">
+      <div class="section-header">
+        <h3>Device Information</h3>
+      </div>
+      <div class="section-content">
+        <div class="form-item">
+          <label class="form-label">Device Name</label>
+          <span class="form-value">{{ formData.DeviceName || '-' }}</span>
+        </div>
+        <div class="form-item">
+          <label class="form-label">Software Version</label>
+          <span class="form-value">{{ formData.SoftwareVer || '-' }}</span>
+        </div>
+        <div class="form-item">
+          <label class="form-label">Hardware Version</label>
+          <span class="form-value">{{ formData.HardwareVer || '-' }}</span>
+        </div>
+        <div class="form-item">
+          <label class="form-label">MAC Address</label>
+          <span class="form-value mono">{{ formData.MacAddr || '-' }}</span>
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { type FormInstance } from 'element-plus'
 import { getCameraDeviceInfo } from '@/api/setting'
+import type { CameraDeviceInfo } from '@/api/types/setting'
 
-/** 表单元素的引用 */
-const formRef = ref<FormInstance | null>(null)
-
-onMounted(() => {
-  deviceInfo()
+const formData = ref<CameraDeviceInfo>({
+  DeviceName: '',
+  SoftwareVer: '',
+  HardwareVer: '',
+  MacAddr: ''
 })
-
-/** 相机设备信息 */
-export interface CameraDeviceInfo {
-  DeviceName: string
-  SoftwareVer: string
-  HardwareVer: string
-  MacAddr: string
-}
-
-const formData = ref<CameraDeviceInfo>({})
 const loading = ref(false)
 
-const deviceInfo = () => {
-  getCameraDeviceInfo().then((device) => {
-    formData.value = device.data
-  })
+const fetchDeviceInfo = async () => {
+  loading.value = true
+  try {
+    const res = await getCameraDeviceInfo()
+    formData.value = res.data
+  } catch {
+    ElMessage.error('Failed to get device info')
+  } finally {
+    loading.value = false
+  }
 }
 
+onMounted(() => {
+  fetchDeviceInfo()
+})
 </script>
 
 <style lang="scss" scoped>
-.setting-container {
-  padding: 20px;
-}
+$text-primary: #1f2d3d;
+$text-secondary: #333;
+$divider-color: #f0f0f0;
 
-.el-input {
-  width: 210px;
-}
+.camera-info {
+  max-width: 700px;
+  padding: 0;
+
+  .section {
+    padding: 20px;
+    background: #fff;
+    border-radius: 8px;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+  }
+
+  .section-header {
+    padding-bottom: 16px;
+    margin-bottom: 16px;
+    border-bottom: 2px solid $divider-color;
+
+    h3 {
+      font-size: 15px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+  }
+
+  .section-content {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .form-item {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .form-label {
+      min-width: 140px;
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-secondary;
+      flex-shrink: 0;
+    }
+
+    .form-value {
+      font-size: 14px;
+      color: #606266;
 
-/* 加深表单label颜色 */
-:deep(.el-form-item__label) {
-  color: #1a1a1a; /* 更深的颜色值 */
+      &.mono {
+        font-family: 'Monaco', 'Courier New', monospace;
+        letter-spacing: 0.5px;
+      }
+    }
+  }
 }
 </style>

+ 29 - 29
src/views/settings/systemSettings/components/user/index.vue → src/views/settings/systemSettings/components/passwordManagement/index.vue

@@ -116,7 +116,7 @@
 
         <div class="form-actions">
 <!--          <el-button  @click="resetForm"> Reset </el-button>-->
-          <el-button class="btn-save" round @click.prevent="handleConfirm">Save</el-button>
+          <el-button class="btn-save" type="primary" round @click.prevent="handleConfirm">Save</el-button>
         </div>
       </el-form>
     </div>
@@ -401,34 +401,34 @@ $primary-hover: #2563eb;
     // justify-content: center;
     justify-content: flex-start;
 
-    .btn-save {
-      background-color: $primary-color !important;
-      color: #fff !important;
-      border: none !important;
-      min-width: 80px;
-      height: 36px;
-      font-weight: 500;
-      font-size: 14px;
-      box-shadow: 0 2px 4px rgba($primary-color, 0.2);
-      transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
-
-      &:hover {
-        background-color: $primary-hover !important;
-        box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
-      }
-
-      &:active {
-        box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
-        transform: translateY(1px);
-      }
-
-      &:disabled,
-      &.is-disabled {
-        background-color: #c0c4cc !important;
-        cursor: not-allowed;
-        box-shadow: none;
-      }
-    }
+    //.btn-save {
+    //  background-color: $primary-color !important;
+    //  color: #fff !important;
+    //  border: none !important;
+    //  min-width: 80px;
+    //  height: 36px;
+    //  font-weight: 500;
+    //  font-size: 14px;
+    //  box-shadow: 0 2px 4px rgba($primary-color, 0.2);
+    //  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+    //
+    //  &:hover {
+    //    background-color: $primary-hover !important;
+    //    box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
+    //  }
+    //
+    //  &:active {
+    //    box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
+    //    transform: translateY(1px);
+    //  }
+    //
+    //  &:disabled,
+    //  &.is-disabled {
+    //    background-color: #c0c4cc !important;
+    //    cursor: not-allowed;
+    //    box-shadow: none;
+    //  }
+    //}
 
     //.el-button {
     //  min-width: 100px;

+ 30 - 29
src/views/settings/systemSettings/components/systemMaintenance/index.vue

@@ -69,6 +69,7 @@
 
     <el-button
         class="btn-save"
+        type="primary"
         round
         @click="Save"
         :loading="loading"
@@ -253,7 +254,7 @@ const Save = async () => {
 
 <style scoped lang="scss">
 // 颜色和尺寸变量定义
-$primary-color: #3b82f6;
+$primary-color: #409EFF;
 $primary-hover: #2563eb;
 $warning-color: #f59e0b;
 $warning-light: #fef3c7;
@@ -387,34 +388,34 @@ $radius-md: 6px;
 // 按钮样式 - 通过覆盖 Element Plus 默认样式
 
 /* Save 按钮 - 主操作 */
-.btn-save {
-  background-color: $primary-color !important;
-  color: #fff !important;
-  border: none !important;
-  min-width: 80px;
-  height: 36px;
-  font-weight: 500;
-  font-size: 14px;
-  box-shadow: 0 2px 4px rgba($primary-color, 0.2);
-  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
-
-  &:hover {
-    background-color: $primary-hover !important;
-    box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
-  }
-
-  &:active {
-    box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
-    transform: translateY(1px);
-  }
-
-  &:disabled,
-  &.is-disabled {
-    background-color: #c0c4cc !important;
-    cursor: not-allowed;
-    box-shadow: none;
-  }
-}
+//.btn-save {
+//  background-color: $primary-color !important;
+//  color: #fff !important;
+//  border: none !important;
+//  min-width: 80px;
+//  height: 36px;
+//  font-weight: 500;
+//  font-size: 14px;
+//  box-shadow: 0 2px 4px rgba($primary-color, 0.2);
+//  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+//
+//  &:hover {
+//    background-color: $primary-hover !important;
+//    box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
+//  }
+//
+//  &:active {
+//    box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
+//    transform: translateY(1px);
+//  }
+//
+//  &:disabled,
+//  &.is-disabled {
+//    background-color: #c0c4cc !important;
+//    cursor: not-allowed;
+//    box-shadow: none;
+//  }
+//}
 
 /* Restart Now - 中等风险操作,琥珀色 */
 .btn-restart {

+ 68 - 17
src/views/settings/systemSettings/components/systemUpgrade/index.vue

@@ -5,7 +5,7 @@
       <p class="subtitle">Select and upload the upgrade file to update your device</p>
     </div>
 
-    <el-form ref="formRef" :model="formData" class="upgrade-form">
+    <el-form class="upgrade-form">
       <div class="upload-section">
         <el-upload
           class="file-upload"
@@ -19,7 +19,7 @@
             <div class="upload-icon">📦</div>
             <div class="upload-text">
               <p class="text-primary">Click or drag file here</p>
-              <p class="text-secondary">Supported file format: .bin, .img, .zip</p>
+              <p class="text-secondary">Supported file format: .img</p>
             </div>
           </div>
         </el-upload>
@@ -69,20 +69,18 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref } from 'vue'
 import {
   ElLoading,
   ElMessage,
-  ElMessageBox,
-  type FormInstance
+  ElMessageBox
 } from 'element-plus'
 import { useUserStore } from '@/stores/modules/user'
 import { useRouter } from 'vue-router'
+import { systemUpgrade } from '@/api/setting'
 
 const router = useRouter()
 const loading = ref(false)
-const formRef = ref<FormInstance | null>(null)
-const formData = reactive({})
 const selectedFile = ref<File | null>(null)
 
 const formatFileSize = (bytes: number): string => {
@@ -94,7 +92,39 @@ const formatFileSize = (bytes: number): string => {
 }
 
 const handleFileChange = (file: any) => {
-  selectedFile.value = file.raw
+  const rawFile = file.raw
+  
+  // 校验文件大小(不超过6M)
+  const maxSize = 6 * 1024 * 1024 // 6MB
+  if (rawFile.size > maxSize) {
+    ElMessage.error('File size cannot exceed 6MB')
+    return
+  }
+  
+  // 校验文件后缀(必须为.img)
+  const fileName = rawFile.name
+  const fileExtension = fileName.split('.').pop()?.toLowerCase()
+  if (fileExtension !== 'img') {
+    ElMessage.error('File must have .img extension')
+    return
+  }
+  
+  // 校验文件名称格式(包含MD5值)
+  const fileNamePattern = /^V[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[A-Z0-9]+_[0-9a-f]{32}\.img$/i
+  if (!fileNamePattern.test(fileName)) {
+    ElMessage.error('Invalid file name format. Please use the correct upgrade file.')
+    return
+  }
+  
+  // 提取并验证MD5值
+  const md5Match = fileName.match(/_(\w{32})\.img$/i)
+  if (md5Match) {
+    const md5Value = md5Match[1]
+    console.log('MD5 value found:', md5Value)
+    // 这里可以添加MD5值的进一步验证逻辑
+  }
+  
+  selectedFile.value = rawFile
 }
 
 const handleClearFile = () => {
@@ -107,6 +137,8 @@ const handleUpgrade = async () => {
     return
   }
 
+  let loadingInstance: any = null
+  
   try {
     await ElMessageBox.confirm(
       'The upgrade operation will restart the device and may take several minutes. Are you sure you want to continue?',
@@ -120,18 +152,37 @@ const handleUpgrade = async () => {
     )
 
     loading.value = true
-    // Here should call the actual upgrade API
-    // Simulate upgrade process
-    setTimeout(() => {
-      loading.value = false
-      ElMessage.success(
-        'Upgrade successful! The device is restarting, please wait...'
-      )
+    loadingInstance = ElLoading.service({
+      lock: true,
+      text: 'System upgrade in progress. Do not close this window or power off your device.',
+      background: 'rgba(0, 0, 0, 0.7)'
+    })
+    
+    const uploadData = new FormData()
+    uploadData.append('UpgradeFile', selectedFile.value)
+    const response = await systemUpgrade(uploadData)
+
+    // 先关闭 loading,再处理结果(避免路由跳转后组件卸载导致无法关闭)
+    loadingInstance.close()
+    loading.value = false
+
+    if(response.data.UpgradeCode === 0){
+      ElMessage.success('Upgrade successful! The device is restarting, please wait...')
       useUserStore().logout()
-      router.push('/login')
-    }, 3000)
+      await router.push('/login')
+    }else if(response.data.UpgradeCode === 1){
+      ElMessage.error('Upgrade failed! MD5 verification failed,please check the file integrity.')
+    }else if(response.data.UpgradeCode === 2){
+      ElMessage.error('Upgrade failed! The version numbers do not match.')
+    } else {
+      ElMessage.error('Upgrade failed! An unexpected error has occurred.')
+    }
   } catch (error) {
     console.log(error)
+    loading.value = false
+    if (loadingInstance) {
+      loadingInstance.close()
+    }
     if (error !== 'cancel') {
       ElMessage.error('Upgrade failed')
     }

+ 49 - 45
src/views/settings/systemSettings/components/time/index.vue → src/views/settings/systemSettings/components/timeSettings/index.vue

@@ -99,6 +99,7 @@
       <div class="button-group">
         <el-button
             round
+            type="primary"
             @click="saveSettings"
             :loading="saveLoading"
             class="save-btn"
@@ -259,7 +260,8 @@ async function saveSettings() {
 
 <style scoped lang="scss">
 // 颜色和尺寸变量定义
-$primary-color: #3b82f6;
+$primary-color: #409EFF;
+//$primary-color: #3b82f6;
 $primary-hover: #2563eb;
 $text-primary: #1f2d3d;
 $text-secondary: #333;
@@ -386,25 +388,27 @@ $transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
     }
 
     .sync-btn {
-      background-color: $primary-color !important;
-      color: #fff !important;
-      border: none !important;
-      height: 30px;
-      min-width: 80px;
+      //background-color: $primary-color !important;
+      //color: #fff !important;
+      //border: none !important;
+      border-radius: 8px;
+
+      height: 26px;
+      width: 75px;
       font-weight: 500;
       font-size: 12px;
       box-shadow: 0 2px 4px rgba($primary-color, 0.2);
       transition: all 0.2s $transition-easing;
 
-      &:hover {
-        background-color: $primary-hover !important;
-        box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
-      }
-
-      &:active {
-        box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
-        transform: translateY(1px);
-      }
+      //&:hover {
+      //  background-color: $primary-hover !important;
+      //  box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
+      //}
+      //
+      //&:active {
+      //  box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
+      //  transform: translateY(1px);
+      //}
     }
   }
 
@@ -454,34 +458,34 @@ $transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
     font-weight: 500;
   }
 
-  .save-btn {
-    background-color: $primary-color !important;
-    color: #fff !important;
-    border: none !important;
-    min-width: 80px;
-    height: 36px;
-    font-weight: 500;
-    font-size: 14px;
-    box-shadow: 0 2px 4px rgba($primary-color, 0.2);
-    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
-
-    &:hover {
-      background-color: $primary-hover !important;
-      box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
-    }
-
-    &:active {
-      box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
-      transform: translateY(1px);
-    }
-
-    &:disabled,
-    &.is-disabled {
-      background-color: #c0c4cc !important;
-      cursor: not-allowed;
-      box-shadow: none;
-    }
-  }
+  //.save-btn {
+  //  background-color: $primary-color !important;
+  //  color: #fff !important;
+  //  border: none !important;
+  //  min-width: 80px;
+  //  height: 36px;
+  //  font-weight: 500;
+  //  font-size: 14px;
+  //  box-shadow: 0 2px 4px rgba($primary-color, 0.2);
+  //  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+  //
+  //  &:hover {
+  //    background-color: $primary-hover !important;
+  //    box-shadow: 0 4px 8px rgba($primary-hover, 0.3);
+  //  }
+  //
+  //  &:active {
+  //    box-shadow: 0 1px 2px rgba($primary-hover, 0.2);
+  //    transform: translateY(1px);
+  //  }
+  //
+  //  &:disabled,
+  //  &.is-disabled {
+  //    background-color: #c0c4cc !important;
+  //    cursor: not-allowed;
+  //    box-shadow: none;
+  //  }
+  //}
 }
 
 // Element Plus 组件样式覆盖
@@ -540,11 +544,11 @@ $transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
 // Switch 样式
 :deep(.el-switch) {
   --el-switch-on-color: $primary-color;
-  --el-switch-off-color: #909399; // 使用更深的灰色以提高对比度
+  --el-switch-off-color: #d1d1d3; // 使用更深的灰色以提高对比度
   height: 22px;
 
   .el-switch__core {
-    border-color: #909399; // 设置边框颜色与关闭状态一致
+    border-color: #d1d1d3; // 设置边框颜色与关闭状态一致
   }
 
   &.is-checked .el-switch__core {

+ 4 - 4
src/views/settings/systemSettings/index.vue

@@ -10,11 +10,11 @@
       </el-tab-pane>
 
       <el-tab-pane label="Time Settings" name="fourth">
-        <TimeSetting />
+        <TimeSettings />
       </el-tab-pane>
 
       <el-tab-pane label="Password Management" name="fifth">
-        <UserSecurity />
+        <PasswordManagement />
       </el-tab-pane>
 
       <el-tab-pane label="System Update" name="third">
@@ -27,8 +27,8 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 import CameraInfo from './components/cameraInfo/index.vue'
-import UserSecurity from './components/user/index.vue'
-import TimeSetting from './components/time/index.vue'
+import PasswordManagement from './components/passwordManagement/index.vue'
+import TimeSettings from './components/timeSettings/index.vue'
 import SystemMaintenance from './components/systemMaintenance/index.vue'
 import SystemUpgrade from './components/systemUpgrade/index.vue'
 

+ 22 - 23
src/views/standardProtocol/components/onvif/index.vue

@@ -10,27 +10,27 @@
       <el-form :model="onvifConfig" label-width="125px" class="onvif-form">
         <!-- Enable ONVIF -->
         <el-form-item label="Enable ONVIF">
-          <el-switch v-model="onvifConfig.OnvifEnable" />
+          <el-switch v-model="onvifConfig.OnvifEnable"/>
         </el-form-item>
 
         <!-- IP Address -->
         <el-form-item label="IP Address">
-          <el-input v-model="onvifConfig.IpAddr" placeholder="Enter IP address" />
+          <el-input v-model="onvifConfig.IpAddr" placeholder="Enter IP address"/>
         </el-form-item>
 
         <!-- Port -->
         <el-form-item label="Port">
-          <el-input v-model="onvifConfig.Port" placeholder="Enter port number" />
+          <el-input v-model="onvifConfig.Port" placeholder="Enter port number"/>
         </el-form-item>
 
         <!-- ONVIF Username -->
         <el-form-item label="ONVIF Username">
-          <el-input v-model="onvifConfig.OnvifName" placeholder="Enter ONVIF username" />
+          <el-input v-model="onvifConfig.OnvifName" placeholder="Enter ONVIF username"/>
         </el-form-item>
 
         <!-- ONVIF Password -->
         <el-form-item label="ONVIF Password">
-          <el-input v-model="onvifConfig.Password" type="password" placeholder="Enter ONVIF password" show-password />
+          <el-input v-model="onvifConfig.Password" type="password" placeholder="Enter ONVIF password" show-password/>
         </el-form-item>
 
         <!-- Save Button -->
@@ -40,21 +40,21 @@
       </el-form>
 
       <!-- Access Example -->
-<!--      <div class="examples-section" v-if="onvifConfig.OnvifEnable">-->
-<!--        <h4>ONVIF Device Manager Access Example</h4>-->
-<!--        <div class="example-box">-->
-<!--          <div class="example-title">Using ONVIF Device Manager</div>-->
-<!--          <div class="code-block">-->
-<!--            <code>{{ onvifAccessUrl }}</code>-->
-<!--            <el-button type="primary" @click="copyToClipboard(onvifAccessUrl)">-->
-<!--              Copy-->
-<!--            </el-button>-->
-<!--          </div>-->
-<!--          <div class="tip-text">-->
-<!--            Use the above URL with your ONVIF Device Manager software to connect to the camera.-->
-<!--          </div>-->
-<!--        </div>-->
-<!--      </div>-->
+      <!--      <div class="examples-section" v-if="onvifConfig.OnvifEnable">-->
+      <!--        <h4>ONVIF Device Manager Access Example</h4>-->
+      <!--        <div class="example-box">-->
+      <!--          <div class="example-title">Using ONVIF Device Manager</div>-->
+      <!--          <div class="code-block">-->
+      <!--            <code>{{ onvifAccessUrl }}</code>-->
+      <!--            <el-button type="primary" @click="copyToClipboard(onvifAccessUrl)">-->
+      <!--              Copy-->
+      <!--            </el-button>-->
+      <!--          </div>-->
+      <!--          <div class="tip-text">-->
+      <!--            Use the above URL with your ONVIF Device Manager software to connect to the camera.-->
+      <!--          </div>-->
+      <!--        </div>-->
+      <!--      </div>-->
     </div>
 
     <!-- Save Success Toast -->
@@ -74,11 +74,10 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted } from 'vue'
-import { getOnvifProtocolApi, OnvifProtocolApi } from '@/api/protocol'
+import {ref, onMounted} from 'vue'
+import {getOnvifProtocolApi, OnvifProtocolApi} from '@/api/protocol'
 import {ElMessage} from 'element-plus'
 
-
 const onvifConfig = ref({
   OnvifEnable: false,
   IpAddr: '',

+ 21 - 13
src/views/standardProtocol/components/rtsp/index.vue

@@ -71,23 +71,32 @@
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue'
+import {getUserSettingApi} from '@/api/setting'
+import {getRtspPasswordApi} from '@/api/userManagement'
 
 const passwordVerificationEnabled = ref(false)
-const cameraIp = ref('192.168.0.105')
+const cameraIp = ref('192.168.1.100')
 const showCopyToast = ref(false)
+const NIC = 1
 
-// Fetch configuration
-onMounted(async () => {
-  try {
-    const response = await fetch('/api/camera/config')
-    if (response.ok) {
-      const data = await response.json()
-      passwordVerificationEnabled.value = data.passwordEnabled === true
-      cameraIp.value = data.cameraIp || '192.168.0.105'
-    }
-  } catch (error) {
-    console.error('Failed to fetch configuration:', error)
+// 获取本机的IP地址
+async function getIpAddress() {
+  const response = await getUserSettingApi(NIC)
+  if (response.data) {
+    cameraIp.value = response.data.ipAddress
   }
+}
+// 获取rtsp password校验的状态
+async function getRtspPasswordStatus() {
+  const response = await getRtspPasswordApi()
+  if (response.data) {
+    passwordVerificationEnabled.value = !!response.data.RtspPwdEn
+  }
+}
+
+onMounted(async () => {
+  await getIpAddress()
+  await getRtspPasswordStatus()
 })
 
 // Copy to clipboard
@@ -100,7 +109,6 @@ const copyToClipboard = async (text: string) => {
   }
 }
 
-// Copy example
 const copyExample = () => {
   const exampleText = `rtsp://username:password@${cameraIp.value}:554/video1`
   copyToClipboard(exampleText)

+ 2 - 2
src/views/standardProtocol/index.vue

@@ -2,11 +2,11 @@
   <div class="settings-container">
     <el-tabs v-model="activeName" class="tabs">
       <el-tab-pane label="RTSP" name="first">
-        <RTSP/>
+        <RTSP v-if="activeName === 'first'" />
       </el-tab-pane>
 
       <el-tab-pane label="ONVIF" name="second">
-        <Onvif/>
+        <Onvif v-if="activeName === 'second'" />
       </el-tab-pane>
     </el-tabs>
   </div>