@ -1,10 +1,49 @@
< script setup lang = "ts" >
import { computed , onMounted , onUnmounted , ref } from 'vue'
import { computed , onMounted , onUnmounted , ref , watch } from 'vue'
import { Message , Modal } from '@arco-design/web-vue'
import { http } from '../../api/http'
import { acknowledgeCameraAlert, createCameraDevice , deleteCameraDevice , fetchCameraAlerts , fetchCameraDevices , fetchCameraHealth , fetchCameraSettings , retryCameraDevice , updateCameraDevice , updateCameraSettings , type CameraAlert , type CameraDevice , type CameraSettings } from '../../api/cameraMonitoring'
import { fetchCameraEmailRecords, type CameraEmailRecord , acknowledgeCameraAlert, createCameraDevice , deleteCameraDevice , fetchCameraAlerts , fetchCameraDevices , fetchCameraHealth , fetchCameraSettings , retryCameraDevice , updateCameraDevice , updateCameraSettings , type CameraAlert , type CameraDevice , type CameraSettings } from '../../api/cameraMonitoring'
const tab = ref ( 'health' ) ; const loading = ref ( false ) ; const devices = ref < CameraDevice [ ] > ( [ ] ) ; const alerts = ref < CameraAlert [ ] > ( [ ] ) ; const settings = ref < CameraSettings | null > ( null ) ; const editing = ref < CameraDevice | null > ( null ) ; const formVisible = ref ( false ) ; const form = ref < Record < string , any > > ( { monitor _enabled : true , notification _enabled : true , include _in _venue _stats : true , sdk _port : 8000 , http _port : 80 , channel : 1 , push _timeout _seconds : 300 } )
const alertPage = ref ( 1 )
const alertTotal = ref ( 0 )
const alertStatus = ref < string > ( )
const alertLoading = ref ( false )
let alertRequest = 0
async function loadAlerts ( page = alertPage . value ) {
alertPage . value = page
const request = ++ alertRequest
alertLoading . value = true
try {
const data = await fetchCameraAlerts ( { page , page _size : 20 , status : alertStatus . value } )
if ( request !== alertRequest ) return
alerts . value = data . data ? ? [ ]
alertTotal . value = data . total ? ? 0
} catch ( e : any ) {
if ( request === alertRequest ) Message . error ( e ? . response ? . data ? . message || '加载告警记录失败' )
} finally { if ( request === alertRequest ) alertLoading . value = false }
}
watch ( tab , value => { if ( value === 'alerts' ) void loadAlerts ( ) } )
const emailRecords = ref < CameraEmailRecord [ ] > ( [ ] )
const emailLoading = ref ( false )
const emailPage = ref ( 1 )
const emailTotal = ref ( 0 )
const emailStatus = ref < string > ( )
let emailRequest = 0
async function loadEmailRecords ( page = emailPage . value ) {
emailPage . value = page
const request = ++ emailRequest
emailLoading . value = true
try {
const data = await fetchCameraEmailRecords ( { page , page _size : 20 , send _status : emailStatus . value } )
if ( request !== emailRequest ) return
emailRecords . value = data . data
emailTotal . value = data . total
} catch ( e : any ) {
if ( request === emailRequest ) Message . error ( e ? . response ? . data ? . message || '加载邮件发送记录失败' )
} finally { if ( request === emailRequest ) emailLoading . value = false }
}
watch ( tab , value => { if ( value === 'emails' ) void loadEmailRecords ( ) } )
const venues = ref < Array < { id : number ; name : string } > > ( [ ] ) ; const filters = ref < { venue _id ? : number ; status ? : string ; keyword : string } > ( { keyword : '' } )
const healthFilter = ref < 'all' | 'normal' | 'abnormal' > ( 'all' )
const retrying = ref < Record < number , boolean > > ( { } )
@ -26,16 +65,16 @@ const healthRows = computed(() => devices.value.filter((row) => {
return rank ( a . health ? . status ) - rank ( b . health ? . status )
} ) )
async function loadVenues ( ) { try { const { data } = await http . get ( '/venues' ) ; venues . value = Array . isArray ( data ) ? data : [ ] } catch { venues . value = [ ] } }
async function load ( ) { loading . value = true ; try { const [ h , a, d, s ] = await Promise . all ( [ fetchCameraHealth ( ) , fetchCameraAlerts ( { status : 'OPEN' , page _size : 100 } ) , fetchCameraDevices ( { page _size : 100 } ) , fetchCameraSettings ( ) ] ) ; devices . value = h . devices ? ? d . data ? ? [ ] ; summary . value = h . summary ? . byStatus ? ? { } ; alerts. value = a . data ? ? [ ] ; settings . value = { ... s , notification _start _time : String ( s . notification _start _time || '' ) . slice ( 0 , 5 ) , notification _end _time : String ( s . notification _end _time || '' ) . slice ( 0 , 5 ) } ; recipientText . value = ( s . recipient _emails || [ ] ) . join ( ',' ) } catch ( e : any ) { Message . error ( e ? . response ? . data ? . message || e ? . message || '加载摄像头预警数据失败' ) } finally { loading . value = false } }
async function load ( ) { loading . value = true ; try { const [ h , d, s ] = await Promise . all ( [ fetchCameraHealth ( ) , fetchCameraDevices ( { page _size : 100 } ) , fetchCameraSettings ( ) ] ) ; devices . value = h . devices ? ? d . data ? ? [ ] ; summary . value = h . summary ? . byStatus ? ? { } ; settings. value = { ... s , repeat _notification _enabled : s . repeat _notification _enabled ? ? false , repeat _notification _interval _hours : s . repeat _notification _interval _hours ? ? 24 , notification _start _time : String ( s . notification _start _time || '' ) . slice ( 0 , 5 ) , notification _end _time : String ( s . notification _end _time || '' ) . slice ( 0 , 5 ) } ; recipientText . value = ( s . recipient _emails || [ ] ) . join ( ',' ) } catch ( e : any ) { Message . error ( e ? . response ? . data ? . message || e ? . message || '加载摄像头预警数据失败' ) } finally { loading . value = false } }
function openCreate ( ) { editing . value = null ; form . value = { monitor _enabled : true , notification _enabled : true , include _in _venue _stats : true , sdk _port : 8000 , http _port : 80 , channel : 1 , push _timeout _seconds : settings . value ? . default _push _timeout _seconds ? ? 300 } ; formVisible . value = true }
function openEdit ( row : CameraDevice ) { editing . value = row ; form . value = { ... row , password : '' } ; formVisible . value = true }
async function save ( ) { try { if ( editing . value ) await updateCameraDevice ( editing . value . id , form . value ) ; else await createCameraDevice ( form . value ) ; Message . success ( '已保存' ) ; formVisible . value = false ; await load ( ) } catch ( e : any ) { Message . error ( e ? . response ? . data ? . message || e ? . message || '保存失败' ) } }
async function retry ( row : CameraDevice ) { if ( retrying . value [ row . id ] ) return ; Modal . confirm ( { title : '确认重新连接' , content : '请确认摄像头已正常在线或已经重启,再重新链接。' , okText : '确认重新连接' , cancelText : '关闭' , onOk : async ( ) => { retrying . value [ row . id ] = true ; try { await retryCameraDevice ( row . id ) ; Message . success ( '重试指令已下发,正在等待登录、布防和客流推送验证' ) ; await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ; await load ( ) ; retrying . value [ row . id ] = false } catch ( e : any ) { retrying . value [ row . id ] = false ; Message . error ( e ? . response ? . data ? . message || e ? . message || '重试失败' ) } } } ) }
async function remove ( row : CameraDevice ) { Modal . confirm ( { title : '停用摄像头' , content : ` 确定停用 ${ row . name } ( ${ row . ip } )吗? ` , okText : '确认停用' , cancelText : '关闭' , onOk : async ( ) => { await deleteCameraDevice ( row . id ) ; Message . success ( '已停用' ) ; await load ( ) } } ) }
async function ack ( row : CameraAlert ) { if ( acknowledging . value [ row . id ] ) return ; acknowledging . value [ row . id ] = true ; try { await acknowledgeCameraAlert ( row . id ) ; Message . success ( '已确认' ) ; await load ( ) } catch ( e : any ) { const status = e ? . response ? . status ; Message . error ( status === 429 ? '操作过于频繁,请稍后再试' : e ? . response ? . data ? . message || e ? . message || '确认处理失败' ) } finally { acknowledging . value [ row . id ] = false } }
async function ack ( row : CameraAlert ) { if ( acknowledging . value [ row . id ] ) return ; acknowledging . value [ row . id ] = true ; try { await acknowledgeCameraAlert ( row . id ) ; Message . success ( '已确认' ) ; await load Alerts ( ) } catch ( e : any ) { const status = e ? . response ? . status ; Message . error ( status === 429 ? '操作过于频繁,请稍后再试' : e ? . response ? . data ? . message || e ? . message || '确认处理失败' ) } finally { acknowledging . value [ row . id ] = false } }
async function saveSettings ( ) { if ( ! settings . value ) return ; settings . value . recipient _emails = recipientText . value . split ( ',' ) . map ( x => x . trim ( ) ) . filter ( Boolean ) ; await updateCameraSettings ( settings . value ) ; Message . success ( '通知设置已保存' ) }
let refreshTimer : ReturnType < typeof setInterval > | undefined
onMounted ( ( ) => { void Promise . all ( [ load ( ) , loadVenues ( ) ] ) ; refreshTimer = setInterval ( ( ) => { if ( ! loading . value && ! Object . values ( retrying . value ) . some ( Boolean ) ) void load ( ) } , 60000 ) } )
onMounted ( ( ) => { void Promise . all ( [ load ( ) , loadVenues ( ) ] ) ; refreshTimer = setInterval ( ( ) => { if ( tab . value === 'alerts' ) { if ( ! alertLoading . value ) void loadAlerts ( ) ; return } if ( tab . value === 'emails' ) { if ( ! emailLoading . value ) void loadEmailRecords ( ) ; return } if ( tab . value !== 'settings' && ! loading . value && ! Object . values ( retrying . value ) . some ( Boolean ) ) void load ( ) } , 60000 ) } )
onUnmounted ( ( ) => { if ( refreshTimer ) clearInterval ( refreshTimer ) } )
< / script >
< template >
@ -47,8 +86,48 @@ onUnmounted(() => { if (refreshTimer) clearInterval(refreshTimer) })
< template # columns > < a -table -column title = "场馆" > < template # cell = "{ record }" > { { record . venue ? . name || record . venue _name || '未绑定' } } < / template > < /a-table-column><a-table-column title="摄像头"><template #cell="{ record }">{{ record.name }}<div class="muted">{{ record.ip }} / 通道 { { record . channel } } < / div > < / template > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 状 态 " > < t e m p l a t e # c e l l = " { r e c o r d } " > < a - t a g : c o l o r = " s t a t u s C o l o r ( r e c o r d . h e a l t h ? . s t a t u s ) " > { { s t a t u s L a b e l ( r e c o r d . h e a l t h ? . s t a t u s ) } } < / a - t a g > < / t e m p l a t e > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 最 后 推 送 " : w i d t h = " 1 9 0 " > < t e m p l a t e # c e l l = " { r e c o r d } " > { { f o r m a t T i m e ( r e c o r d . h e a l t h ? . l a s t _ p d c _ a t _ s o u r c e | | r e c o r d . h e a l t h ? . l a s t _ p d c _ a t ) } } < / t e m p l a t e > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 网 络 连 通 性 " : w i d t h = " 1 4 0 " > < t e m p l a t e # c e l l = " { r e c o r d } " > { { r e c o r d . h e a l t h ? . t c p _ r e a c h a b l e = = = t r u e ? ' 网 络 正 常 ' : r e c o r d . h e a l t h ? . t c p _ r e a c h a b l e = = = f a l s e ? ' 网 络 不 可 达 ' : ' 未 检 测 ' } } < / t e m p l a t e > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 操 作 " > < t e m p l a t e # c e l l = " { r e c o r d } " > < a - s p a c e > < a - b u t t o n s i z e = " s m a l l " @ c l i c k = " o p e n E d i t ( r e c o r d ) " > 编 辑 < / a - b u t t o n > < a - b u t t o n s i z e = " s m a l l " t y p e = " p r i m a r y " : l o a d i n g = " r e t r y i n g [ r e c o r d . i d ] " : d i s a b l e d = " r e t r y i n g [ r e c o r d . i d ] " @ c l i c k = " r e t r y ( r e c o r d ) " > { { r e t r y i n g [ r e c o r d . i d ] ? ' 重 新 连 接 中 ' : ' 重 新 连 接 ' } } < / a - b u t t o n > < a - b u t t o n s i z e = " s m a l l " s t a t u s = " d a n g e r " @ c l i c k = " r e m o v e ( r e c o r d ) " > 停 用 < / a - b u t t o n > < / a - s p a c e > < / t e m p l a t e > < / a - t a b l e - c o l u m n > < / t e m p l a t e >
< / a - t a b l e >
< / a - t a b - p a n e >
< a -tab -pane key = "alerts" title = "待处理告警" > < a -table :data ="alerts" :pagination ="false" > < template # columns > < a -table -column title = "时间" > < template # cell = "{ record }" > { { formatTime ( record . first _occurred _at ) } } < / template > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 场 馆 " > < t e m p l a t e # c e l l = " { r e c o r d } " > { { r e c o r d . v e n u e ? . n a m e | | ' — ' } } < / t e m p l a t e > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 设 备 " > < t e m p l a t e # c e l l = " { r e c o r d } " > { { r e c o r d . c a m e r a ? . n a m e } } ( { { r e c o r d . c a m e r a ? . i p } } ) < / t e m p l a t e > < / a - t a b l e - c o l u m n > < a - t a b l e - c o l u m n t i t l e = " 原 因 " d a t a - i n d e x = " r e a s o n " / > < a - t a b l e - c o l u m n t i t l e = " 操 作 " > < t e m p l a t e # c e l l = " { r e c o r d } " > < a - b u t t o n s i z e = " s m a l l " : l o a d i n g = " a c k n o w l e d g i n g [ r e c o r d . i d ] " : d i s a b l e d = " a c k n o w l e d g i n g [ r e c o r d . i d ] " @ c l i c k = " a c k ( r e c o r d ) " > 确 认 处 理 < / a - b u t t o n > < / t e m p l a t e > < / a - t a b l e - c o l u m n > < / t e m p l a t e > < / a - t a b l e > < / a - t a b - p a n e >
< a -tab -pane key = "settings" title = "通知设置" > < a -form v-if ="settings" :model="settings" layout="vertical" style="max-width: 680px"><a-form-item label="接收邮箱"><a-input v-model="recipientText" placeholder="多个邮箱用逗号分隔" /></a-form-item><a-form-item label="通知开始时间"><a-time-picker v-model="settings.notification_start_time" format="HH:mm" /></a-form-item><a-form-item label="通知结束时间"><a-time-picker v-model="settings.notification_end_time" format="HH:mm" /></a-form-item><a-form-item label="客流推送超时时间"><a-input-number v-model="settings.default_push_timeout_seconds" :min="60" :max="86400" /><div class="form-help">超过这个时间没有收到该摄像头的客流数据,系统会判定为推送异常,并继续进行自动重连判断。单位:秒。</div></a-form-item><a-form-item><a-button type="primary" @click="saveSettings" > 保 存 设 置 < / a -button > < / a - f o r m - i t e m > < / a - f o r m > < / a - t a b - p a n e >
< a -tab -pane key = "alerts" title = "处理告警" >
< a -space :size ="12" style = "margin-bottom: 16px" >
< a -select v-model ="alertStatus" allow-clear placeholder="全部处理状态" style="width: 200px" @change="loadAlerts(1)" >
< a -option value = "OPEN" > 待处理 < / a - o p t i o n > < a - o p t i o n v a l u e = " A C K N O W L E D G E D " > 已 人 工 确 认 < / a - o p t i o n > < a - o p t i o n v a l u e = " R E S O L V E D " > 已 自 动 恢 复 < / a - o p t i o n >
< / a - s e l e c t >
< a -button :loading ="alertLoading" @click ="loadAlerts()" > 刷 新 < / a -button >
< / a - s p a c e >
< a -table :data ="alerts" :loading ="alertLoading" row -key = " id " : scroll = "{ x: 1450 }"
: pagination = "{ current: alertPage, pageSize: 20, total: alertTotal, showTotal: true }" @ page - change = "loadAlerts" >
< template # columns >
< a -table -column title = "告警时间" :width ="190" > < template # cell = "{ record }" > { { formatTime ( record . first _occurred _at ) } } < / template > < / a - t a b l e - c o l u m n >
< a -table -column title = "场馆" :width ="190" > < template # cell = "{ record }" > { { record . venue ? . name || record . camera ? . venue _name || '—' } } < / template > < / a - t a b l e - c o l u m n >
< a -table -column title = "设备" :width ="220" > < template # cell = "{ record }" > { { record . camera ? . name || '—' } } < div v-if ="record.camera" class="muted" > {{ record.camera.ip }} / 通 道 {{ record.camera.channel }} < / div > < / template > < / a -table -column >
< a -table -column title = "原因" data -index = " reason " :width ="300" / >
< a -table -column title = "处理状态" :width ="140" > < template # cell = "{ record }" > < a -tag : color = "record.status === 'OPEN' ? 'red' : record.status === 'RESOLVED' ? 'green' : 'blue'" > { { ( { OPEN : '待处理' , ACKNOWLEDGED : '已人工确认' , RESOLVED : '已自动恢复' } as Record < string , string > ) [ record . status ] || record . status } } < / a - t a g > < / t e m p l a t e > < / a - t a b l e - c o l u m n >
< a -table -column title = "处理时间" :width ="220" > < template # cell = "{ record }" > { { formatTime ( record . status === 'RESOLVED' ? record . recovered _at : record . acknowledged _at ) } } < div v-if ="record.status === 'RESOLVED' && record.acknowledged_at" class="muted" > 人 工 确 认 : {{ formatTime ( record.acknowledged_at ) }} < / div > < / template > < / a -table -column >
< a -table -column title = "操作" :width ="140" > < template # cell = "{ record }" > < a -button v-if ="record.status === 'OPEN'" size="small" :loading="acknowledging[record.id]" :disabled="acknowledging[record.id]" @click="ack(record)" > 确 认 处 理 < / a -button > < span v-else > — < / span > < / template > < / a -table -column >
< / template >
< / a - t a b l e >
< div class = "form-help" > 默认保留并显示全部告警记录 。 已人工确认不代表设备恢复 ; 恢复推送后自动标记为已自动恢复 , 并保留原人工确认时间 。 时间按北京时间显示 。 < / div >
< / a - t a b - p a n e >
< a -tab -pane key = "settings" title = "通知设置" > < a -form v-if ="settings" :model="settings" layout="vertical" style="max-width: 680px"><a-form-item label="接收邮箱"><a-input v-model="recipientText" placeholder="多个邮箱用逗号分隔" /></a-form-item><a-form-item label="通知开始时间"><a-time-picker v-model="settings.notification_start_time" format="HH:mm" /></a-form-item><a-form-item label="通知结束时间"><a-time-picker v-model="settings.notification_end_time" format="HH:mm" /></a-form-item><a-form-item label="持续异常重复邮件提醒"><a-switch v-model="settings.repeat_notification_enabled" /><div class="form-help">开启后,摄像头持续异常且仍需人工处理时,按设定间隔再次发送邮件。确认处理不会停止提醒,恢复推送后自动停止。</div></a-form-item><a-form-item v-if="settings.repeat_notification_enabled" label="重复通知间隔(小时)"><a-input-number v-model="settings.repeat_notification_interval_hours" :min="1" :max="8760" :precision="0" /><div class="form-help">默认 24 小时,从上次成功发送邮件开始计算;到期不在通知时间段内,将顺延至允许发送时段。不会发送恢复通知。</div></a-form-item><a-form-item label="客流推送超时时间"><a-input-number v-model="settings.default_push_timeout_seconds" :min="60" :max="86400" /><div class="form-help">超过这个时间没有收到该摄像头的客流数据,系统会判定为推送异常,并继续进行自动重连判断。单位:秒。</div></a-form-item><a-form-item><a-button type="primary" @click="saveSettings" > 保 存 设 置 < / a -button > < / a - f o r m - i t e m > < / a - f o r m > < / a - t a b - p a n e >
< a -tab -pane key = "emails" title = "邮件发送记录" >
< a -space :size ="12" style = "margin-bottom: 16px" >
< a -select v-model ="emailStatus" allow-clear placeholder="全部发送结果" style="width: 180px" @change="loadEmailRecords(1)" >
< a -option value = "sent" > 发送成功 < / a - o p t i o n > < a - o p t i o n v a l u e = " f a i l e d " > 发 送 失 败 < / a - o p t i o n > < a - o p t i o n v a l u e = " p e n d i n g " > 待 发 送 < / a - o p t i o n >
< / a - s e l e c t >
< a -button :loading ="emailLoading" @click ="loadEmailRecords()" > 刷 新 < / a -button >
< / a - s p a c e >
< a -table :data ="emailRecords" :loading ="emailLoading" row -key = " id " : scroll = "{ x: 1300 }"
: pagination = "{ current: emailPage, pageSize: 20, total: emailTotal, showTotal: true }" @ page - change = "loadEmailRecords" >
< template # columns >
< a -table -column title = "发送时间" :width ="190" > < template # cell = "{ record }" > { { formatTime ( record . sent _at || record . created _at ) } } < / template > < / a - t a b l e - c o l u m n >
< a -table -column title = "场馆" :width ="200" > < template # cell = "{ record }" > { { record . alert ? . venue ? . name || record . alert ? . camera ? . venue _name || '—' } } < / template > < / a - t a b l e - c o l u m n >
< a -table -column title = "摄像头" :width ="230" > < template # cell = "{ record }" > { { record . alert ? . camera ? . name || '—' } } < div v-if ="record.alert?.camera" class="muted" > {{ record.alert.camera.ip }} / 通 道 {{ record.alert.camera.channel }} < / div > < / template > < / a -table -column >
< a -table -column title = "收件邮箱" data -index = " receiver " :width ="230" / >
< a -table -column title = "发送结果" :width ="120" > < template # cell = "{ record }" > < a -tag : color = "record.send_status === 'sent' ? 'green' : record.send_status === 'failed' ? 'red' : 'gray'" > { { ( { sent : '发送成功' , failed : '发送失败' , pending : '待发送' } as Record < string , string > ) [ record . send _status ] || record . send _status } } < / a - t a g > < / t e m p l a t e > < / a - t a b l e - c o l u m n >
< a -table -column title = "失败原因" :width ="330" > < template # cell = "{ record }" > < div style = "white-space: normal; overflow-wrap: anywhere" > { { record . error _message || '—' } } < / div > < / template > < / a - t a b l e - c o l u m n >
< / template >
< / a - t a b l e >
< div class = "form-help" > 时间按北京时间显示 ; 发送失败时显示尝试时间 。 发送成功表示邮件服务器已接受发送 , 不代表收件人已阅读 。 首次和重复发送均会记录 。 < / div >
< / a - t a b - p a n e >
< / a - t a b s >
< / a - c a r d >
< a -modal v -model :visible ="formVisible" : title = "editing ? '编辑摄像头' : '新增摄像头'" :height ="640" : body -style = " { maxHeight : ' 520px ' , overflowY : ' auto ' } " @ok ="save" > < a -form :model ="form" layout = "vertical" > < a -form -item label = "名称" > < a -input v-model ="form.name" /></a-form-item><a-form-item label="所属场馆"><a-select v-model="form.venue_id" allow-clear allow-search placeholder="请输入或选择场馆"><a-option v-for="venue in venues" :key="venue.id" :value="venue.id">{{ venue.name }}</a-option></a-select></a-form-item><a-form-item label="IP/域名"><a-input v-model="form.ip" /></a-form-item><a-form-item label="SDK 端口"><a-input-number v-model="form.sdk_port" /></a-form-item><a-form-item label="通道"><a-input-number v-model="form.channel" :min="1" /></a-form-item><a-form-item label="设备槽位"><a-input-number v-model="form.device_slot" /></a-form-item><a-form-item label="用户名"><a-input v-model="form.username" /></a-form-item><a-form-item label="密码"><a-input-password v-model="form.password" placeholder="编辑时留空表示不修改" /></a-form-item><a-form-item label="客流推送超时时间"><a-input-number v-model="form.push_timeout_seconds" :min="60" /><div class="form-help">该摄像头超过设定时间没有客流数据时,系统会判定推送异常。单位:秒。</div></a-form-item><a-form-item label="监测"><a-switch v-model="form.monitor_enabled" /></a-form-item><a-form-item label="发送人工介入邮件" > < a -switch v -model = " form.notification_enabled " / > < / a - f o r m - i t e m > < / a - f o r m > < / a - m o d a l >