Просмотр исходного кода

feat(camera-scan): update device discovery response type and enhance binding functionality

- Changed the return type of `getDiscoveredDevices` to `Promise<IBaseResponse<DiscoveredCameraDTO[]>>` for better type safety.
- Refactored `DiscoveredCameraDTO` to include `cameraId` and `uuid` as well as updated properties for clarity.
- Added binding functionality in `useScanDevices` to allow users to bind discovered devices with confirmation prompts and success/error messages.
- Improved user feedback during device scanning and binding processes.
yb 1 день назад
Родитель
Сommit
654ac82fd7
4 измененных файлов с 52 добавлено и 37 удалено
  1. 1 1
      src/api/camera-scan.ts
  2. 10 15
      src/types/index.ts
  3. 28 9
      src/views/lss/composables/useScanDevices.ts
  4. 13 12
      src/views/lss/index.vue

+ 1 - 1
src/api/camera-scan.ts

@@ -22,7 +22,7 @@ export function scanDevices(lssId: string): Promise<IBaseResponse<ScanResultDTO>
 }
 
 // 获取发现设备列表
-export function getDiscoveredDevices(lssId: string): Promise<IListResponse<DiscoveredCameraDTO>> {
+export function getDiscoveredDevices(lssId: string): Promise<IBaseResponse<DiscoveredCameraDTO[]>> {
   return get(`/admin/lss/${lssId}/discovered`)
 }
 

+ 10 - 15
src/types/index.ts

@@ -744,30 +744,25 @@ export interface StreamPushChannelDTO {
 // ==================== 摄像头扫描相关类型 ====================
 
 // 扫描匹配状态
-export type MatchStatus = 'PENDING' | 'MATCHING' | 'MATCHED' | 'UNMATCHED'
+// PENDING("pending", "待匹配"),
+// MATCHING("matching", "匹配中"),
+// MATCHED("matched", "已匹配"),
+// UNMATCHED("unmatched", "未匹配");
+export type MatchStatus = 'pending' | 'matching' | 'matched' | 'unmatched'
 
 // 发现设备 DTO
 export interface DiscoveredCameraDTO {
   id: number
-  lssId: string
   ip: string
   port: number
-  uuid: string
-  deviceName: string
   vendor: string
   model: string
-  serialNumber: string
-  rtspUrl: string
+  cameraId: string
+  uuid: string | null
   matchStatus: MatchStatus
-  matchedCredentialId?: number
-  matchAttempts: number
-  lastMatchAttempt?: string
-  matchError?: string
-  deviceInfo: string
   bound: boolean
-  boundCameraId?: number
-  canMatch: boolean
-  discoveredAt: string
+  canBind: boolean
+  discoveredAt: number
 }
 
 // 扫描结果 DTO
@@ -834,7 +829,7 @@ export interface MatchRequest {
 // 绑定设备请求
 export interface BindDeviceRequest {
   discoveredId: number
-  cameraId: number
+  cameraId: string
 }
 
 // 播放信息 DTO

+ 28 - 9
src/views/lss/composables/useScanDevices.ts

@@ -1,10 +1,10 @@
 import { ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useI18n } from 'vue-i18n'
-import { scanDevices, getDiscoveredDevices, triggerMatch } from '@/api/camera-scan'
+import { scanDevices, getDiscoveredDevices, triggerMatch, bindDevice } from '@/api/camera-scan'
 import type { LssNodeDTO, DiscoveredCameraDTO } from '@/types'
 
-export function useScanDevices() {
+export function useScanDevices(options?: { onRefresh?: () => void }) {
   const { t } = useI18n({ useScope: 'global' })
 
   const scanDrawerVisible = ref(false)
@@ -17,20 +17,21 @@ export function useScanDevices() {
   async function handleScanDevices(row: LssNodeDTO) {
     scanLssId.value = row.lssId
     scanMatched.value = false
-    scanDrawerVisible.value = true
-    scanLoading.value = true
-
     try {
       if (row.scanned) {
-        // 已扫描过,直接加载发现设备列表
+        // 已扫描过,直接打开抽屉加载列表
+        scanDrawerVisible.value = true
+        scanLoading.value = true
         await loadDiscoveredDevices()
       } else {
-        // 未扫描,触发扫描
+        // 未扫描,先触发扫描
+        scanLoading.value = true
         const res = await scanDevices(row.lssId)
         if (res.success) {
           const status = res?.data?.status || ''
           switch (status) {
             case 'COMPLETED':
+              scanDrawerVisible.value = true
               await loadDiscoveredDevices()
               break
             case 'SCANNING':
@@ -52,6 +53,8 @@ export function useScanDevices() {
       ElMessage.error(t('操作失败'))
     } finally {
       scanLoading.value = false
+      // 每次扫描操作后刷新 LSS 列表,更新 scanned 状态
+      options?.onRefresh?.()
     }
   }
 
@@ -98,8 +101,24 @@ export function useScanDevices() {
   }
 
   async function handleBindDevice(row: DiscoveredCameraDTO) {
-    console.log('bind device', row)
-    ElMessage.info('绑定功能开发中')
+    try {
+      await ElMessageBox.confirm(t('确定要绑定该设备吗?') + `<br/>IP: ${row.ip}:${row.port}`, t('绑定设备'), {
+        type: 'info',
+        dangerouslyUseHTMLString: true
+      })
+      const res = await bindDevice({ discoveredId: row.id, cameraId: row.cameraId })
+      if (res.success) {
+        ElMessage.success(t('绑定成功'))
+        await loadDiscoveredDevices()
+      } else {
+        ElMessage.error(res.errMessage || t('绑定失败'))
+      }
+    } catch (error) {
+      if (error !== 'cancel') {
+        console.error('绑定失败', error)
+        ElMessage.error(t('绑定失败'))
+      }
+    }
   }
 
   return {

+ 13 - 12
src/views/lss/index.vue

@@ -642,7 +642,7 @@
       v-model="scanDrawerVisible"
       :title="t('扫描')"
       direction="rtl"
-      size="50%"
+      size="650px"
       destroy-on-close
       class="scan-drawer"
     >
@@ -666,10 +666,10 @@
             <el-empty :description="t('暂无发现设备')" />
           </template>
           <el-table-column type="index" :label="t('序号')" width="60" align="center" />
-          <el-table-column prop="ip" label="IP" min-width="120" show-overflow-tooltip />
+          <el-table-column prop="ip" label="IP" width="110" show-overflow-tooltip />
           <el-table-column prop="port" :label="t('端口')" width="80" align="center" />
-          <el-table-column prop="deviceName" :label="t('设备名称')" min-width="140" show-overflow-tooltip />
-          <el-table-column :label="t('匹配状态')" width="100" align="center">
+          <!-- <el-table-column prop="deviceName" :label="t('设备名称')" min-width="140" show-overflow-tooltip /> -->
+          <el-table-column :label="t('匹配状态')" min-width="140" align="center">
             <template #default="{ row }">
               <Icon
                 v-if="row.matchStatus === 'MATCHED'"
@@ -695,12 +695,12 @@
               <span v-else style="color: #909399">{{ t('待匹配') }}</span>
             </template>
           </el-table-column>
-          <el-table-column :label="t('操作')" width="80" align="center">
+          <el-table-column :label="t('操作')" align="center">
             <template #default="{ row }">
               <el-button
+                :icon="Plus"
                 v-if="row.matchStatus === 'MATCHED' && !row.bound"
                 type="primary"
-                link
                 @click="handleBindDevice(row)"
               >
                 {{ t('添加') }}
@@ -727,7 +727,7 @@
       v-model="credentialDrawerVisible"
       :title="t('账号配置')"
       direction="rtl"
-      size="50%"
+      size="600"
       destroy-on-close
       :append-to-body="true"
       class="credential-drawer"
@@ -740,7 +740,7 @@
                 v-model.trim="credentialSearchForm.username"
                 :placeholder="t('用户名')"
                 clearable
-                style="width: 150px"
+                style="width: 100px"
               />
             </el-form-item>
             <el-form-item>
@@ -748,7 +748,7 @@
                 v-model.trim="credentialSearchForm.password"
                 :placeholder="t('密码')"
                 clearable
-                style="width: 150px"
+                style="width: 100px"
               />
             </el-form-item>
             <el-form-item>
@@ -848,6 +848,7 @@ import { onMounted, watch } from 'vue'
 import { Icon } from '@iconify/vue'
 import { useI18n } from 'vue-i18n'
 import { formatTime } from '@/utils/dayjs'
+import { Plus } from '@element-plus/icons-vue'
 import CodeEditor from '@/components/CodeEditor.vue'
 import {
   formatStatus,
@@ -942,7 +943,7 @@ const {
   handleTriggerMatch,
   handleRematch,
   handleBindDevice
-} = useScanDevices()
+} = useScanDevices({ onRefresh: getList })
 
 // ==================== 凭证管理 ====================
 const {
@@ -1315,7 +1316,7 @@ onMounted(() => {
 .scan-drawer-content {
   flex: 1;
   overflow-y: auto;
-  padding: 16px;
+  padding: 0 16px;
 }
 
 .scan-toolbar {
@@ -1333,7 +1334,7 @@ onMounted(() => {
 }
 
 .credential-content {
-  padding: 16px;
+  padding: 0 16px;
 }
 
 .credential-toolbar {