@ -1,5 +1,5 @@
< script setup lang = "ts" >
import { computed , onMounted , ref } from 'vue'
import { computed , onMounted , onUnmounted, ref } 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'
@ -7,40 +7,46 @@ import { acknowledgeCameraAlert, createCameraDevice, deleteCameraDevice, fetchCa
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 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 > > ( { } )
const summary = ref < Record < string , number > > ( { } )
const recipientText = ref ( '' )
const statusLabel = ( s ? : string ) => ( { ONLINE _PUSHING : '推送正常' , PUSH _SUSPECT : '疑似停止' , PUSH _STALE : '推送停止' , NETWORK _DOWN : '网络 断开 ', LOGIN _FAILED : '登录 失败', ARM _FAILED : '布防失败 ', UNKNOWN : '未知' , DISABLED : '已停用' , RECONNECTING : '重连中' } [ String ( s || 'UNKNOWN' ) ] || String ( s || '未知' ) )
const statusLabel = ( s ? : string ) => ( { ONLINE _PUSHING : '推送正常' , PUSH _SUSPECT : '疑似停止' , PUSH _STALE : '推送停止' , NETWORK _DOWN : '网络 不可达 ', LOGIN _FAILED : '登录 认证 失败', ARM _FAILED : '布防失败 ', NO _PUSH _YET : '尚未收到推送' , SERVICE _UNAVAILABLE : '监测服务不可用 ', UNKNOWN : '未知' , DISABLED : '已停用' , RECONNECTING : '重连中' } [ String ( s || 'UNKNOWN' ) ] || String ( s || '未知' ) )
const statusColor = ( s ? : string ) => [ 'ONLINE_PUSHING' ] . includes ( String ( s ) ) ? 'green' : [ 'PUSH_SUSPECT' , 'RECONNECTING' ] . includes ( String ( s ) ) ? 'orangered' : 'red'
const formatTime = ( v ? : string | null ) => v ? new Date ( v ) . toLocaleString ( 'zh-CN' ) : '—'
const formatTime = ( v ? : string | null ) => { if ( ! v ) return '—' ; const raw = String ( v ) . replace ( /\.(\d{3})\d+/g , '.$1' ) ; const normalized = /(?:Z|[+-]\d{2}:?\d{2})$/i . test ( raw ) ? raw : ` ${ raw . replace ( ' ' , 'T' ) } Z ` ; const date = new Date ( normalized ) ; return Number . isNaN ( date . getTime ( ) ) ? '—' : date . toLocaleString ( 'zh-CN' , { timeZone : 'Asia/Shanghai' } ) }
const healthRows = computed ( ( ) => devices . value . filter ( ( row ) => {
const venueMatch = ! filters . value . venue _id || Number ( row . venue _id ) === Number ( filters . value . venue _id )
const status = String ( row . health ? . status || 'UNKNOWN' )
const statusMatch = healthFilter . value === 'normal' ? status === 'ONLINE_PUSHING' : healthFilter . value === 'abnormal' ? [ 'PUSH_STALE' , 'NETWORK_DOWN' , 'LOGIN_FAILED' , 'ARM_FAILED' , 'PUSH_SUSPECT' ]. includes ( status ) : ( ! filters . value . status || status === filters . value . status )
const statusMatch = healthFilter . value === 'normal' ? status === 'ONLINE_PUSHING' : healthFilter . value === 'abnormal' ? [ 'PUSH_STALE' , 'NETWORK_DOWN' , 'LOGIN_FAILED' , 'ARM_FAILED' , 'PUSH_SUSPECT' , 'NO_PUSH_YET' , 'SERVICE_UNAVAILABLE' ]. includes ( status ) : ( ! filters . value . status || status === filters . value . status )
const keyword = filters . value . keyword . trim ( ) . toLowerCase ( )
const keywordMatch = ! keyword || ` ${ row . name } ${ row . ip } ${ row . venue _name || row . venue ? . name || '' } ` . toLowerCase ( ) . includes ( keyword )
return venueMatch && statusMatch && keywordMatch
} ) . sort ( ( a , b ) => {
const rank = ( status ? : string ) => ( { SERVICE _UNAVAILABLE : 0 , LOGIN _FAILED : 1 , ARM _FAILED : 2 , NETWORK _DOWN : 3 , PUSH _STALE : 4 , PUSH _SUSPECT : 5 , NO _PUSH _YET : 6 , UNKNOWN : 7 , RECONNECTING : 8 , ONLINE _PUSHING : 9 } [ String ( status || 'UNKNOWN' ) ] ? ? 7 )
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 } }
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 ) { Modal . confirm ( { title : '确认重新连接' , content : '请确认摄像头已正常在线或已经重启,再重新链接。' , okText : '确认重新连接' , cancelText : '关闭' , onOk : async ( ) => { try { await retryCameraDevice ( row . id ) ; Message . success ( '重试指令已下发, 等待新的 客流推送验证') ; 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 ) { await acknowledgeCameraAlert ( row . id ) ; Message . success ( '已确认' ) ; await load ( ) }
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 ( '通知设置已保存' ) }
onMounted ( ( ) => { void Promise . all ( [ load ( ) , loadVenues ( ) ] ) } )
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 ) } )
onUnmounted ( ( ) => { if ( refreshTimer ) clearInterval ( refreshTimer ) } )
< / script >
< template >
< a -card title = "场馆管理 / 摄像头预警" >
< a -tabs v -model :active-key ="tab" >
< a -tab -pane key = "health" title = "实时状态" >
< a -space wrap :size ="12" style = "margin-bottom: 12px" > < a -tag color = "green" class = "clickable-tag" @ click = "healthFilter = 'normal'; filters.status = undefined" > 正常 { { summary . ONLINE _PUSHING || 0 } } < / a - t a g > < a - t a g c o l o r = " r e d " c l a s s = " c l i c k a b l e - t a g " @ c l i c k = " h e a l t h F i l t e r = ' a b n o r m a l ' ; f i l t e r s . s t a t u s = u n d e f i n e d " > 异 常 { { ( s u m m a r y . P U S H _ S T A L E | | 0 ) + ( s u m m a r y . N E T W O R K _ D O W N | | 0 ) + ( s u m m a r y . L O G I N _ F A I L E D | | 0 ) + ( s u m m a r y . A R M _ F A I L E D | | 0 ) }} < / a - t a g > < a - t a g c l a s s = " c l i c k a b l e - t a g " @ c l i c k = " h e a l t h F i l t e r = ' a l l ' ; f i l t e r s . s t a t u s = u n d e f i n e d " > 总 数 { { d e v i c e s . l e n g t h } } < / a - t a g > < a - i n p u t v - m o d e l = " f i l t e r s . k e y w o r d " a l l o w - c l e a r p l a c e h o l d e r = " 搜 索 摄 像 头 名 称 或 I P " s t y l e = " w i d t h : 2 4 0 p x " / > < a - s e l e c t v - m o d e l = " f i l t e r s . v e n u e _ i d " a l l o w - c l e a r a l l o w - s e a r c h p l a c e h o l d e r = " 按 场 馆 筛 选 " s t y l e = " w i d t h : 2 2 0 p x " > < a - o p t i o n v - f o r = " v e n u e i n v e n u e s " : k e y = " v e n u e . i d " : v a l u e = " v e n u e . i d " > { { v e n u e . n a m e } } < / a - o p t i o n > < / a - s e l e c t > < a - s e l e c t v - m o d e l = " f i l t e r s . s t a t u s " a l l o w - c l e a r a l l o w - s e a r c h p l a c e h o l d e r = " 按 状 态 筛 选 " s t y l e = " w i d t h : 1 6 0 p x " @ c h a n g e = " h e a l t h F i l t e r = ' a l l ' " > < a - o p t i o n v a l u e = " O N L I N E _ P U S H I N G " > 推 送 正 常 < / a - o p t i o n > < a - o p t i o n v a l u e = " P U S H _ S T A L E " > 推 送 停 止 < / a - o p t i o n > < a - o p t i o n v a l u e = " N E T W O R K _ D O W N " > 网 络 断 开 < / a - o p t i o n > < a - o p t i o n v a l u e = " L O G I N _ 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 = " A R M _ 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 = " U N K N O W N " > 未 知 < / a - o p t i o n > < / a - s e l e c t > < a - b u t t o n @ c l i c k = " l o a d " : l o a d i n g = " l o a d i n g " > 刷 新 < / a - b u t t o n > < a - b u t t o n t y p e = " p r i m a r y " @ c l i c k = " o p e n C r e a t e " > 新 增 摄 像 头 < / a - b u t t o n > < / a - s p a c e >
< a -space wrap :size ="12" style = "margin-bottom: 12px" > < a -tag color = "green" class = "clickable-tag" @ click = "healthFilter = 'normal'; filters.status = undefined" > 正常 { { summary . ONLINE _PUSHING || 0 } } < / a - t a g > < a - t a g c o l o r = " r e d " c l a s s = " c l i c k a b l e - t a g " @ c l i c k = " h e a l t h F i l t e r = ' a b n o r m a l ' ; f i l t e r s . s t a t u s = u n d e f i n e d " > 异 常 { { ( s u m m a r y . P U S H _ S T A L E | | 0 ) + ( s u m m a r y . N E T W O R K _ D O W N | | 0 ) + ( s u m m a r y . L O G I N _ F A I L E D | | 0 ) + ( s u m m a r y . A R M _ F A I L E D | | 0 ) + ( s u m m a r y . N O _ P U S H _ Y E T | | 0 ) + ( s u m m a r y . S E R V I C E _ U N A V A I L A B L E | | 0 ) }} < / a - t a g > < a - t a g c l a s s = " c l i c k a b l e - t a g " @ c l i c k = " h e a l t h F i l t e r = ' a l l ' ; f i l t e r s . s t a t u s = u n d e f i n e d " > 总 数 { { d e v i c e s . l e n g t h } } < / a - t a g > < a - i n p u t v - m o d e l = " f i l t e r s . k e y w o r d " a l l o w - c l e a r p l a c e h o l d e r = " 搜 索 摄 像 头 名 称 或 I P " s t y l e = " w i d t h : 2 4 0 p x " / > < a - s e l e c t v - m o d e l = " f i l t e r s . v e n u e _ i d " a l l o w - c l e a r a l l o w - s e a r c h p l a c e h o l d e r = " 按 场 馆 筛 选 " s t y l e = " w i d t h : 2 2 0 p x " > < a - o p t i o n v - f o r = " v e n u e i n v e n u e s " : k e y = " v e n u e . i d " : v a l u e = " v e n u e . i d " > { { v e n u e . n a m e } } < / a - o p t i o n > < / a - s e l e c t > < a - s e l e c t v - m o d e l = " f i l t e r s . s t a t u s " a l l o w - c l e a r a l l o w - s e a r c h p l a c e h o l d e r = " 按 状 态 筛 选 " s t y l e = " w i d t h : 1 6 0 p x " @ c h a n g e = " h e a l t h F i l t e r = ' a l l ' " > < a - o p t i o n v a l u e = " O N L I N E _ P U S H I N G " > 推 送 正 常 < / a - o p t i o n > < a - o p t i o n v a l u e = " P U S H _ S T A L E " > 推 送 停 止 < / a - o p t i o n > < a - o p t i o n v a l u e = " N E T W O R K _ D O W N " > 网 络 断 开 < / a - o p t i o n > < a - o p t i o n v a l u e = " L O G I N _ 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 = " A R M _ 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 = " N O _ P U S H _ Y E T " > 尚 未 收 到 推 送 < / a - o p t i o n > < a - o p t i o n v a l u e = " S E R V I C E _ U N A V A I L A B L E " > 监 测 服 务 不 可 用 < / a - o p t i o n > < a - o p t i o n v a l u e = " U N K N O W N " > 未 知 < / a - o p t i o n > < / a - s e l e c t > < a - b u t t o n @ c l i c k = " l o a d " : l o a d i n g = " l o a d i n g " > 刷 新 < / a - b u t t o n > < a - b u t t o n t y p e = " p r i m a r y " @ c l i c k = " o p e n C r e a t e " > 新 增 摄 像 头 < / a - b u t t o n > < / a - s p a c e >
< a -table :data ="healthRows" :loading ="loading" row -key = " id " :pagination ="false" >
< 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 = " 最 后 推 送 " >< 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 ) } } < / 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 . 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 " @c l i c k = " r e t r y ( 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 " 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 >
< 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 = "时间" data -index = " first_occurred_at " / > < a -table -column title = "场馆" > < template # cell = "{ record }" > { { record . venue ? . name || '—' } } < / 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 . 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 " @ 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 = "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 " @ 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 - t a b s >
< / a - c a r d >