@@ -0,0 +1,80 @@
< template >
< BasicLayout >
< template # wrapper >
< el-card class = "box-card" >
< div class = "page-header" >
< div > < h3 > 视频接入 < / h3 > < p > 选择已登记的视频设备 , 发现或填写 ONVIF 地址 , 然后验证主 、 子码流 。 < / p > < / div >
< el-button v-permisaction = "['sense:admission:discover']" type="primary" plain :loading="discovering" @click="handleDiscover" > 发现设备 < / el -button >
< / div >
< el-alert title = "发现只使用服务端配置的获准网卡;手工地址也只能访问获准网段。" type = "info" :closable = "false" show -icon / >
< el-form ref = "probeFormRef" :model = "form" :rules = "rules" label -width = " 120px " class = "probe-form" >
< el-form-item label = "设备" prop = "deviceId" >
< el-select v-model = "form.deviceId" filterable placeholder="请选择已登记的视频设备" @change="selectDevice" >
< el -option v-for = "device in devices" :key="device.id" :label="`${device.name} · ${device.location || '未填写位置'}`" :value="device.id" :disabled="device.status === 'disabled' || !device.onvifCredentialConfigured" / >
< / el-select >
< span class = "field-hint" > 未配置凭据或已停用的设备不可探测 。 < / span >
< / el-form-item >
< el-form-item label = "ONVIF 地址" prop = "address" >
< el-input v-model = "form.address" placeholder="例如:http://设备地址/onvif/device_service" / >
< span class = "field-hint" > 地址中不能包含用户名或密码 ; 凭据从设备管理安全读取 。 < / span >
< / el-form-item >
< el-form-item >
< el-button v-permisaction = "['sense:admission:probe']" type="primary" :loading="probing" @click="handleProbe" > 验证接入 < / el -button >
< el-button @click ="loadSaved" > 查看上次结果 < / el -button >
< / el-form-item >
< / el-form >
< el-divider / >
< el-empty v-if = "!result" description="尚无接入验证结果" / >
< template v-else >
< el -descriptions :column = "2" border >
< el-descriptions-item label = "设备状态" > < el-tag : type = "result.status === 'ready' ? 'success' : 'warning'" > { { statusLabel ( result . status ) } } < / el-tag > < / el-descriptions-item >
< el-descriptions-item label = "检查时间" > { { parseTime ( result . checkedAt ) } } < / el-descriptions-item >
< el-descriptions-item label = "结果说明" :span = "2" > { { result . detail } } < / el-descriptions-item >
< / el-descriptions >
< el-table : data = "result.profiles || []" border stripe class = "profile-table" >
< el-table-column prop = "name" label = "Profile" min -width = " 130 " / >
< el-table-column label = "用途" width = "90" > < template # default = "scope" > < el-tag size = "small" > { { scope . row . kind === 'main' ? '主码流' : scope . row . kind === 'sub' ? '子码流' : '其他' } } < / el-tag > < / template > < / el-table-column >
< el-table-column label = "分辨率" width = "110" > < template # default = "scope" > { { scope . row . width } } × { { scope . row . height } } < / template > < / el-table-column >
< el-table-column prop = "encoding" label = "编码" width = "90" / >
< el-table-column label = "验证状态" width = "130" > < template # default = "scope" > < el-tag : type = "scope.row.verificationStatus === 'ready' ? 'success' : 'danger'" > { { statusLabel ( scope . row . verificationStatus ) } } < / el-tag > < / template > < / el-table-column >
< el-table-column prop = "verificationDetail" label = "说明" min -width = " 180 " / >
< / el-table >
< / template >
< / el-card >
< el-dialog v-model = "discoveryOpen" title="发现结果" width="720px" >
< el -empty v-if = "!discovered.length" description="获准网卡内未发现设备" / >
< el-table v-else : data = "discovered.map(address => ({ address }))" border >
< el-table-column prop = "address" label = "ONVIF 地址" show -overflow -tooltip / >
< el-table-column label = "操作" width = "100" > < template # default = "scope" > < el-button type = "primary" link @click ="useAddress(scope.row.address)" > 使用 < / el -button > < / template > < / el-table-column >
< / el-table >
< / el-dialog >
< / template >
< / BasicLayout >
< / template >
< script setup >
import { onMounted , reactive , ref } from 'vue'
import { ElMessage } from 'element-plus'
import { listDevices } from '@/api/sense/device'
import { discoverDevices , getAdmission , probeDevice } from '@/api/sense/admission'
import { addressHasCredentials , buildProbePayload } from './admissionPayload'
const devices = ref ( [ ] ) ; const result = ref ( null ) ; const discovered = ref ( [ ] )
const discovering = ref ( false ) ; const probing = ref ( false ) ; const discoveryOpen = ref ( false ) ; const probeFormRef = ref ( )
const form = reactive ( { deviceId : '' , address : '' , version : 0 } )
const rules = { deviceId : [ { required : true , message : '请选择设备' , trigger : 'change' } ] , address : [ { required : true , message : '请输入 ONVIF 地址' , trigger : 'blur' } , { validator : ( _r , value , done ) => addressHasCredentials ( value ) ? done ( new Error ( '地址中不能包含用户名或密码' ) ) : done ( ) , trigger : 'blur' } ] }
function unwrap ( response ) { return response ? . data ? . data ? ? response ? . data ? ? response }
function selectDevice ( id ) { const device = devices . value . find ( item => item . id === id ) ; form . version = device ? . version || 0 ; result . value = null }
async function loadDevices ( ) { const response = await listDevices ( { pageIndex : 1 , pageSize : 100 , modality : 'video' } ) ; const payload = unwrap ( response ) ; devices . value = payload ? . list || payload ? . data || payload || [ ] }
async function handleDiscover ( ) { discovering . value = true ; try { const response = await discoverDevices ( ) ; const payload = unwrap ( response ) ; discovered . value = payload ? . addresses || [ ] ; discoveryOpen . value = true } catch ( error ) { ElMessage . error ( error . message || '发现失败,请检查获准网卡配置' ) } finally { discovering . value = false } }
function useAddress ( address ) { form . address = address ; discoveryOpen . value = false }
async function handleProbe ( ) { const valid = await probeFormRef . value ? . validate ( ) . catch ( ( ) => false ) ; if ( ! valid ) return ; probing . value = true ; try { const response = await probeDevice ( form . deviceId , buildProbePayload ( form ) ) ; result . value = unwrap ( response ) ; const device = devices . value . find ( item => item . id === form . deviceId ) ; if ( device ) { device . version += 1 ; form . version = device . version } ElMessage . success ( '接入验证完成' ) } catch ( error ) { ElMessage . error ( error . message || '接入验证失败' ) } finally { probing . value = false } }
async function loadSaved ( ) { if ( ! form . deviceId ) return ElMessage . warning ( '请先选择设备' ) ; try { result . value = unwrap ( await getAdmission ( form . deviceId ) ) } catch ( error ) { ElMessage . warning ( error . message || '尚无验证结果' ) } }
function statusLabel ( status ) { return ( { ready : '可用' , profile _failed : '部分码流失败' , authentication _failed : '认证失败' , target _not _allowed : '目标未获准' , redirect _rejected : '重定向已拒绝' , timeout : '响应超时' , clock _skew : '设备时间异常' , unreachable : '无法连接' , failed : '验证失败' } ) [ status ] || status || '未知' }
onMounted ( loadDevices )
< / script >
< style scoped >
. page - header { display : flex ; justify - content : space - between ; align - items : flex - start ; margin - bottom : 16 px } . page - header h3 { margin : 0 0 6 px } . page - header p { margin : 0 ; color : # 909399 } . probe - form { max - width : 820 px ; margin - top : 22 px } . probe - form . el - select { width : 100 % } . field - hint { display : block ; color : # 909399 ; font - size : 12 px ; line - height : 20 px } . profile - table { margin - top : 18 px }
< / style >