Files
ndm-web-client/src/components/device-page/device-card/current-diag-card/nvr-record-diag-card.vue
2025-11-27 16:01:50 +08:00

216 lines
7.8 KiB
Vue

<script setup lang="ts">
import type { NdmNvrResultVO, NdmRecordCheck, RecordItem, Station } from '@/apis';
import { getChannelListApi, getRecordCheckApi as getRecordCheckByParentIdApi, reloadAllRecordCheckApi as reloadAllRecordCheckApi, reloadRecordCheckApi as reloadRecordCheckByGbIdApi } from '@/apis';
import { exportRecordDiagCsv, transformRecordChecks } from '@/helper';
import { useStationStore } from '@/stores';
import { useMutation } from '@tanstack/vue-query';
import { DownloadOutlined } from '@vicons/antd';
import { RefreshOutline } from '@vicons/ionicons5';
import dayjs from 'dayjs';
import { NButton, NCard, NFlex, NIcon, NPagination, NPopconfirm, NPopover, NRadioButton, NRadioGroup, NTooltip, useThemeVars } from 'naive-ui';
import { computed, onMounted, ref, toRefs } from 'vue';
const props = defineProps<{
stationCode: Station['code'];
ndmNvr: NdmNvrResultVO;
}>();
const { stationCode, ndmNvr } = toRefs(props);
const recordChecks = ref<NdmRecordCheck[]>([]);
const lossInput = ref<number>(0);
const recordDiags = computed(() => {
return transformRecordChecks(recordChecks.value).filter((recordDiag) => {
if (lossInput.value === 0) {
return true;
} else if (lossInput.value === 1) {
return recordDiag.lostChunks.length > 0;
} else if (lossInput.value === 2) {
return recordDiag.lostChunks.length === 0;
}
return false;
});
});
const { mutate: getRecordCheckByParentId, isPending: loading } = useMutation({
mutationFn: async () => {
const checks = await getRecordCheckByParentIdApi(ndmNvr.value, 90, [], { stationCode: stationCode.value });
return checks;
},
onSuccess: (checks) => {
recordChecks.value = checks;
},
onError: (error) => {
console.error(error);
window.$message.error(error.message);
},
});
const { mutate: reloadAllRecordCheck, isPending: reloading } = useMutation({
mutationFn: async () => {
await reloadAllRecordCheckApi(90, { stationCode: stationCode.value });
},
onSuccess: () => {
window.$message.success('正在逐步刷新中,请稍后点击刷新按钮查看');
},
onError: (error) => {
console.error(error);
window.$message.error(error.message);
},
});
const onExportRecordCheck = () => {
const stationStore = useStationStore();
const stationName = stationStore.stationList.find((station) => station.code === stationCode.value)?.name ?? '';
exportRecordDiagCsv(recordDiags.value, stationName);
};
const page = ref(1);
const pageSize = ref(10);
const pagedRecordDiags = computed(() => {
const startIndex = (page.value - 1) * pageSize.value;
const endIndex = page.value * pageSize.value;
return recordDiags.value.slice(startIndex, endIndex);
});
const getLostChunkDOMStyle = (lostChunk: RecordItem, duration: RecordItem) => {
const chunk = dayjs(lostChunk.endTime).diff(dayjs(lostChunk.startTime));
const offset = dayjs(lostChunk.startTime).diff(dayjs(duration.startTime));
const total = dayjs(duration.endTime).diff(dayjs(duration.startTime));
return {
left: `${(offset / total) * 100}%`,
width: `${(chunk / total) * 100}%`,
};
};
const { mutate: reloadRecordCheckByGbId } = useMutation({
mutationFn: async (params: { gbCode: string }) => {
const channelList = await getChannelListApi(ndmNvr.value, { stationCode: stationCode.value });
const channel = channelList.find((channel) => channel.code === params.gbCode);
if (!channel) throw new Error('通道不存在');
window.$message.loading('刷新耗时较长, 请不要多次刷新, 并耐心等待...', {
duration: 1000 * 60 * 60 * 24 * 300,
});
const isSuccess = await reloadRecordCheckByGbIdApi(channel, 90, { stationCode: stationCode.value });
window.$message.destroyAll();
if (isSuccess) {
window.$message.success('刷新成功');
} else {
window.$message.error('刷新失败');
}
},
onSuccess: () => {
getRecordCheckByParentId();
},
onError: (error) => {
console.error(error);
window.$message.error(error.message);
},
});
onMounted(() => {
getRecordCheckByParentId();
});
const themeVars = useThemeVars();
</script>
<template>
<NCard size="small" hoverable>
<template #header>
<NFlex align="center" :size="24">
<div>录像诊断</div>
<NPopconfirm @positive-click="() => reloadAllRecordCheck()">
<template #trigger>
<NButton secondary size="small" :loading="reloading">更新所有通道录像诊断</NButton>
</template>
<template #default>
<span>确认更新所有通道录像诊断吗?</span>
</template>
</NPopconfirm>
</NFlex>
</template>
<template #header-extra>
<NFlex>
<NTooltip trigger="hover">
<template #trigger>
<NButton size="small" quaternary circle :loading="loading" @click="() => getRecordCheckByParentId()">
<template #icon>
<NIcon>
<RefreshOutline />
</NIcon>
</template>
</NButton>
</template>
<template #default>
<span>刷新数据</span>
</template>
</NTooltip>
<NTooltip trigger="hover">
<template #trigger>
<NButton size="small" quaternary circle @click="onExportRecordCheck">
<template #icon>
<NIcon>
<DownloadOutlined />
</NIcon>
</template>
</NButton>
</template>
<template #default>
<span>导出录像诊断</span>
</template>
</NTooltip>
</NFlex>
</template>
<template #default>
<NFlex justify="flex-end" style="margin-bottom: 6px">
<NRadioGroup size="small" v-model:value="lossInput">
<NRadioButton label="全部" :value="0" />
<NRadioButton label="有缺失" :value="1" />
<NRadioButton label="无缺失" :value="2" />
</NRadioGroup>
</NFlex>
<template v-for="{ gbCode, channelName, recordDuration, lostChunks } in pagedRecordDiags" :key="gbCode">
<div style="display: flex; justify-content: space-between">
<div>
<span>{{ channelName }}</span>
<span>{{ '\u3000' }}</span>
<span>{{ recordDuration.startTime }} - {{ recordDuration.endTime }}</span>
</div>
<NPopconfirm trigger="click" @positive-click="() => reloadRecordCheckByGbId({ gbCode })">
<template #trigger>
<NButton ghost size="tiny" type="info">刷新</NButton>
</template>
<template #default>
<span>是否确认刷新?</span>
</template>
</NPopconfirm>
</div>
<div style="position: relative; height: 24px; margin: 2px 0" :style="{ backgroundColor: lostChunks.length > 0 ? themeVars.infoColor : themeVars.successColor }">
<template v-for="{ startTime, endTime } in lostChunks" :key="`${startTime}-${endTime}`">
<NPopover trigger="hover">
<template #trigger>
<div style="position: absolute; height: 100%; cursor: pointer; background-color: #eee" :style="getLostChunkDOMStyle({ startTime, endTime }, recordDuration)" />
</template>
<template #default>
<div>开始时间:{{ dayjs(startTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
<div>结束时间:{{ dayjs(endTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
</template>
</NPopover>
</template>
</div>
</template>
</template>
<template #action>
<NFlex justify="flex-end">
<NPagination size="small" :page="page" :page-size="pageSize" :page-count="Math.ceil(recordDiags.length / pageSize)" @update:page="(p) => (page = p)" />
</NFlex>
</template>
</NCard>
</template>
<style scoped lang="scss"></style>