refactor: formatDuration & filterLostRecordList
This commit is contained in:
@@ -57,7 +57,7 @@ export interface NdmRecordCheck extends BaseModel {
|
|||||||
parentGbCode: string;
|
parentGbCode: string;
|
||||||
name: string;
|
name: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
diagInfo: string | RecordInfo;
|
diagInfo: string; // RecordInfo
|
||||||
checkDate: string;
|
checkDate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1,81 @@
|
|||||||
<script lang="ts">
|
<script lang="ts"></script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { NdmNvrResultVO, NdmRecordCheck, RecordInfo, RecordItem } from '@/apis/models';
|
||||||
|
import { getRecordCheckByParentId as getRecordCheckByParentIdApi, reloadAllRecordCheck as reloadAllRecordCheckApi } from '@/apis/requests';
|
||||||
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
|
import { formatDuration } from '@/utils/format-duration';
|
||||||
|
import { VideocamOutline, TimeOutline, WarningOutline, CheckmarkCircleOutline, RefreshOutline } from '@vicons/ionicons5';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { destr } from 'destr';
|
||||||
|
import { groupBy } from 'es-toolkit';
|
||||||
|
import { NCard, NFlex, NText, NTag, NTimeline, NTimelineItem, NIcon, NEmpty, NStatistic, NGrid, NGridItem, NCollapse, NCollapseItem, NButton, NPopconfirm } from 'naive-ui';
|
||||||
|
import { computed, onMounted, ref, toRefs } from 'vue';
|
||||||
|
|
||||||
type NvrRecordDiag = {
|
type NvrRecordDiag = {
|
||||||
gbCode: string;
|
gbCode: string;
|
||||||
recordName: string;
|
recordName: string;
|
||||||
recordDuration: {
|
recordDuration: RecordItem;
|
||||||
startTime: string;
|
lostRecordList: RecordItem[];
|
||||||
endTime: string;
|
|
||||||
};
|
|
||||||
lostRecordList: {
|
|
||||||
startTime: string;
|
|
||||||
endTime: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 过滤出丢失的录像时间段
|
// 过滤出丢失的录像时间段
|
||||||
const filterLostRecordList = (recordCheckRawData: any): NvrRecordDiag[] => {
|
const filterLostRecordList = (rawRecordChecks: NdmRecordCheck[]): NvrRecordDiag[] => {
|
||||||
const rawData = (recordCheckRawData as any[]).map((item) => {
|
// 1. 解析diagInfo
|
||||||
|
const recordChecks = rawRecordChecks.map((recordCheck) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...recordCheck,
|
||||||
diagInfo: JSON.parse(item.diagInfo),
|
diagInfo: destr<RecordInfo>(recordCheck.diagInfo),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// 按国标码分组
|
// 2.按国标码分组
|
||||||
const groupedData = groupBy(rawData, (item) => item.gbCode);
|
const recordChecksByGbCode = groupBy(recordChecks, (recordCheck) => recordCheck.gbCode);
|
||||||
// 拆分keys和values
|
// 3. 提取分组后的国标码和录像诊断记录
|
||||||
const gbCodeList = Object.keys(groupedData);
|
const channelGbCodes = Object.keys(recordChecksByGbCode);
|
||||||
const groupedDiagRecordsList = Object.values(groupedData);
|
const groupedRecordChecks = Object.values(recordChecksByGbCode);
|
||||||
// 初始化
|
// 4. 初始化每个通道的诊断数据结构
|
||||||
let recordChunks = gbCodeList.map((gbCode, index) => ({
|
let recordDiagList = channelGbCodes.map((gbCode, index) => ({
|
||||||
gbCode,
|
gbCode,
|
||||||
recordName: groupedDiagRecordsList[index].at(-1).name,
|
recordName: groupedRecordChecks[index].at(-1)?.name ?? '',
|
||||||
videoList: [] as any[],
|
records: [] as RecordItem[],
|
||||||
lostVideoList: [] as any[],
|
lostRecords: [] as RecordItem[],
|
||||||
}));
|
}));
|
||||||
// 合并同一gbCode的录像时间记录
|
// 5. 写入同一gbCode的录像片段
|
||||||
groupedDiagRecordsList.forEach((records, index) => {
|
groupedRecordChecks.forEach((recordCheckGroup, index) => {
|
||||||
records.forEach((record) => {
|
recordCheckGroup.forEach((recordCheck) => {
|
||||||
recordChunks[index].videoList.push(...record.diagInfo.recordList);
|
recordDiagList[index].records.push(...recordCheck.diagInfo.recordList);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
recordChunks = recordChunks.filter((chunk) => chunk.videoList.length > 0);
|
// 6. 过滤掉没有录像记录的通道
|
||||||
// 计算丢失的录像时间段
|
recordDiagList = recordDiagList.filter((chunk) => chunk.records.length > 0);
|
||||||
recordChunks.forEach((chunk) => {
|
// 7. 计算每个通道丢失的录像时间片段
|
||||||
chunk.videoList.forEach((video, index, videoList) => {
|
recordDiagList.forEach((chunk) => {
|
||||||
if (videoList[index + 1]) {
|
chunk.records.forEach((record, index, recordList) => {
|
||||||
// 如果时间不连续,则初步判断录像出现丢失
|
if (recordList[index + 1]) {
|
||||||
if (videoList[index + 1].startTime !== video.endTime) {
|
// 如果下一段录像的开始时间不等于当前录像的结束时间,则判定为丢失
|
||||||
chunk.lostVideoList.push({
|
if (recordList[index + 1].startTime !== record.endTime) {
|
||||||
startTime: video.endTime,
|
chunk.lostRecords.push({
|
||||||
endTime: videoList[index + 1].startTime,
|
startTime: record.endTime,
|
||||||
|
endTime: recordList[index + 1].startTime,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return recordChunks.map((chunk) => ({
|
return recordDiagList.map((recordDiag) => ({
|
||||||
gbCode: chunk.gbCode,
|
gbCode: recordDiag.gbCode,
|
||||||
recordName: chunk.recordName,
|
recordName: recordDiag.recordName,
|
||||||
recordDuration: {
|
recordDuration: {
|
||||||
startTime: chunk.videoList[0].startTime,
|
startTime: recordDiag.records.at(0)?.startTime ?? '',
|
||||||
endTime: chunk.videoList.at(-1).endTime,
|
endTime: recordDiag.records.at(-1)?.endTime ?? '',
|
||||||
},
|
},
|
||||||
lostRecordList: chunk.lostVideoList,
|
lostRecordList: recordDiag.lostRecords,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
const formatTime = (time: string) => {
|
||||||
import type { ClientChannel, NdmNvrResultVO, NdmRecordCheck } from '@/apis/models';
|
return dayjs(time).format('MM-DD HH:mm:ss');
|
||||||
import { getChannelList as getChannelListApi, getRecordCheckByParentId as getRecordCheckByParentIdApi, reloadAllRecordCheck as reloadAllRecordCheckApi } from '@/apis/requests';
|
};
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
|
||||||
import { VideocamOutline, TimeOutline, WarningOutline, CheckmarkCircleOutline, RefreshOutline } from '@vicons/ionicons5';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import duration from 'dayjs/plugin/duration';
|
|
||||||
import { groupBy } from 'es-toolkit';
|
|
||||||
import { NCard, NFlex, NText, NTag, NTimeline, NTimelineItem, NIcon, NEmpty, NStatistic, NGrid, NGridItem, NCollapse, NCollapseItem, NButton, NPopconfirm } from 'naive-ui';
|
|
||||||
import { computed, onMounted, ref, toRefs } from 'vue';
|
|
||||||
|
|
||||||
dayjs.extend(duration);
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
stationCode: string;
|
stationCode: string;
|
||||||
@@ -85,45 +84,11 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const { stationCode, ndmNvr } = toRefs(props);
|
const { stationCode, ndmNvr } = toRefs(props);
|
||||||
|
|
||||||
const clientChannelList = ref<ClientChannel[]>([]);
|
|
||||||
const recordCheckList = ref<NdmRecordCheck[]>([]);
|
const recordCheckList = ref<NdmRecordCheck[]>([]);
|
||||||
|
|
||||||
const lostRecordList = computed(() => filterLostRecordList(recordCheckList.value));
|
const lostRecordList = computed(() => filterLostRecordList(recordCheckList.value));
|
||||||
|
|
||||||
// 时间格式化函数
|
const recordDiagStatistics = computed(() => {
|
||||||
const formatTime = (timeStr: string) => {
|
|
||||||
return dayjs(timeStr).format('MM-DD HH:mm:ss');
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDuration = (startTime: string, endTime: string) => {
|
|
||||||
const start = dayjs(startTime);
|
|
||||||
const end = dayjs(endTime);
|
|
||||||
const diffMs = end.diff(start);
|
|
||||||
|
|
||||||
const isNegative = diffMs < 0;
|
|
||||||
const durAbs = dayjs.duration(Math.abs(diffMs));
|
|
||||||
|
|
||||||
const hours = Math.floor(durAbs.asHours());
|
|
||||||
const minutes = durAbs.minutes();
|
|
||||||
const seconds = durAbs.seconds();
|
|
||||||
|
|
||||||
let result: string;
|
|
||||||
if (hours > 0) {
|
|
||||||
result = `${hours}小时${minutes}分${seconds}秒`;
|
|
||||||
} else if (minutes > 0) {
|
|
||||||
result = `${minutes}分${seconds}秒`;
|
|
||||||
} else {
|
|
||||||
result = `${seconds}秒`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isNegative ? `-${result}` : result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 录像诊断统计信息计算
|
|
||||||
* 基于lostRecordList数据计算各项统计指标,用于在UI顶部展示概览信息
|
|
||||||
*/
|
|
||||||
const recordStats = computed(() => {
|
|
||||||
// 总通道数:参与录像诊断的通道总数
|
// 总通道数:参与录像诊断的通道总数
|
||||||
const totalChannels = lostRecordList.value.length;
|
const totalChannels = lostRecordList.value.length;
|
||||||
|
|
||||||
@@ -157,16 +122,6 @@ const recordStats = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const {} = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const channelList = await getChannelListApi(stationCode.value, ndmNvr.value);
|
|
||||||
return channelList;
|
|
||||||
},
|
|
||||||
onSuccess: (channelList) => {
|
|
||||||
clientChannelList.value = channelList;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: getRecordCheckByParentId, isPending: loading } = useMutation({
|
const { mutate: getRecordCheckByParentId, isPending: loading } = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const recordCheckList = await getRecordCheckByParentIdApi(stationCode.value, ndmNvr.value, 90);
|
const recordCheckList = await getRecordCheckByParentIdApi(stationCode.value, ndmNvr.value, 90);
|
||||||
@@ -225,20 +180,20 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
<!-- 统计信息 -->
|
|
||||||
<NFlex vertical :size="16">
|
<NFlex vertical :size="16">
|
||||||
|
<!-- 统计信息 -->
|
||||||
<NGrid :cols="4" :x-gap="12">
|
<NGrid :cols="4" :x-gap="12">
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NStatistic label="总通道数" :value="recordStats.totalChannels" />
|
<NStatistic label="总通道数" :value="recordDiagStatistics.totalChannels" />
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NStatistic label="有缺失通道" :value="recordStats.channelsWithLoss" />
|
<NStatistic label="有缺失通道" :value="recordDiagStatistics.channelsWithLoss" />
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NStatistic label="缺失次数" :value="recordStats.totalLossCount" />
|
<NStatistic label="缺失次数" :value="recordDiagStatistics.totalLossCount" />
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NStatistic label="缺失时长" :value="recordStats.formattedTotalDuration" />
|
<NStatistic label="缺失时长" :value="recordDiagStatistics.formattedTotalDuration" />
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,7 @@
|
|||||||
<script lang="ts">
|
|
||||||
const formatDuration = (time: number) => {
|
|
||||||
if (time < 1) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const d = Math.floor(time / 86400);
|
|
||||||
const h = Math.floor((time % 86400) / 3600);
|
|
||||||
const m = Math.floor((time % 3600) / 60);
|
|
||||||
const s = Math.floor(time % 60);
|
|
||||||
let result = '';
|
|
||||||
if (d > 0) {
|
|
||||||
result += `${d}天`;
|
|
||||||
}
|
|
||||||
if (h > 0) {
|
|
||||||
result += `${h}小时`;
|
|
||||||
}
|
|
||||||
if (m > 0) {
|
|
||||||
result += `${m}分钟`;
|
|
||||||
}
|
|
||||||
if (s > 0) {
|
|
||||||
result += `${s}秒`;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmDeviceResultVO, NdmIcmpLogResultVO, NdmIcmpLogVO, PageParams, PageResult } from '@/apis/models';
|
import type { NdmDeviceResultVO, NdmIcmpLogResultVO, NdmIcmpLogVO, PageParams, PageResult } from '@/apis/models';
|
||||||
import { postIcmpLogPage } from '@/apis/requests';
|
import { postIcmpLogPage } from '@/apis/requests';
|
||||||
|
import { formatDuration } from '@/utils/format-duration';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { NCard, NPagination, NScrollbar, NTimeline, NTimelineItem, type DatePickerProps, type PaginationProps, type TimelineItemProps } from 'naive-ui';
|
import { NCard, NPagination, NScrollbar, NTimeline, NTimelineItem, type DatePickerProps, type PaginationProps, type TimelineItemProps } from 'naive-ui';
|
||||||
@@ -113,8 +88,7 @@ const timelineItems = computed<TimelineItemProps[]>(() => {
|
|||||||
const { deviceStatus, createdTime } = icmpLog;
|
const { deviceStatus, createdTime } = icmpLog;
|
||||||
const type: TimelineItemProps['type'] = deviceStatus === '10' ? 'success' : deviceStatus === '20' ? 'error' : 'warning';
|
const type: TimelineItemProps['type'] = deviceStatus === '10' ? 'success' : deviceStatus === '20' ? 'error' : 'warning';
|
||||||
const title = deviceStatus === '10' ? '在线' : deviceStatus === '20' ? '离线' : '未知';
|
const title = deviceStatus === '10' ? '在线' : deviceStatus === '20' ? '离线' : '未知';
|
||||||
const duration = prevIcmpLog ? dayjs(prevIcmpLog.createdTime).diff(dayjs(createdTime), 'second') : undefined;
|
const content = `持续时长:${prevIcmpLog ? formatDuration(createdTime, prevIcmpLog.createdTime) : '至今'}`;
|
||||||
const content = `持续时长:${duration ? formatDuration(duration) : '至今'}`;
|
|
||||||
const time = createdTime;
|
const time = createdTime;
|
||||||
items.push({
|
items.push({
|
||||||
type,
|
type,
|
||||||
|
|||||||
22
src/utils/format-duration.ts
Normal file
22
src/utils/format-duration.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
export const formatDuration = (startTime?: string, endTime?: string) => {
|
||||||
|
if (!startTime || !endTime) return '';
|
||||||
|
const start = dayjs(startTime);
|
||||||
|
const end = dayjs(endTime);
|
||||||
|
const diffMillis = end.diff(start, 'second');
|
||||||
|
const duration = dayjs.duration(Math.abs(diffMillis), 'second');
|
||||||
|
const d = Math.floor(duration.asDays());
|
||||||
|
const h = duration.hours();
|
||||||
|
const m = duration.minutes();
|
||||||
|
const s = duration.seconds();
|
||||||
|
let result = '';
|
||||||
|
if (d > 0) result += `${d}天`;
|
||||||
|
if (h > 0) result += `${h}小时`;
|
||||||
|
if (m > 0) result += `${m}分钟`;
|
||||||
|
if (s > 0) result += `${s}秒`;
|
||||||
|
return diffMillis < 0 ? `-${result}` : result;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user