|
@@ -663,7 +663,7 @@
|
|
|
<el-table-column prop="ip" label="IP" min-width="120" show-overflow-tooltip />
|
|
<el-table-column prop="ip" label="IP" min-width="120" show-overflow-tooltip />
|
|
|
<el-table-column prop="port" :label="t('端口')" width="80" align="center" />
|
|
<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 prop="deviceName" :label="t('设备名称')" min-width="140" show-overflow-tooltip />
|
|
|
- <el-table-column v-if="scanMatched" :label="t('匹配状态')" width="100" align="center">
|
|
|
|
|
|
|
+ <el-table-column :label="t('匹配状态')" width="100" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<Icon
|
|
<Icon
|
|
|
v-if="row.matchStatus === 'MATCHED'"
|
|
v-if="row.matchStatus === 'MATCHED'"
|
|
@@ -689,7 +689,7 @@
|
|
|
<span v-else style="color: #909399">{{ t('待匹配') }}</span>
|
|
<span v-else style="color: #909399">{{ t('待匹配') }}</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column v-if="scanMatched" :label="t('操作')" width="80" align="center">
|
|
|
|
|
|
|
+ <el-table-column :label="t('操作')" width="80" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-button
|
|
<el-button
|
|
|
v-if="row.matchStatus === 'MATCHED' && !row.bound"
|
|
v-if="row.matchStatus === 'MATCHED' && !row.bound"
|
|
@@ -838,1039 +838,142 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, onMounted, computed, watch } from 'vue'
|
|
|
|
|
-// Element Plus icons removed - using Iconify instead
|
|
|
|
|
-import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
|
+import { onMounted, watch } from 'vue'
|
|
|
import { Icon } from '@iconify/vue'
|
|
import { Icon } from '@iconify/vue'
|
|
|
-import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
import { formatTime } from '@/utils/dayjs'
|
|
import { formatTime } from '@/utils/dayjs'
|
|
|
-import { useRouter } from 'vue-router'
|
|
|
|
|
-import { listLssNodes, deleteLssNode, setLssNodeEnabled, updateLssNode } from '@/api/lss'
|
|
|
|
|
-import { adminListCameras, adminAddCamera, adminUpdateCamera, adminDeleteCamera, adminGetCamera } from '@/api/camera'
|
|
|
|
|
-import { listCameraVendors } from '@/api/camera-vendor'
|
|
|
|
|
-import {
|
|
|
|
|
- scanDevices,
|
|
|
|
|
- getDiscoveredDevices,
|
|
|
|
|
- triggerMatch,
|
|
|
|
|
- bindDevice,
|
|
|
|
|
- getCredentials,
|
|
|
|
|
- addCredential,
|
|
|
|
|
- updateCredential,
|
|
|
|
|
- deleteCredential
|
|
|
|
|
-} from '@/api/camera-scan'
|
|
|
|
|
import CodeEditor from '@/components/CodeEditor.vue'
|
|
import CodeEditor from '@/components/CodeEditor.vue'
|
|
|
-import type {
|
|
|
|
|
- LssNodeDTO,
|
|
|
|
|
- LssNodeStatus,
|
|
|
|
|
- LssNodeListRequest,
|
|
|
|
|
- LssHeartbeatStatus,
|
|
|
|
|
- CameraInfoDTO,
|
|
|
|
|
- CameraAddRequest,
|
|
|
|
|
- CameraUpdateRequest,
|
|
|
|
|
- CameraVendorDTO,
|
|
|
|
|
- IAbly,
|
|
|
|
|
- CameraHeartbeatStatus,
|
|
|
|
|
- DiscoveredCameraDTO,
|
|
|
|
|
- CameraCredentialDTO
|
|
|
|
|
-} from '@/types'
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ formatStatus,
|
|
|
|
|
+ getStatusTagType,
|
|
|
|
|
+ formatCameraStatus,
|
|
|
|
|
+ formatHeartbeat,
|
|
|
|
|
+ getHeartbeatClass,
|
|
|
|
|
+ formatBrand
|
|
|
|
|
+} from './composables/useFormatters'
|
|
|
|
|
+import { useLssList } from './composables/useLssList'
|
|
|
|
|
+import { useCameraList } from './composables/useCameraList'
|
|
|
|
|
+import { useScanDevices } from './composables/useScanDevices'
|
|
|
|
|
+import { useCredentials } from './composables/useCredentials'
|
|
|
|
|
|
|
|
const { t } = useI18n({ useScope: 'global' })
|
|
const { t } = useI18n({ useScope: 'global' })
|
|
|
|
|
|
|
|
-const router = useRouter()
|
|
|
|
|
-
|
|
|
|
|
-// 格式化状态显示
|
|
|
|
|
-function formatStatus(status: LssNodeStatus | undefined): string {
|
|
|
|
|
- switch (status) {
|
|
|
|
|
- case 'active':
|
|
|
|
|
- return '在线'
|
|
|
|
|
- case 'hold':
|
|
|
|
|
- return '离线'
|
|
|
|
|
- case 'dead':
|
|
|
|
|
- return '离线'
|
|
|
|
|
- default:
|
|
|
|
|
- return '离线'
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 获取状态标签类型
|
|
|
|
|
-function getStatusTagType(status: LssNodeStatus | undefined): 'success' | 'danger' | 'warning' | 'info' {
|
|
|
|
|
- switch (status) {
|
|
|
|
|
- case 'active':
|
|
|
|
|
- return 'success'
|
|
|
|
|
- case 'hold':
|
|
|
|
|
- return 'danger'
|
|
|
|
|
- case 'dead':
|
|
|
|
|
- return 'warning'
|
|
|
|
|
- default:
|
|
|
|
|
- return 'info'
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 格式化摄像头状态
|
|
|
|
|
-function formatCameraStatus(row: CameraInfoDTO): string {
|
|
|
|
|
- if (row.status === 'active') {
|
|
|
|
|
- // 大约5秒钟
|
|
|
|
|
- return `active [${formatTime(row.updatedAt)}]`
|
|
|
|
|
- }
|
|
|
|
|
- if (row.status === 'hold') {
|
|
|
|
|
- // 大约5分钟没有返回
|
|
|
|
|
- return `hold [${formatTime(row.updatedAt)}]`
|
|
|
|
|
- }
|
|
|
|
|
- // 大约10分钟没有返回
|
|
|
|
|
- return `dead (离线)`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 当前激活的摄像头 ID
|
|
|
|
|
-const activeCameraId = ref<number | null>(null)
|
|
|
|
|
-
|
|
|
|
|
-async function handleViewCamera(row: CameraInfoDTO) {
|
|
|
|
|
- // 如果没有 streamSn,显示提示对话框
|
|
|
|
|
- if (!row.streamSn) {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(t('请先新增 Live Stream,才能进行后续操作。'), t('尚未建立 Live Stream'), {
|
|
|
|
|
- confirmButtonText: t('新增 Live Stream'),
|
|
|
|
|
- cancelButtonText: t('取消'),
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- center: true,
|
|
|
|
|
- customClass: 'live-stream-dialog',
|
|
|
|
|
- distinguishCancelAndClose: true
|
|
|
|
|
- })
|
|
|
|
|
- router.push(`/live-stream-manage/list?cameraId=${row.cameraId}&lssId=${row.lssId}&action=create`)
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 用户点击了取消,不做任何操作
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- // 有 streamSn,正常跳转
|
|
|
|
|
- router.push(`/live-stream-manage/list?cameraId=${row.cameraId}`)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 格式化品牌
|
|
|
|
|
-function formatBrand(brand: string | undefined): string {
|
|
|
|
|
- const brandMap: Record<string, string> = {
|
|
|
|
|
- hikvision: '海康威视',
|
|
|
|
|
- dahua: '大华',
|
|
|
|
|
- uniview: '宇视',
|
|
|
|
|
- other: '其他'
|
|
|
|
|
- }
|
|
|
|
|
- return brand ? brandMap[brand] || brand.toUpperCase() : '-'
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 验证 JSON 格式
|
|
|
|
|
-function isValidJson(str: string): boolean {
|
|
|
|
|
- if (!str || !str.trim()) return true // 空值视为有效
|
|
|
|
|
- try {
|
|
|
|
|
- JSON.parse(str)
|
|
|
|
|
- return true
|
|
|
|
|
- } catch {
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 格式化心跳状态
|
|
|
|
|
-function formatHeartbeat(lss: LssNodeDTO | null | undefined): string {
|
|
|
|
|
- if (!lss) return '-'
|
|
|
|
|
- const status = lss.heartbeat || (lss.status === 'active' ? 'active' : lss.status === 'hold' ? 'hold' : 'dead')
|
|
|
|
|
- const time = lss.heartbeatTime || lss.updatedAt
|
|
|
|
|
- if (status === 'active') {
|
|
|
|
|
- return `active [${formatTime(time)}]`
|
|
|
|
|
- }
|
|
|
|
|
- if (status === 'hold') {
|
|
|
|
|
- return `hold [${formatTime(time)}]`
|
|
|
|
|
- }
|
|
|
|
|
- return `dead (离线)`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 获取心跳状态样式类
|
|
|
|
|
-function getHeartbeatClass(status: LssHeartbeatStatus | undefined): string {
|
|
|
|
|
- switch (status) {
|
|
|
|
|
- case 'active':
|
|
|
|
|
- return 'status-active'
|
|
|
|
|
- case 'hold':
|
|
|
|
|
- return 'status-hold'
|
|
|
|
|
- case 'dead':
|
|
|
|
|
- default:
|
|
|
|
|
- return 'status-dead'
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 查看参数配置
|
|
|
|
|
-function handleViewConfig(row: CameraInfoDTO) {
|
|
|
|
|
- paramsCamera.value = row
|
|
|
|
|
- paramsDialogType.value = 'config'
|
|
|
|
|
- paramsDialogTitle.value = `参数配置 - ${row.cameraName}`
|
|
|
|
|
- paramsContent.value = row.paramConfig || ''
|
|
|
|
|
- paramsDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 查看运行参数
|
|
|
|
|
-function handleViewRunParams(row: CameraInfoDTO) {
|
|
|
|
|
- paramsCamera.value = row
|
|
|
|
|
- paramsDialogType.value = 'run'
|
|
|
|
|
- paramsDialogTitle.value = `运行参数 - ${row.cameraName}`
|
|
|
|
|
- paramsContent.value = row.runtimeParams || ''
|
|
|
|
|
- paramsDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 保存参数配置/运行参数
|
|
|
|
|
-async function handleSaveParams() {
|
|
|
|
|
- if (!paramsCamera.value) return
|
|
|
|
|
-
|
|
|
|
|
- paramsSubmitting.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const data: CameraUpdateRequest = {
|
|
|
|
|
- id: paramsCamera.value.id
|
|
|
|
|
- }
|
|
|
|
|
- if (paramsDialogType.value === 'config') {
|
|
|
|
|
- data.paramConfig = paramsContent.value
|
|
|
|
|
- } else {
|
|
|
|
|
- data.runtimeParams = paramsContent.value
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const res = await adminUpdateCamera(data)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('保存成功')
|
|
|
|
|
- paramsDialogVisible.value = false
|
|
|
|
|
- // 更新本地数据
|
|
|
|
|
- if (paramsDialogType.value === 'config') {
|
|
|
|
|
- paramsCamera.value.configParams = paramsContent.value
|
|
|
|
|
- } else {
|
|
|
|
|
- paramsCamera.value.runParams = paramsContent.value
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '保存失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('保存参数失败', error)
|
|
|
|
|
- ElMessage.error('保存失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- paramsSubmitting.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const loading = ref(false)
|
|
|
|
|
-const lssList = ref<(LssNodeDTO & { _switching?: boolean })[]>([])
|
|
|
|
|
-const tableRef = ref()
|
|
|
|
|
-
|
|
|
|
|
-// 抽屉状态
|
|
|
|
|
-const detailDrawerVisible = ref(false)
|
|
|
|
|
-const currentLss = ref<LssNodeDTO | null>(null)
|
|
|
|
|
-
|
|
|
|
|
-// LSS 编辑抽屉状态
|
|
|
|
|
-const lssEditDrawerVisible = ref(false)
|
|
|
|
|
-const lssUpdating = ref(false)
|
|
|
|
|
-const editActiveTab = ref('detail')
|
|
|
|
|
-const lssEditFormRef = ref<FormInstance>()
|
|
|
|
|
-const lssEditForm = reactive({
|
|
|
|
|
- lssName: '',
|
|
|
|
|
- address: '',
|
|
|
|
|
- ip: '',
|
|
|
|
|
- ably: ''
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 根据当前 tab 计算抽屉宽度
|
|
|
|
|
-const editDrawerSize = computed(() => {
|
|
|
|
|
- return editActiveTab.value === 'detail' ? '800px' : '80%'
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 设备列表抽屉状态
|
|
|
|
|
-const cameraDrawerVisible = ref(false)
|
|
|
|
|
-const cameraLoading = ref(false)
|
|
|
|
|
-const deviceActiveTab = ref('camera')
|
|
|
|
|
-const cameraList = ref<CameraInfoDTO[]>([])
|
|
|
|
|
-const cameraVendorList = ref<CameraVendorDTO[]>([])
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头分页
|
|
|
|
|
-const cameraCurrentPage = ref(1)
|
|
|
|
|
-const cameraPageSize = ref(15)
|
|
|
|
|
-const cameraTotal = ref(0)
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头表格高度 (视口高度 - 顶部导航 - tabs - 搜索栏 - 分页 - padding)
|
|
|
|
|
-const cameraTableHeight = computed(() => {
|
|
|
|
|
- return 'calc(100vh - 238px)'
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头搜索表单
|
|
|
|
|
-const cameraSearchForm = reactive({
|
|
|
|
|
- cameraId: '',
|
|
|
|
|
- cameraName: '',
|
|
|
|
|
- status: '' as CameraHeartbeatStatus | ''
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头编辑弹窗状态
|
|
|
|
|
-const cameraDialogVisible = ref(false)
|
|
|
|
|
-const cameraFormRef = ref<FormInstance>()
|
|
|
|
|
-const isEditCamera = ref(false)
|
|
|
|
|
-const cameraSubmitting = ref(false)
|
|
|
|
|
-const currentCamera = ref<CameraInfoDTO | null>(null)
|
|
|
|
|
-
|
|
|
|
|
-// 参数配置/运行参数弹窗状态
|
|
|
|
|
-const paramsDialogVisible = ref(false)
|
|
|
|
|
-const paramsDialogTitle = ref('')
|
|
|
|
|
-const paramsDialogType = ref<'config' | 'run'>('config')
|
|
|
|
|
-const paramsContent = ref('')
|
|
|
|
|
-const paramsSubmitting = ref(false)
|
|
|
|
|
-const paramsCamera = ref<CameraInfoDTO | null>(null)
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头表单
|
|
|
|
|
-const cameraForm = reactive({
|
|
|
|
|
- selectedVendorId: null as number | null,
|
|
|
|
|
- cameraId: '',
|
|
|
|
|
- cameraName: '',
|
|
|
|
|
- vendorName: '',
|
|
|
|
|
- model: '',
|
|
|
|
|
- ip: '',
|
|
|
|
|
- port: 80,
|
|
|
|
|
- username: '',
|
|
|
|
|
- password: '',
|
|
|
|
|
- brand: '',
|
|
|
|
|
- capability: 'switch_only' as 'switch_only' | 'ptz_enabled',
|
|
|
|
|
- rtspUrl: '',
|
|
|
|
|
- channelNo: '',
|
|
|
|
|
- remark: '',
|
|
|
|
|
- enabled: true,
|
|
|
|
|
- paramConfig: '',
|
|
|
|
|
- runtimeParams: '',
|
|
|
|
|
- createdAt: '',
|
|
|
|
|
- updatedAt: ''
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 摄像头表单验证规则(动态)
|
|
|
|
|
-const cameraRules = computed<FormRules>(() => ({
|
|
|
|
|
- cameraId: [{ required: true, message: t('请输入设备ID'), trigger: 'blur' }]
|
|
|
|
|
-}))
|
|
|
|
|
-
|
|
|
|
|
-// 排序状态
|
|
|
|
|
-const sortState = reactive<{
|
|
|
|
|
- sortBy: string
|
|
|
|
|
- sortDir: 'ASC' | 'DESC' | undefined
|
|
|
|
|
-}>({
|
|
|
|
|
- sortBy: '',
|
|
|
|
|
- sortDir: undefined
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 搜索表单
|
|
|
|
|
-const searchForm = reactive<{
|
|
|
|
|
- lssId: string
|
|
|
|
|
- lssName: string
|
|
|
|
|
- status: LssNodeStatus | ''
|
|
|
|
|
-}>({
|
|
|
|
|
- lssId: '',
|
|
|
|
|
- lssName: '',
|
|
|
|
|
- status: ''
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 分页相关
|
|
|
|
|
-const currentPage = ref(1)
|
|
|
|
|
-const pageSize = ref(15)
|
|
|
|
|
-const total = ref(0)
|
|
|
|
|
-
|
|
|
|
|
-async function getList() {
|
|
|
|
|
- loading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const params: LssNodeListRequest = {
|
|
|
|
|
- page: currentPage.value,
|
|
|
|
|
- size: pageSize.value
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (searchForm.lssId) {
|
|
|
|
|
- params.lssId = searchForm.lssId
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (searchForm.lssName) {
|
|
|
|
|
- params.lssName = searchForm.lssName
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (searchForm.status) {
|
|
|
|
|
- params.status = searchForm.status
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (sortState.sortBy) {
|
|
|
|
|
- params.sortBy = sortState.sortBy
|
|
|
|
|
- params.sortDir = sortState.sortDir
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const res = await listLssNodes(params)
|
|
|
|
|
- if (res.success && res.data) {
|
|
|
|
|
- lssList.value = res.data.list
|
|
|
|
|
- total.value = res.data.total || 0
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '获取列表失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取 LSS 列表失败', error)
|
|
|
|
|
- ElMessage.error('获取列表失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleSearch() {
|
|
|
|
|
- currentPage.value = 1
|
|
|
|
|
- getList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleReset() {
|
|
|
|
|
- searchForm.lssId = ''
|
|
|
|
|
- searchForm.lssName = ''
|
|
|
|
|
- searchForm.status = ''
|
|
|
|
|
- sortState.sortBy = ''
|
|
|
|
|
- sortState.sortDir = undefined
|
|
|
|
|
- currentPage.value = 1
|
|
|
|
|
- getList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleSortChange({ prop, order }: { prop: string; order: 'ascending' | 'descending' | null }) {
|
|
|
|
|
- sortState.sortBy = prop || ''
|
|
|
|
|
- sortState.sortDir = order === 'ascending' ? 'ASC' : order === 'descending' ? 'DESC' : undefined
|
|
|
|
|
- getList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleSizeChange(val: number) {
|
|
|
|
|
- pageSize.value = val
|
|
|
|
|
- currentPage.value = 1
|
|
|
|
|
- getList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleCurrentChange(val: number) {
|
|
|
|
|
- currentPage.value = val
|
|
|
|
|
- getList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleViewDetail(row: LssNodeDTO) {
|
|
|
|
|
- currentLss.value = row
|
|
|
|
|
- detailDrawerVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 扫描设备相关 ====================
|
|
|
|
|
-const scanDrawerVisible = ref(false)
|
|
|
|
|
-const scanLoading = ref(false)
|
|
|
|
|
-const matchLoading = ref(false)
|
|
|
|
|
-const scanMatched = ref(false)
|
|
|
|
|
-const discoveredDevices = ref<DiscoveredCameraDTO[]>([])
|
|
|
|
|
-const scanLssId = ref('')
|
|
|
|
|
-
|
|
|
|
|
-async function handleScanDevices(row: LssNodeDTO) {
|
|
|
|
|
- scanLssId.value = row.lssId
|
|
|
|
|
- scanMatched.value = false
|
|
|
|
|
-
|
|
|
|
|
- // 先尝试获取已有的发现设备
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await getDiscoveredDevices(row.lssId)
|
|
|
|
|
- if (res.success && res.data && res.data.length > 0) {
|
|
|
|
|
- // 已有扫描结果,直接打开抽屉
|
|
|
|
|
- discoveredDevices.value = res.data
|
|
|
|
|
- scanDrawerVisible.value = true
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取发现设备失败', error)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 没有扫描结果,弹确认框询问是否开启扫描
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(t('当前 LSS 节点尚未扫描设备,是否开启扫描?'), t('扫描设备'), {
|
|
|
|
|
- confirmButtonText: t('开始扫描'),
|
|
|
|
|
- cancelButtonText: t('取消'),
|
|
|
|
|
- type: 'info'
|
|
|
|
|
- })
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 用户取消
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 用户确认,触发扫描
|
|
|
|
|
- scanDrawerVisible.value = true
|
|
|
|
|
- scanLoading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await scanDevices(row.lssId)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(t('扫描完成'))
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('扫描失败'))
|
|
|
|
|
- }
|
|
|
|
|
- // 刷新列表
|
|
|
|
|
- await loadDiscoveredDevices()
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('扫描失败', error)
|
|
|
|
|
- ElMessage.error(t('扫描失败'))
|
|
|
|
|
- } finally {
|
|
|
|
|
- scanLoading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function loadDiscoveredDevices() {
|
|
|
|
|
- if (!scanLssId.value) return
|
|
|
|
|
- scanLoading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await getDiscoveredDevices(scanLssId.value)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- discoveredDevices.value = res.data || []
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('获取发现设备失败'))
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取发现设备失败', error)
|
|
|
|
|
- } finally {
|
|
|
|
|
- scanLoading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleTriggerMatch() {
|
|
|
|
|
- if (!scanLssId.value) return
|
|
|
|
|
- matchLoading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await triggerMatch(scanLssId.value)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(t('匹配完成'))
|
|
|
|
|
- scanMatched.value = true
|
|
|
|
|
- await loadDiscoveredDevices()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('匹配失败'))
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('匹配失败', error)
|
|
|
|
|
- ElMessage.error(t('匹配失败'))
|
|
|
|
|
- } finally {
|
|
|
|
|
- matchLoading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleRematch() {
|
|
|
|
|
- scanMatched.value = false
|
|
|
|
|
- await handleTriggerMatch()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleBindDevice(row: DiscoveredCameraDTO) {
|
|
|
|
|
- // 占位:后续接入真实 bind 接口
|
|
|
|
|
- console.log('bind device', row)
|
|
|
|
|
- ElMessage.info('绑定功能开发中')
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 凭证管理相关 ====================
|
|
|
|
|
-const credentialDrawerVisible = ref(false)
|
|
|
|
|
-const credentialLoading = ref(false)
|
|
|
|
|
-const credentials = ref<CameraCredentialDTO[]>([])
|
|
|
|
|
-const credentialSearchForm = reactive({ username: '', password: '' })
|
|
|
|
|
-
|
|
|
|
|
-const filteredCredentials = computed(() => {
|
|
|
|
|
- let list = credentials.value
|
|
|
|
|
- if (credentialSearchForm.username) {
|
|
|
|
|
- list = list.filter((c) => c.username.includes(credentialSearchForm.username))
|
|
|
|
|
- }
|
|
|
|
|
- if (credentialSearchForm.password) {
|
|
|
|
|
- list = list.filter((c) => c.password.includes(credentialSearchForm.password))
|
|
|
|
|
- }
|
|
|
|
|
- return list
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-async function loadCredentials() {
|
|
|
|
|
- credentialLoading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await getCredentials()
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- credentials.value = res.data || []
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('获取凭证列表失败'))
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取凭证列表失败', error)
|
|
|
|
|
- } finally {
|
|
|
|
|
- credentialLoading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleCredentialReset() {
|
|
|
|
|
- credentialSearchForm.username = ''
|
|
|
|
|
- credentialSearchForm.password = ''
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 凭证编辑
|
|
|
|
|
-const credentialDialogVisible = ref(false)
|
|
|
|
|
-const isEditCredential = ref(false)
|
|
|
|
|
-const credentialSubmitting = ref(false)
|
|
|
|
|
-const currentCredential = ref<CameraCredentialDTO | null>(null)
|
|
|
|
|
-const credentialFormRef = ref<FormInstance>()
|
|
|
|
|
-const credentialForm = reactive({
|
|
|
|
|
- name: '',
|
|
|
|
|
- username: '',
|
|
|
|
|
- password: '',
|
|
|
|
|
- vendor: '',
|
|
|
|
|
- priority: 0,
|
|
|
|
|
- enabled: true,
|
|
|
|
|
- remark: ''
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const credentialRules = computed<FormRules>(() => ({
|
|
|
|
|
- name: [{ required: true, message: t('请输入凭证名称'), trigger: 'blur' }],
|
|
|
|
|
- username: [{ required: true, message: t('请输入用户名'), trigger: 'blur' }],
|
|
|
|
|
- password: [{ required: true, message: t('请输入密码'), trigger: 'blur' }]
|
|
|
|
|
-}))
|
|
|
|
|
-
|
|
|
|
|
-function resetCredentialForm() {
|
|
|
|
|
- credentialForm.name = ''
|
|
|
|
|
- credentialForm.username = ''
|
|
|
|
|
- credentialForm.password = ''
|
|
|
|
|
- credentialForm.vendor = ''
|
|
|
|
|
- credentialForm.priority = 0
|
|
|
|
|
- credentialForm.enabled = true
|
|
|
|
|
- credentialForm.remark = ''
|
|
|
|
|
- credentialFormRef.value?.clearValidate()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleAddCredential() {
|
|
|
|
|
- isEditCredential.value = false
|
|
|
|
|
- currentCredential.value = null
|
|
|
|
|
- resetCredentialForm()
|
|
|
|
|
- credentialDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleEditCredential(row: CameraCredentialDTO) {
|
|
|
|
|
- isEditCredential.value = true
|
|
|
|
|
- currentCredential.value = row
|
|
|
|
|
- credentialForm.name = row.name
|
|
|
|
|
- credentialForm.username = row.username
|
|
|
|
|
- credentialForm.password = row.password
|
|
|
|
|
- credentialForm.vendor = row.vendor || ''
|
|
|
|
|
- credentialForm.priority = row.priority || 0
|
|
|
|
|
- credentialForm.enabled = row.enabled
|
|
|
|
|
- credentialForm.remark = row.remark || ''
|
|
|
|
|
- credentialDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleSubmitCredential() {
|
|
|
|
|
- if (!credentialFormRef.value) return
|
|
|
|
|
- await credentialFormRef.value.validate(async (valid) => {
|
|
|
|
|
- if (!valid) return
|
|
|
|
|
- credentialSubmitting.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- if (isEditCredential.value && currentCredential.value) {
|
|
|
|
|
- const res = await updateCredential({
|
|
|
|
|
- id: currentCredential.value.id,
|
|
|
|
|
- ...credentialForm
|
|
|
|
|
- })
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(t('更新成功'))
|
|
|
|
|
- credentialDialogVisible.value = false
|
|
|
|
|
- loadCredentials()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('更新失败'))
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- const res = await addCredential({ ...credentialForm })
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(t('新增成功'))
|
|
|
|
|
- credentialDialogVisible.value = false
|
|
|
|
|
- loadCredentials()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('新增失败'))
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('保存凭证失败', error)
|
|
|
|
|
- ElMessage.error(t('操作失败'))
|
|
|
|
|
- } finally {
|
|
|
|
|
- credentialSubmitting.value = false
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleDeleteCredential(row: CameraCredentialDTO) {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(t('确定要删除该凭证吗?'), t('提示'), { type: 'warning' })
|
|
|
|
|
- const res = await deleteCredential(row.id)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(t('删除凭证成功'))
|
|
|
|
|
- loadCredentials()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || t('删除失败'))
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('删除凭证失败', error)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 打开凭证抽屉时加载数据
|
|
|
|
|
-watch(credentialDrawerVisible, (val) => {
|
|
|
|
|
- if (val) loadCredentials()
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-function handleEdit(row: LssNodeDTO, tab: 'detail' | 'camera' | 'pusher') {
|
|
|
|
|
|
|
+// ==================== LSS 列表 ====================
|
|
|
|
|
+const {
|
|
|
|
|
+ loading,
|
|
|
|
|
+ lssList,
|
|
|
|
|
+ tableRef,
|
|
|
|
|
+ detailDrawerVisible,
|
|
|
|
|
+ currentLss,
|
|
|
|
|
+ lssEditDrawerVisible,
|
|
|
|
|
+ lssUpdating,
|
|
|
|
|
+ editActiveTab,
|
|
|
|
|
+ lssEditFormRef,
|
|
|
|
|
+ lssEditForm,
|
|
|
|
|
+ editDrawerSize,
|
|
|
|
|
+ searchForm,
|
|
|
|
|
+ currentPage,
|
|
|
|
|
+ pageSize,
|
|
|
|
|
+ total,
|
|
|
|
|
+ getList,
|
|
|
|
|
+ handleSearch,
|
|
|
|
|
+ handleReset,
|
|
|
|
|
+ handleSortChange,
|
|
|
|
|
+ handleSizeChange,
|
|
|
|
|
+ handleCurrentChange,
|
|
|
|
|
+ handleUpdateLss,
|
|
|
|
|
+ handleDelete
|
|
|
|
|
+} = useLssList()
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 摄像头列表 ====================
|
|
|
|
|
+const {
|
|
|
|
|
+ cameraDrawerVisible,
|
|
|
|
|
+ cameraLoading,
|
|
|
|
|
+ deviceActiveTab,
|
|
|
|
|
+ cameraList,
|
|
|
|
|
+ cameraCurrentPage,
|
|
|
|
|
+ cameraPageSize,
|
|
|
|
|
+ cameraTotal,
|
|
|
|
|
+ cameraTableHeight,
|
|
|
|
|
+ cameraSearchForm,
|
|
|
|
|
+ cameraDialogVisible,
|
|
|
|
|
+ cameraFormRef,
|
|
|
|
|
+ isEditCamera,
|
|
|
|
|
+ cameraSubmitting,
|
|
|
|
|
+ cameraForm,
|
|
|
|
|
+ cameraRules,
|
|
|
|
|
+ paramsDialogVisible,
|
|
|
|
|
+ paramsDialogTitle,
|
|
|
|
|
+ paramsDialogType,
|
|
|
|
|
+ paramsContent,
|
|
|
|
|
+ paramsSubmitting,
|
|
|
|
|
+ loadCameraList,
|
|
|
|
|
+ handleCameraSearch,
|
|
|
|
|
+ handleCameraReset,
|
|
|
|
|
+ handleCameraSizeChange,
|
|
|
|
|
+ handleCameraPageChange,
|
|
|
|
|
+ handleVendorSelect,
|
|
|
|
|
+ generateCameraId,
|
|
|
|
|
+ handleAddCamera,
|
|
|
|
|
+ handleEditCamera,
|
|
|
|
|
+ handleSubmitCamera,
|
|
|
|
|
+ handleDeleteCamera,
|
|
|
|
|
+ handleViewCamera,
|
|
|
|
|
+ handleViewConfig,
|
|
|
|
|
+ handleViewRunParams,
|
|
|
|
|
+ handleSaveParams,
|
|
|
|
|
+ resetCameraSearch
|
|
|
|
|
+} = useCameraList(currentLss)
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 扫描设备 ====================
|
|
|
|
|
+const {
|
|
|
|
|
+ scanDrawerVisible,
|
|
|
|
|
+ scanLoading,
|
|
|
|
|
+ matchLoading,
|
|
|
|
|
+ scanMatched,
|
|
|
|
|
+ discoveredDevices,
|
|
|
|
|
+ handleScanDevices,
|
|
|
|
|
+ handleTriggerMatch,
|
|
|
|
|
+ handleRematch,
|
|
|
|
|
+ handleBindDevice
|
|
|
|
|
+} = useScanDevices()
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 凭证管理 ====================
|
|
|
|
|
+const {
|
|
|
|
|
+ credentialDrawerVisible,
|
|
|
|
|
+ credentialLoading,
|
|
|
|
|
+ credentialSearchForm,
|
|
|
|
|
+ filteredCredentials,
|
|
|
|
|
+ credentialDialogVisible,
|
|
|
|
|
+ isEditCredential,
|
|
|
|
|
+ credentialSubmitting,
|
|
|
|
|
+ credentialFormRef,
|
|
|
|
|
+ credentialForm,
|
|
|
|
|
+ credentialRules,
|
|
|
|
|
+ loadCredentials,
|
|
|
|
|
+ handleCredentialReset,
|
|
|
|
|
+ handleAddCredential,
|
|
|
|
|
+ handleEditCredential,
|
|
|
|
|
+ handleSubmitCredential,
|
|
|
|
|
+ handleDeleteCredential
|
|
|
|
|
+} = useCredentials()
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 页面级编排 ====================
|
|
|
|
|
+function handleEdit(row: any, tab: 'detail' | 'camera' | 'pusher') {
|
|
|
currentLss.value = row
|
|
currentLss.value = row
|
|
|
lssEditForm.lssName = row.lssName || ''
|
|
lssEditForm.lssName = row.lssName || ''
|
|
|
lssEditForm.address = row.address || ''
|
|
lssEditForm.address = row.address || ''
|
|
|
lssEditForm.ably = JSON.stringify(row.ably)
|
|
lssEditForm.ably = JSON.stringify(row.ably)
|
|
|
editActiveTab.value = tab
|
|
editActiveTab.value = tab
|
|
|
lssEditDrawerVisible.value = true
|
|
lssEditDrawerVisible.value = true
|
|
|
- // 每次打开抽屉都刷新摄像头列表
|
|
|
|
|
- cameraSearchForm.cameraId = ''
|
|
|
|
|
- cameraSearchForm.cameraName = ''
|
|
|
|
|
- cameraSearchForm.status = ''
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleCameraList(row: LssNodeDTO) {
|
|
|
|
|
- currentLss.value = row
|
|
|
|
|
- cameraSearchForm.cameraId = ''
|
|
|
|
|
- cameraSearchForm.cameraName = ''
|
|
|
|
|
- cameraSearchForm.status = ''
|
|
|
|
|
- deviceActiveTab.value = 'camera'
|
|
|
|
|
- cameraDrawerVisible.value = true
|
|
|
|
|
- await loadCameraList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleUpdateLss() {
|
|
|
|
|
- if (!currentLss.value) return
|
|
|
|
|
-
|
|
|
|
|
- lssUpdating.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await updateLssNode({
|
|
|
|
|
- lssId: currentLss.value.lssId,
|
|
|
|
|
- lssName: lssEditForm.lssName,
|
|
|
|
|
- address: lssEditForm.address,
|
|
|
|
|
- ablyInfo: lssEditForm.ably
|
|
|
|
|
- })
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('更新成功')
|
|
|
|
|
- lssEditDrawerVisible.value = false
|
|
|
|
|
- getList()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '更新失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('更新 LSS 失败', error)
|
|
|
|
|
- ElMessage.error('更新失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- lssUpdating.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function loadCameraList() {
|
|
|
|
|
- if (!currentLss.value) return
|
|
|
|
|
- cameraLoading.value = true
|
|
|
|
|
- cameraList.value = []
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const params: any = {
|
|
|
|
|
- lssId: currentLss.value.lssId,
|
|
|
|
|
- page: cameraCurrentPage.value,
|
|
|
|
|
- size: cameraPageSize.value
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (cameraSearchForm.cameraId) {
|
|
|
|
|
- params.cameraId = cameraSearchForm.cameraId
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (cameraSearchForm.cameraName) {
|
|
|
|
|
- params.cameraName = cameraSearchForm.cameraName
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (cameraSearchForm.status) {
|
|
|
|
|
- params.status = cameraSearchForm.status
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const res = await adminListCameras(params)
|
|
|
|
|
- if (res.success && res.data) {
|
|
|
|
|
- cameraList.value = res.data.list || []
|
|
|
|
|
- cameraTotal.value = res.data.total || 0
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '获取摄像头列表失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取摄像头列表失败', error)
|
|
|
|
|
- ElMessage.error('获取摄像头列表失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- cameraLoading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleCameraSearch() {
|
|
|
|
|
- cameraCurrentPage.value = 1
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleCameraReset() {
|
|
|
|
|
- cameraSearchForm.cameraId = ''
|
|
|
|
|
- cameraSearchForm.cameraName = ''
|
|
|
|
|
- cameraSearchForm.status = ''
|
|
|
|
|
- cameraCurrentPage.value = 1
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleCameraSizeChange(val: number) {
|
|
|
|
|
- cameraPageSize.value = val
|
|
|
|
|
- cameraCurrentPage.value = 1
|
|
|
|
|
|
|
+ resetCameraSearch()
|
|
|
loadCameraList()
|
|
loadCameraList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function handleCameraPageChange(val: number) {
|
|
|
|
|
- cameraCurrentPage.value = val
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function resetCameraForm() {
|
|
|
|
|
- cameraForm.selectedVendorId = null
|
|
|
|
|
- cameraForm.cameraId = ''
|
|
|
|
|
- cameraForm.cameraName = ''
|
|
|
|
|
- cameraForm.vendorName = ''
|
|
|
|
|
- cameraForm.model = ''
|
|
|
|
|
- cameraForm.ip = ''
|
|
|
|
|
- cameraForm.port = 80
|
|
|
|
|
- cameraForm.username = ''
|
|
|
|
|
- cameraForm.password = ''
|
|
|
|
|
- cameraForm.brand = ''
|
|
|
|
|
- cameraForm.capability = 'switch_only'
|
|
|
|
|
- cameraForm.rtspUrl = ''
|
|
|
|
|
- cameraForm.model = ''
|
|
|
|
|
- cameraForm.channelNo = ''
|
|
|
|
|
- cameraForm.remark = ''
|
|
|
|
|
- cameraForm.enabled = true
|
|
|
|
|
- cameraForm.paramConfig = ''
|
|
|
|
|
- cameraForm.runtimeParams = ''
|
|
|
|
|
- cameraForm.createdAt = ''
|
|
|
|
|
- cameraForm.updatedAt = ''
|
|
|
|
|
- cameraFormRef.value?.clearValidate()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function loadCameraVendorList() {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await listCameraVendors({ enabled: true })
|
|
|
|
|
- if (res.success && res.data) {
|
|
|
|
|
- cameraVendorList.value = res.data.list || []
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取厂商列表失败', error)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleVendorSelect(vendorId: number) {
|
|
|
|
|
- const vendor = cameraVendorList.value.find((v) => v.id === vendorId)
|
|
|
|
|
- if (vendor) {
|
|
|
|
|
- cameraForm.brand = vendor.code
|
|
|
|
|
- // 设置厂商默认端口
|
|
|
|
|
- if (vendor.defaultPort) {
|
|
|
|
|
- cameraForm.port = vendor.defaultPort
|
|
|
|
|
- }
|
|
|
|
|
- // 根据厂商设置默认能力
|
|
|
|
|
- cameraForm.capability = vendor.supportPtz ? 'ptz_enabled' : 'switch_only'
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function generateCameraId() {
|
|
|
|
|
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
|
|
|
- const prefix = 'CAM-'
|
|
|
|
|
- let id = ''
|
|
|
|
|
- for (let i = 0; i < 6; i++) {
|
|
|
|
|
- id += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
|
|
|
- }
|
|
|
|
|
- cameraForm.cameraId = prefix + id
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleAddCamera() {
|
|
|
|
|
- isEditCamera.value = false
|
|
|
|
|
- currentCamera.value = null
|
|
|
|
|
- resetCameraForm()
|
|
|
|
|
- await loadCameraVendorList()
|
|
|
|
|
- cameraDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleEditCamera(row: CameraInfoDTO) {
|
|
|
|
|
- isEditCamera.value = true
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- // 通过 API 获取摄像头详情
|
|
|
|
|
- const res = await adminGetCamera({ id: row.id })
|
|
|
|
|
- if (!res.success || !res.data) {
|
|
|
|
|
- ElMessage.error(res.errMessage || '获取摄像头详情失败')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const camera = res.data
|
|
|
|
|
- currentCamera.value = camera
|
|
|
|
|
- cameraForm.selectedVendorId = null
|
|
|
|
|
- cameraForm.cameraId = camera.cameraId
|
|
|
|
|
- cameraForm.cameraName = camera.cameraName
|
|
|
|
|
- cameraForm.vendorName = camera.vendorName || ''
|
|
|
|
|
- cameraForm.model = camera.model || ''
|
|
|
|
|
- cameraForm.ip = camera.ip
|
|
|
|
|
- cameraForm.port = camera.port || 80
|
|
|
|
|
- cameraForm.username = camera.username || ''
|
|
|
|
|
- cameraForm.password = ''
|
|
|
|
|
- cameraForm.brand = camera.brand || ''
|
|
|
|
|
- cameraForm.capability = camera.capability || 'switch_only'
|
|
|
|
|
- cameraForm.rtspUrl = camera.rtspUrl || ''
|
|
|
|
|
- cameraForm.model = camera.model || ''
|
|
|
|
|
- cameraForm.channelNo = camera.channelNo || ''
|
|
|
|
|
- cameraForm.remark = camera.remark || ''
|
|
|
|
|
- cameraForm.createdAt = camera.createdAt || ''
|
|
|
|
|
- cameraForm.updatedAt = camera.updatedAt || ''
|
|
|
|
|
- cameraForm.enabled = camera.enabled
|
|
|
|
|
- cameraForm.paramConfig = camera.paramConfig || ''
|
|
|
|
|
- cameraForm.runtimeParams = camera.runtimeParams || ''
|
|
|
|
|
-
|
|
|
|
|
- await loadCameraVendorList()
|
|
|
|
|
- cameraDialogVisible.value = true
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取摄像头详情失败', error)
|
|
|
|
|
- ElMessage.error('获取摄像头详情失败')
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleSubmitCamera() {
|
|
|
|
|
- if (!cameraFormRef.value) return
|
|
|
|
|
-
|
|
|
|
|
- await cameraFormRef.value.validate(async (valid) => {
|
|
|
|
|
- if (!valid) return
|
|
|
|
|
-
|
|
|
|
|
- // 验证 JSON 格式
|
|
|
|
|
- if (!isValidJson(cameraForm.paramConfig)) {
|
|
|
|
|
- ElMessage.error('参数配置格式错误,请输入有效的 JSON')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (!isValidJson(cameraForm.runtimeParams)) {
|
|
|
|
|
- ElMessage.error('设备运行参数格式错误,请输入有效的 JSON')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cameraSubmitting.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- if (isEditCamera.value) {
|
|
|
|
|
- // 编辑模式:更新摄像头信息
|
|
|
|
|
- if (!currentCamera.value) {
|
|
|
|
|
- ElMessage.error('摄像头信息错误')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- const data: CameraUpdateRequest = {
|
|
|
|
|
- id: currentCamera.value.id,
|
|
|
|
|
- cameraName: cameraForm.cameraName,
|
|
|
|
|
- vendorName: cameraForm.vendorName,
|
|
|
|
|
- model: cameraForm.model,
|
|
|
|
|
- port: cameraForm.port,
|
|
|
|
|
- username: cameraForm.username,
|
|
|
|
|
- brand: cameraForm.brand,
|
|
|
|
|
- capability: cameraForm.capability,
|
|
|
|
|
- lssId: currentLss.value?.lssId,
|
|
|
|
|
- rtspUrl: cameraForm.rtspUrl,
|
|
|
|
|
- channelNo: cameraForm.channelNo,
|
|
|
|
|
- remark: cameraForm.remark,
|
|
|
|
|
- enabled: cameraForm.enabled,
|
|
|
|
|
- paramConfig: cameraForm.paramConfig,
|
|
|
|
|
- runtimeParams: cameraForm.runtimeParams
|
|
|
|
|
- }
|
|
|
|
|
- if (cameraForm.password) {
|
|
|
|
|
- data.password = cameraForm.password
|
|
|
|
|
- }
|
|
|
|
|
- const res = await adminUpdateCamera(data)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('更新成功')
|
|
|
|
|
- cameraDialogVisible.value = false
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '更新失败')
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // 新增模式:创建摄像头并绑定到当前 LSS
|
|
|
|
|
- const data: CameraAddRequest = {
|
|
|
|
|
- cameraId: cameraForm.cameraId,
|
|
|
|
|
- cameraName: cameraForm.cameraName,
|
|
|
|
|
- vendorName: cameraForm.vendorName,
|
|
|
|
|
- model: cameraForm.model,
|
|
|
|
|
- paramConfig: cameraForm.paramConfig,
|
|
|
|
|
- runtimeParams: cameraForm.runtimeParams,
|
|
|
|
|
- lssId: currentLss.value?.lssId
|
|
|
|
|
- }
|
|
|
|
|
- const res = await adminAddCamera(data)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('添加成功')
|
|
|
|
|
- cameraDialogVisible.value = false
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '添加失败')
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('保存摄像头失败', error)
|
|
|
|
|
- ElMessage.error('操作失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- cameraSubmitting.value = false
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleDeleteCamera(row: CameraInfoDTO) {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(
|
|
|
|
|
- `你确定要删除这个设备吗?<br/>设备ID:${row.cameraId}<br/>设备名称:${row.cameraName}`,
|
|
|
|
|
- '提示',
|
|
|
|
|
- {
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- dangerouslyUseHTMLString: true
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- const res = await adminDeleteCamera({ id: row.id })
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('删除成功')
|
|
|
|
|
- loadCameraList()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '删除失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('删除摄像头失败', error)
|
|
|
|
|
- ElMessage.error('删除失败')
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleToggleEnabled(row: LssNodeDTO & { _switching?: boolean }, enabled: boolean) {
|
|
|
|
|
- row._switching = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await setLssNodeEnabled(row.lssId, enabled)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success(enabled ? '已启用' : '已禁用')
|
|
|
|
|
- } else {
|
|
|
|
|
- // 恢复原状态
|
|
|
|
|
- row.enabled = !enabled
|
|
|
|
|
- ElMessage.error(res.errMessage || '操作失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- row.enabled = !enabled
|
|
|
|
|
- console.error('切换启用状态失败', error)
|
|
|
|
|
- ElMessage.error('操作失败')
|
|
|
|
|
- } finally {
|
|
|
|
|
- row._switching = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleDelete(row: LssNodeDTO) {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(`确定要删除 LSS 节点 "${row.lssName}" 吗?`, '提示', {
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- })
|
|
|
|
|
- const res = await deleteLssNode(row.lssId)
|
|
|
|
|
- if (res.success) {
|
|
|
|
|
- ElMessage.success('删除成功')
|
|
|
|
|
- getList()
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(res.errMessage || '删除失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('删除失败', error)
|
|
|
|
|
- ElMessage.error('删除失败')
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// 监听 tab 切换,加载对应数据
|
|
// 监听 tab 切换,加载对应数据
|
|
|
watch(editActiveTab, (newTab) => {
|
|
watch(editActiveTab, (newTab) => {
|
|
|
if (newTab === 'camera' && currentLss.value) {
|
|
if (newTab === 'camera' && currentLss.value) {
|
|
|
- cameraSearchForm.cameraId = ''
|
|
|
|
|
- cameraSearchForm.cameraName = ''
|
|
|
|
|
- cameraSearchForm.status = ''
|
|
|
|
|
|
|
+ resetCameraSearch()
|
|
|
cameraCurrentPage.value = 1
|
|
cameraCurrentPage.value = 1
|
|
|
loadCameraList()
|
|
loadCameraList()
|
|
|
}
|
|
}
|