feat: 添加告警画面截图相关设置

- 配置告警画面截图保留天数
- 是否自动获取告警画面截图
This commit is contained in:
yangsy
2025-12-30 15:14:48 +08:00
parent e112b01a21
commit 286449b831
6 changed files with 165 additions and 15 deletions

View File

@@ -6,4 +6,5 @@ export * from './icmp';
export * from './log';
export * from './storage';
export * from './other';
export * from './upper-ndm';
export * from './video';

View File

@@ -0,0 +1 @@
export * from './upper-ndm';

View File

@@ -0,0 +1,20 @@
import { ndmClient, userClient, type Station } from '@/apis';
import { unwrapResponse } from '@/utils';
export async function snapStatusApi(method: 'get', options?: { stationCode?: Station['code']; signal?: AbortSignal }): Promise<boolean>;
export async function snapStatusApi(method: 'post', options: { doSnap: boolean; stationCode?: Station['code']; signal?: AbortSignal }): Promise<boolean>;
export async function snapStatusApi(method: 'get' | 'post', options?: { doSnap?: boolean; stationCode?: Station['code']; signal?: AbortSignal }) {
const { doSnap, stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/anyTenant/snapStatus`;
if (method === 'get') {
const resp = await client.get<boolean>(endpoint, { signal });
const data = unwrapResponse(resp);
return data;
} else {
const resp = await client.post<boolean>(endpoint, doSnap, { signal });
const data = unwrapResponse(resp);
return data;
}
}

View File

@@ -3,4 +3,5 @@ export * from './ndm-camera-ignore';
export * from './ndm-decoder';
export * from './ndm-keyboard';
export * from './ndm-media-server';
export * from './ndm-snap';
export * from './ndm-video-server';

View File

@@ -0,0 +1,20 @@
import { ndmClient, userClient, type Station } from '@/apis';
import { unwrapResponse } from '@/utils';
export async function retentionDaysApi(method: 'get', options?: { stationCode?: Station['code']; signal?: AbortSignal }): Promise<number>;
export async function retentionDaysApi(method: 'post', options: { days: number; stationCode?: Station['code']; signal?: AbortSignal }): Promise<number>;
export async function retentionDaysApi(method: 'get' | 'post', options?: { days?: number; stationCode?: Station['code']; signal?: AbortSignal }) {
const { days, stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/ndmSnap/retentionDays`;
if (method === 'get') {
const resp = await client.get<number>(endpoint, { signal });
const data = unwrapResponse(resp);
return data;
} else {
const resp = await client.post<number>(endpoint, days, { signal });
const data = unwrapResponse(resp);
return data;
}
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { LineAlarms, LineDevices, Station, VersionInfo } from '@/apis';
import { retentionDaysApi, snapStatusApi, type LineAlarms, type LineDevices, type Station, type VersionInfo } from '@/apis';
import { ThemeSwitch } from '@/components';
import { NDM_ALARM_STORE_ID, NDM_DEVICE_STORE_ID, NDM_STATION_STORE_ID } from '@/constants';
import { usePollingStore, useSettingStore } from '@/stores';
@@ -7,16 +7,13 @@ import { downloadByData, getAppEnvConfig, parseErrorFeedback, sleep } from '@/ut
import { useMutation } from '@tanstack/vue-query';
import { DeleteOutlined, ExportOutlined, ImportOutlined } from '@vicons/antd';
import { useEventListener } from '@vueuse/core';
import axios from 'axios';
import axios, { isCancel } from 'axios';
import destr from 'destr';
import { isFunction } from 'es-toolkit';
import localforage from 'localforage';
import { NButton, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, type DropdownOption } from 'naive-ui';
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, type DropdownOption } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
import { ref, watch } from 'vue';
const show = defineModel<boolean>('show', { default: false });
@@ -40,6 +37,83 @@ const { mutate: getVersionInfo } = useMutation({
},
});
const abortControllers = ref({
retentionDays: new AbortController(),
snapStatus: new AbortController(),
});
const retentionDays = ref(0);
const { mutate: getRetentionDays, isPending: retentionDaysLoading } = useMutation({
mutationFn: async () => {
abortControllers.value.retentionDays.abort();
abortControllers.value.retentionDays = new AbortController();
const signal = abortControllers.value.retentionDays.signal;
const days = await retentionDaysApi('get', { signal });
return days;
},
onSuccess: (days) => {
retentionDays.value = days;
},
onError: (error) => {
if (isCancel(error)) return;
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
const { mutate: saveRetentionDays, isPending: retentionDaysSaving } = useMutation({
mutationFn: async () => {
abortControllers.value.retentionDays.abort();
abortControllers.value.retentionDays = new AbortController();
const signal = abortControllers.value.retentionDays.signal;
await retentionDaysApi('post', { days: retentionDays.value, signal });
},
onError: (error) => {
if (isCancel(error)) return;
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
// 修改失败,刷新 retentionDays
getRetentionDays();
},
});
const snapStatus = ref(false);
const { mutate: getSnapStatus, isPending: snapStatusLoading } = useMutation({
mutationFn: async () => {
abortControllers.value.snapStatus.abort();
abortControllers.value.snapStatus = new AbortController();
const signal = abortControllers.value.snapStatus.signal;
const status = await snapStatusApi('get', { signal });
return status;
},
onSuccess: (status) => {
snapStatus.value = status;
},
onError: (error) => {
if (isCancel(error)) return;
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
const { mutate: saveSnapStatus, isPending: snapStatusSaving } = useMutation({
mutationFn: async () => {
abortControllers.value.snapStatus.abort();
abortControllers.value.snapStatus = new AbortController();
const signal = abortControllers.value.snapStatus.signal;
await snapStatusApi('post', { doSnap: snapStatus.value, signal });
},
onError: (error) => {
if (isCancel(error)) return;
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
// 修改失败,刷新 snapStatus
getSnapStatus();
},
});
const showDebugCodeModal = ref(false);
const debugCode = ref('');
const enableDebugMode = () => {
@@ -192,13 +266,28 @@ const onSelectDropdownOption = (key: string, option: DropdownOption) => {
}
};
onMounted(() => {
getVersionInfo();
watch([offlineDev, show], ([offline, entered]) => {
if (!offline) {
if (entered) {
getRetentionDays();
getSnapStatus();
} else {
abortControllers.value.retentionDays.abort();
abortControllers.value.snapStatus.abort();
}
}
});
const onDrawerAfterEnter = () => {
getVersionInfo();
};
const onDrawerAfterLeave = () => {
abortControllers.value.retentionDays.abort();
abortControllers.value.snapStatus.abort();
};
</script>
<template>
<NDrawer v-model:show="show" :width="560" :auto-focus="false">
<NDrawer v-model:show="show" :width="560" :auto-focus="false" @after-enter="onDrawerAfterEnter" @after-leave="onDrawerAfterLeave">
<NDrawerContent closable title="系统设置" :native-scrollbar="false">
<NFlex vertical>
<NDivider>主题</NDivider>
@@ -210,11 +299,29 @@ onMounted(() => {
<NFormItem label="折叠菜单" label-placement="left">
<NSwitch size="small" v-model:value="menuCollpased" />
</NFormItem>
<template v-if="route.path === '/station'">
<NFormItem label="车站列数" label-placement="left">
<NInputNumber v-model:value="stationGridCols" :min="1" :max="10" />
</NFormItem>
</template>
<NFormItem label="车站列数" label-placement="left">
<NInputNumber v-model:value="stationGridCols" :min="1" :max="10" />
</NFormItem>
<NDivider>告警</NDivider>
<NFormItem label="告警画面截图保留天数" label-placement="left">
<NFlex justify="space-between" align="center" style="width: 100%">
<NInputNumber v-model:value="retentionDays" :min="1" :max="15" />
<NButtonGroup>
<NButton secondary size="small" :disabled="retentionDaysSaving" :loading="retentionDaysLoading" @click="() => getRetentionDays()">刷新</NButton>
<NButton secondary size="small" :disabled="retentionDaysLoading" :loading="retentionDaysSaving" @click="() => saveRetentionDays()">保存</NButton>
</NButtonGroup>
</NFlex>
</NFormItem>
<NFormItem label="自动获取告警画面截图" label-placement="left">
<NFlex justify="space-between" align="center" style="width: 100%">
<NSwitch size="small" v-model:value="snapStatus" />
<NButtonGroup>
<NButton secondary size="small" :disabled="snapStatusSaving" :loading="snapStatusLoading" @click="() => getSnapStatus()">刷新</NButton>
<NButton secondary size="small" :disabled="snapStatusLoading" :loading="snapStatusSaving" @click="() => saveSnapStatus()">保存</NButton>
</NButtonGroup>
</NFlex>
</NFormItem>
<template v-if="debugModeEnabled">
<NDivider title-placement="center">调试</NDivider>