@@ -1,177 +1,148 @@
< script setup lang = "ts" >
< script setup lang = "ts" >
import { initStationAlarms , initStationDevices , syncCameraApi , syncNvrChannelsApi , type Station } from '@/apis' ;
import { initStationAlarms , initStationDevices , syncCameraApi , syncNvrChannelsApi , type Station } from '@/apis' ;
import { AlarmDetailModal , DeviceDetailModal , DeviceParamConfigModal , IcmpExportModal , RecordCheckExportModal , StationCard , type StationCardProps } from '@/components' ;
import { AlarmDetailModal , DeviceDetailModal , DeviceParamConfigModal , IcmpExportModal , RecordCheckExportModal , StationCard , type StationCardProps } from '@/components' ;
import { useLineDevicesQuery } from '@/composables' ;
import { useBatchActions , useLineDevicesQuery } from '@/composables' ;
import { useAlarmStore , useDeviceStore , useSettingStore , useStationStore } from '@/stores' ;
import { useAlarmStore , useDeviceStore , useSettingStore , useStationStore } from '@/stores' ;
import { parseErrorFeedback } from '@/utils' ;
import { useMutation } from '@tanstack/vue-query' ;
import { useMutation } from '@tanstack/vue-query' ;
import { objectEntries } from '@vueuse/core' ;
import { isCancel } from 'axios' ;
import { NButton , NButtonGroup , NCheckbox , NFlex , NGrid , NGridItem , NScrollbar } from 'naive-ui' ;
import { NButton , NButtonGroup , NCheckbox , NFlex , NGrid , NGridItem , NScrollbar } from 'naive-ui' ;
import { storeToRefs } from 'pinia' ;
import { storeToRefs } from 'pinia' ;
import { computed , ref } from 'vue' ;
import { computed , ref } from 'vue' ;
const settingStore = useSettingStore ( ) ;
const settingStore = useSettingStore ( ) ;
const { stationGridCols : stationGridColumns } = storeToRefs ( settingStore ) ;
const { stationGridCols : stationGridColumns } = storeToRefs ( settingStore ) ;
const stationStore = useStationStore ( ) ;
const stationStore = useStationStore ( ) ;
const { stations } = storeToRefs ( stationStore ) ;
const { stations } = storeToRefs ( stationStore ) ;
const deviceStore = useDeviceStore ( ) ;
const deviceStore = useDeviceStore ( ) ;
const { lineDevices } = storeToRefs ( deviceStore ) ;
const { lineDevices } = storeToRefs ( deviceStore ) ;
const alarmStore = useAlarmStore ( ) ;
const alarmStore = useAlarmStore ( ) ;
const { lineAlarms } = storeToRefs ( alarmStore ) ;
const { lineAlarms } = storeToRefs ( alarmStore ) ;
const { refetch : refetchLineDevicesQuery } = useLineDevicesQuery ( ) ;
// 操作栏
// 当点击操作栏中的一个按钮时,其他按钮会被禁用
type Action = 'export-icmp' | 'export-record' | 'sync-camera' | 'sync-nvr' | null ;
const selectedAction = ref < Action > ( null ) ;
const showOperation = ref ( false ) ;
const stationSelectable = ref ( false ) ;
const stationSelection = ref < Record < Station [ ' code ' ] , boolean > > ( { } ) ;
const showIcmpExportModal = ref ( false ) ;
const showIcmpExportModal = ref ( false ) ;
const showRecordCheckExportModal = ref ( false ) ;
const showRecordCheckExportModal = ref ( false ) ;
const onToggleSelectAll = ( checked : boolean ) => {
const abortController = ref ( new AbortController ( ) ) ;
if ( ! checked ) {
stationSelection . value = { } ;
} else {
stationSelection . value = Object . fromEntries ( stations . value . map ( ( station ) => [ station . code , true ] ) ) ;
}
} ;
const onAction = ( action : Action ) => {
const { batchActions , selectedAction , selectableStations , stationSelection , toggleSelectAction , toggleSelectAllStations , confirmAction , cancelAction } = useBatchActions ( stations , abortController ) ;
selectedAction . value = action ;
if ( action === null ) return ;
showOperation . value = true ;
stationSelectable . value = true ;
} ;
const onCancel = ( ) => {
const { refetch : refetchLineDevicesQuery } = useLineDevicesQuery ( ) ;
selectedAction . value = null ;
showOperation . value = false ;
stationSelectable . value = false ;
stationSelection . value = { } ;
} ;
const onFinish = onCancel ;
const { mutate : syncCamera , isPending : cameraSyncing } = useMutation ( {
const { mutate : syncCamera , isPending : cameraSyncing } = useMutation ( {
mutationFn : async ( ) => {
mutationFn : async ( ) => {
abortController . value . abort ( ) ;
abortController . value = new AbortController ( ) ;
const signal = abortController . value . signal ;
const stationCodes = Object . entries ( stationSelection . value )
const stationCodes = Object . entries ( stationSelection . value )
. filter ( ( [ , selected ] ) => selected )
. filter ( ( [ , selected ] ) => selected )
. map ( ( [ code ] ) => code ) ;
. map ( ( [ code ] ) => code ) ;
const results = await Promise . allSettled ( stationCodes . map ( ( stationCode ) => syncCameraApi ( { stationCode } ) ) ) ;
const requests = await Promise . allSettled ( stationCodes . map ( ( stationCode ) => syncCameraApi ( { stationCode , signal } ) ) ) ;
return results . map ( ( result , index ) => ( { ... result , stationCode : stationCodes [ index ] } ) ) ;
return requests . map ( ( result , index ) => ( { ... result , stationCode : stationCodes [ index ] } ) ) ;
} ,
} ,
onSuccess : ( results ) => {
onSuccess : ( requests ) => {
const successCount = results . filter ( ( result ) => result . status === 'fulfilled' ) . length ;
type PromiseRequest = ( typeof requests ) [ number ] ;
const failures = results . filter ( ( result ) => result . status === 'rejected' ) ;
const successRequests : PromiseRequest [ ] = [ ] ;
const failureCount = failures . length ;
const failedRequests : PromiseRequest [ ] = [ ] ;
if ( failureCount > 0 ) {
const canceledRequests : PromiseRequest [ ] = [ ] ;
const failedStations = failures . map ( ( f ) => stations . value . find ( ( s ) => s . code === f . stationCode ) ? . name ) . join ( '、' ) ;
for ( const request of requests ) {
if ( successCount === 0 ) {
if ( request . status === 'fulfilled' ) {
window . $message . error ( '摄像机同步全部失败' ) ;
successRequests . push ( request ) ;
window . $message . error ( ` ${ failedStations } ` ) ;
} else if ( isCancel ( request . reason ) ) {
canceledRequests . push ( request ) ;
} else {
} else {
window . $message . warning ( ` 摄像机同步完成:成功 ${ successCount } 个车站,失败 ${ failureCount } 个车站 ` ) ;
failedRequests . push ( request ) ;
window . $message . warning ( ` ${ failedStations } ` ) ;
}
}
} else {
window . $message . success ( '摄像机同步成功' ) ;
}
}
if ( successCount > 0 ) {
const notices : string [ ] = [ ` 成功 ${ successRequests . length } 个车站 ` , ` 失败 ${ failedRequests . length } 个车站 ` ] ;
// 摄像机同步后,需要重新查询一次设备,待测试
if ( canceledRequests . length > 0 ) notices . push ( ` 取消 ${ canceledRequests . length } 个车站 ` ) ;
window . $notification . info ( {
title : '摄像机同步结果' ,
content : notices . join ( ', ' ) ,
duration : 3000 ,
} ) ;
if ( successRequests . length > 0 ) {
// 摄像机同步后,需要重新查询一次设备
refetchLineDevicesQuery ( ) ;
refetchLineDevicesQuery ( ) ;
}
}
onFinish ( ) ;
cancelAction ( ) ;
} ,
onError : ( error ) => {
console . error ( error ) ;
const errorFeedback = parseErrorFeedback ( error ) ;
window . $message . error ( errorFeedback ) ;
onCancel ( ) ;
} ,
} ,
} ) ;
} ) ;
const { mutate : syncNvrChannels , isPending : nvrChannelsSyncing } = useMutation ( {
const { mutate : syncNvrChannels , isPending : nvrChannelsSyncing } = useMutation ( {
mutationFn : async ( ) => {
mutationFn : async ( ) => {
abortController . value . abort ( ) ;
abortController . value = new AbortController ( ) ;
const signal = abortController . value . signal ;
const stationCodes = Object . entries ( stationSelection . value )
const stationCodes = Object . entries ( stationSelection . value )
. filter ( ( [ , selected ] ) => selected )
. filter ( ( [ , selected ] ) => selected )
. map ( ( [ code ] ) => code ) ;
. map ( ( [ code ] ) => code ) ;
const results = await Promise . allSettled ( stationCodes . map ( ( stationCode ) => syncNvrChannelsApi ( { stationCode } ) ) ) ;
const requests = await Promise . allSettled ( stationCodes . map ( ( stationCode ) => syncNvrChannelsApi ( { stationCode , signal } ) ) ) ;
return results . map ( ( result , index ) => ( { ... result , stationCode : stationCodes [ index ] } ) ) ;
return requests . map ( ( result , index ) => ( { ... result , stationCode : stationCodes [ index ] } ) ) ;
} ,
} ,
onSuccess : ( results ) => {
onSuccess : ( requests ) => {
const successCount = results . filter ( ( result ) => result . status === 'fulfilled' ) . length ;
type PromiseRequest = ( typeof requests ) [ number ] ;
const failures = results . filter ( ( result ) => result . status === 'rejected' ) ;
const successRequests : PromiseRequest [ ] = [ ] ;
const failureCount = failures . length ;
const failedRequests : PromiseRequest [ ] = [ ] ;
if ( failureCount > 0 ) {
const canceledRequests : PromiseRequest [ ] = [ ] ;
const failedStations = failures . map ( ( failure ) => stations . value . find ( ( station ) => station . code === failure . stationCode ) ? . name ) . join ( '、' ) ;
for ( const request of requests ) {
if ( successCount === 0 ) {
if ( request . status === 'fulfilled' ) {
window . $message . error ( '录像机通道同步全部失败' ) ;
successRequests . push ( request ) ;
window . $message . error ( ` ${ failedStations } ` ) ;
} else if ( isCancel ( request . reason ) ) {
canceledRequests . push ( request ) ;
} else {
} else {
window . $message . warning ( ` 录像机通道同步完成:成功 ${ successCount } 个车站,失败 ${ failureCount } 个车站 ` ) ;
failedRequests . push ( request ) ;
window . $message . warning ( ` ${ failedStations } ` ) ;
}
}
} else {
window . $message . success ( '录像机通道同步成功' ) ;
}
}
onFinish ( ) ;
const notices : string [ ] = [ ` 成功 ${ successRequests . length } 个车站 ` , ` 失败 ${ failedRequests . length } 个车站 ` ] ;
} ,
if ( canceledRequests . length > 0 ) notices . push ( ` 取消 ${ canceledRequests . length } 个车站 ` ) ;
onError : ( error ) => {
window . $notification . info ( {
console . error ( error ) ;
title : '录像机通道同步结果' ,
const errorFeedback = parseErrorFeedback ( error ) ;
content : notices . join ( ', ' ) ,
window . $message . error ( errorFeedback ) ;
duration : 3000 ,
onCancel ( ) ;
} ) ;
cancelAction ( ) ;
} ,
} ,
} ) ;
} ) ;
const confirming = computed ( ( ) => cameraSyncing . value || nvrChannelsSyncing . value ) ;
const confirming = computed ( ( ) => cameraSyncing . value || nvrChannelsSyncing . value ) ;
const onConfirm = async ( ) => {
const onClickConfirmAction = ( ) => {
const noStationSelected = ! Object . values ( stationSelection . value ) . some ( ( selected ) => selected ) ;
confirmAction ( {
if ( selectedAction . value === 'export-icmp' ) {
'export-icmp' : ( ) => {
if ( noStationSelected ) {
window . $message . warning ( '请选择要导出设备状态的车站' ) ;
return ;
}
showIcmpExportModal . value = true ;
showIcmpExportModal . value = true ;
} else if ( selectedAction . value === 'export-record' ) {
} ,
if ( noStationSelected ) {
'export-record' : ( ) => {
window . $message . warning ( '请选择要导出录像诊断的车站' ) ;
return ;
}
showRecordCheckExportModal . value = true ;
showRecordCheckExportModal . value = true ;
} else if ( selectedAction . value === 'sync-camera' ) {
} ,
if ( noStationSelected ) {
'sync-camera' : ( ) => {
window . $message . warning ( '请选择要同步摄像机的车站' ) ;
return ;
}
syncCamera ( ) ;
syncCamera ( ) ;
} else if ( selectedAction . value === 'sync-nvr' ) {
} ,
if ( noStationSelected ) {
'sync-nvr' : ( ) => {
window . $message . warning ( '请选择要同步录像机通道的车站' ) ;
return ;
}
syncNvrChannels ( ) ;
syncNvrChannels ( ) ;
} else {
} ,
return ;
} ) ;
}
} ;
} ;
// 车站卡片的事件
// 车站卡片的事件
const selectedStation = ref < Station > ( ) ;
const modalStation = ref < Station > ( ) ;
const showDeviceParamConfigModal = ref ( false ) ;
const showDeviceParamConfigModal = ref ( false ) ;
const showDeviceDetailModal = ref ( false ) ;
const showDeviceDetailModal = ref ( false ) ;
const showAlarmDetailModal = ref ( false ) ;
const showAlarmDetailModal = ref ( false ) ;
const onClickConfig : StationCardProps [ 'onClickConfig' ] = ( station ) => {
const onClickConfig : StationCardProps [ 'onClickConfig' ] = ( station ) => {
selectedStation . value = station ;
modalStation . value = station ;
showDeviceParamConfigModal . value = true ;
showDeviceParamConfigModal . value = true ;
} ;
} ;
const onClickDetail : StationCardProps [ 'onClickDetail' ] = ( type , station ) => {
const onClickDetail : StationCardProps [ 'onClickDetail' ] = ( type , station ) => {
selectedStation . value = station ;
modalStation . value = station ;
if ( type === 'device' ) {
if ( type === 'device' ) {
showDeviceDetailModal . value = true ;
showDeviceDetailModal . value = true ;
}
}
@@ -186,15 +157,19 @@ const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
<!-- 工具栏 -- >
<!-- 工具栏 -- >
< NFlex align = "center" style = "padding: 8px 8px 0 8px" >
< NFlex align = "center" style = "padding: 8px 8px 0 8px" >
< NButtonGroup >
< NButtonGroup >
< NButton secondary :focusable = "false" : disabled = "!!selectedAction && selectedAction !== 'export-icmp'" @click ="() => onAction('export-icmp')" > 导出设备状态 < / NButton >
< template v-for = "batchAction in batchActions" :key="batchAction.key" >
< NButton secondary :focusable = "false" : disabled = "!!selectedAction && selectedAction !== 'export-record'" @click ="() => onAction('export-record')" > 导出录像诊断 < / NButton >
< NButton :secondary = "!batchAction.active" :focusable = "false" @click ="() => toggleSelectAction(batchAction)" > {{ batchAction.label }} < / NButton >
< NButton secondary :focusable = "false" : disabled = "!!selectedAction && selectedAction !== 'sync-camera'" @click ="() => onAction('sync-camera')" > 同步摄像机 < / NButton >
< / template >
< NButton secondary :focusable = "false" : disabled = "!!selectedAction && selectedAction !== 'sync-nvr'" @click ="() => onAction('sync-nvr')" > 同步录像机通道 < / NButton >
< / NButtonGroup >
< / NButtonGroup >
< template v-if = "showOpera tion" >
< template v-if = "selectedAc tion" >
< NCheckbox label = "全选" @ update :checked = "onToggleSelectAll" / >
< NCheckbox
< NButton tertiary size = "small" type = "primary" :focusable = "false" :loading = "confirming" @click ="onConfirm" > 确定 < / NButton >
label = "全选"
< NButton tertiary size = "small" type = "tertiary" :focusable = "false" :disabled = "confirming" @click ="onCancel" > 取消 < / NButton >
: disabled = "selectableStations.length === 0"
: checked = "selectableStations.length > 0 && selectableStations.length === objectEntries(stationSelection).filter(([, selected]) => selected).length"
@ update :checked = "toggleSelectAllStations"
/ >
< NButton tertiary size = "small" type = "primary" :focusable = "false" :loading = "confirming" @click ="onClickConfirmAction" > 确定 < / NButton >
< NButton tertiary size = "small" type = "tertiary" :focusable = "false" @click ="cancelAction" > 取消 < / NButton >
< / template >
< / template >
< / NFlex >
< / NFlex >
@@ -205,7 +180,7 @@ const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
:station = "station"
:station = "station"
: devices = "lineDevices[station.code] ?? initStationDevices()"
: devices = "lineDevices[station.code] ?? initStationDevices()"
: alarms = "lineAlarms[station.code] ?? initStationAlarms()"
: alarms = "lineAlarms[station.code] ?? initStationAlarms()"
:selectable = "stationSelectable "
: selectable = "!!selectableStations.find((selectable) => selectable.code === station.code) "
v -model :selected = "stationSelection[station.code]"
v -model :selected = "stationSelection[station.code]"
@ click -detail = " onClickDetail "
@ click -detail = " onClickDetail "
@ click -config = " onClickConfig "
@ click -config = " onClickConfig "
@@ -214,12 +189,12 @@ const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
< / NGrid >
< / NGrid >
< / NScrollbar >
< / NScrollbar >
< IcmpExportModal v -model :show = "showIcmpExportModal" : stations = "stations.filter((station) => stationSelection[station.code])" @ after -leave = " onFinish " / >
< IcmpExportModal v -model :show = "showIcmpExportModal" : stations = "stations.filter((station) => stationSelection[station.code])" @ after -leave = " cancelAction " / >
< RecordCheckExportModal v -model :show = "showRecordCheckExportModal" : stations = "stations.filter((station) => stationSelection[station.code])" @ after -leave = " onFinish " / >
< RecordCheckExportModal v -model :show = "showRecordCheckExportModal" : stations = "stations.filter((station) => stationSelection[station.code])" @ after -leave = " cancelAction " / >
< DeviceParamConfigModal v -model :show = "showDeviceParamConfigModal" :station = "selected Station" / >
< DeviceParamConfigModal v -model :show = "showDeviceParamConfigModal" :station = "modal Station" / >
< DeviceDetailModal v -model :show = "showDeviceDetailModal" :station = "selected Station" / >
< DeviceDetailModal v -model :show = "showDeviceDetailModal" :station = "modal Station" / >
< AlarmDetailModal v -model :show = "showAlarmDetailModal" :station = "selected Station" / >
< AlarmDetailModal v -model :show = "showAlarmDetailModal" :station = "modal Station" / >
< / template >
< / template >
< style scoped lang = "scss" > < / style >
< style scoped lang = "scss" > < / style >