Commit eeb8e4d1db99b0dec1563a0bd01e085cd4289b8e

Authored by ww
1 parent 400760d8

feat: add global alarm notify

... ... @@ -42,3 +42,5 @@ VITE_GLOB_CONFIGURATION = /thingskit-drawio
42 42 # Content Security Policy
43 43 VITE_CONTENT_SECURITY_POLICY = false
44 44
  45 +# Alarm Notify Polling Interval Time
  46 +VITE_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 5000
... ...
... ... @@ -42,3 +42,6 @@ VITE_GLOB_CONFIGURATION = /thingskit-scada
42 42
43 43 # Content Security Policy
44 44 VITE_CONTENT_SECURITY_POLICY = false
  45 +
  46 +# Alarm Notify Polling Interval Time
  47 +VITE_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 60000
... ...
... ... @@ -8,6 +8,7 @@
8 8
9 9 <script lang="ts" setup>
10 10 import { ConfigProvider } from 'ant-design-vue';
  11 + import { useAlarmNotify } from './views/alarm/log/hook/useAlarmNotify';
11 12 import { AppProvider } from '/@/components/Application';
12 13 import { useTitle } from '/@/hooks/web/useTitle';
13 14 import { useLocale } from '/@/locales/useLocale';
... ... @@ -15,4 +16,5 @@
15 16 const { getAntdLocale } = useLocale();
16 17
17 18 useTitle();
18   -</script>
\ No newline at end of file
  19 + useAlarmNotify();
  20 +</script>
... ...
... ... @@ -6,6 +6,8 @@ import {
6 6 DeviceQueryParam,
7 7 } from '/@/api/device/model/deviceModel';
8 8 import { ChildDeviceParams } from './model/deviceModel';
  9 +import { PaginationResult } from '/#/axios';
  10 +import { AlarmLogItem } from './model/deviceConfigModel';
9 11 enum DeviceManagerApi {
10 12 /**
11 13 * 设备URL
... ... @@ -94,7 +96,7 @@ export const getDeviceDetail = (id: string) => {
94 96
95 97 // 查询设备详情中的告警
96 98 export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => {
97   - return defHttp.get({
  99 + return defHttp.get<PaginationResult<AlarmLogItem>>({
98 100 url: DeviceManagerApi.DEVICE_ALARM_URL,
99 101 params,
100 102 });
... ...
... ... @@ -165,3 +165,37 @@ export interface IDeviceConfigAddOrEditModel {
165 165 updateTime?: '2021-12-15T02:17:26.645Z';
166 166 updater?: string;
167 167 }
  168 +
  169 +export interface Data {
  170 + CO: number;
  171 +}
  172 +
  173 +export interface Details {
  174 + data: Data;
  175 +}
  176 +
  177 +export interface AlarmLogItem {
  178 + id: string;
  179 + tenantId: string;
  180 + creator?: any;
  181 + updater?: any;
  182 + createdTime: string;
  183 + updatedTime: string;
  184 + customerId: string;
  185 + tbDeviceId: string;
  186 + originatorType: number;
  187 + deviceId: string;
  188 + deviceName: string;
  189 + type: string;
  190 + severity: string;
  191 + status: string;
  192 + startTs: string;
  193 + endTs: string;
  194 + ackTs: string;
  195 + clearTs: string;
  196 + details: Details;
  197 + propagate: boolean;
  198 + propagateRelationTypes?: any;
  199 + organizationId: string;
  200 + organizationName: string;
  201 +}
... ...
1 1 import { BasicPageParams } from '/@/api/model/baseModel';
  2 +import { AlarmStatus } from '/@/views/alarm/log/config/detail.config';
2 3 export enum DeviceState {
3 4 INACTIVE = 'INACTIVE',
4 5 ONLINE = 'ONLINE',
... ... @@ -17,6 +18,7 @@ export type DeviceParam = {
17 18 };
18 19 export type DeviceProfileParam = {
19 20 name?: string;
  21 + status?: AlarmStatus;
20 22 };
21 23 export type DeviceId = {
22 24 id?: string;
... ...
... ... @@ -2,6 +2,21 @@ import { alarmLevel, statusType } from '/@/views/device/list/config/detail.confi
2 2 import { FormSchema } from '/@/components/Form';
3 3 import { BasicColumn } from '/@/components/Table';
4 4 import moment from 'moment';
  5 +
  6 +export enum AlarmStatus {
  7 + CLEARED_UN_ACK = 'CLEARED_UNACK',
  8 + ACTIVE_UN_ACK = 'ACTIVE_UNACK',
  9 + CLEARED_ACK = 'CLEARED_ACK',
  10 + ACTIVE_ACK = 'ACTIVE_ACK',
  11 +}
  12 +
  13 +export enum AlarmStatusMean {
  14 + CLEARED_UNACK = '清楚未确认',
  15 + ACTIVE_UNACK = '激活未确认',
  16 + CLEARED_ACK = '清除已确认',
  17 + ACTIVE_ACK = '激活已确认',
  18 +}
  19 +
5 20 export const alarmSearchSchemas: FormSchema[] = [
6 21 {
7 22 field: 'status',
... ... @@ -11,20 +26,20 @@ export const alarmSearchSchemas: FormSchema[] = [
11 26 componentProps: {
12 27 options: [
13 28 {
14   - label: '清除未确认',
15   - value: 'CLEARED_UNACK',
  29 + label: AlarmStatusMean[AlarmStatus.CLEARED_UN_ACK],
  30 + value: AlarmStatus.CLEARED_UN_ACK,
16 31 },
17 32 {
18   - label: '激活未确认',
19   - value: 'ACTIVE_UNACK',
  33 + label: AlarmStatusMean[AlarmStatus.ACTIVE_UN_ACK],
  34 + value: AlarmStatus.ACTIVE_UN_ACK,
20 35 },
21 36 {
22   - label: '清除已确认',
23   - value: 'CLEARED_ACK',
  37 + label: AlarmStatusMean[AlarmStatus.CLEARED_ACK],
  38 + value: AlarmStatus.CLEARED_ACK,
24 39 },
25 40 {
26   - label: '激活已确认',
27   - value: 'ACTIVE_ACK',
  41 + label: AlarmStatusMean[AlarmStatus.ACTIVE_ACK],
  42 + value: AlarmStatus.ACTIVE_ACK,
28 43 },
29 44 ],
30 45 },
... ...
  1 +import { AlarmStatus, AlarmStatusMean } from '../config/detail.config';
  2 +import { clearOrAckAlarm, getDeviceAlarm } from '/@/api/device/deviceManager';
  3 +import { notification, Button, Tag } from 'ant-design-vue';
  4 +import { h, onMounted, onUnmounted } from 'vue';
  5 +import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
  6 +import { alarmLevel } from '/@/views/device/list/config/detail.config';
  7 +
  8 +interface UseAlarmNotifyParams {
  9 + alarmNotifyStatus?: AlarmStatus;
  10 + interval?: number;
  11 + color?: string;
  12 +}
  13 +
  14 +export function useAlarmNotify(params: UseAlarmNotifyParams = {}) {
  15 + const {
  16 + alarmNotifyStatus = AlarmStatus.ACTIVE_UN_ACK,
  17 + interval = import.meta.env.VITE_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
  18 + color = 'orange',
  19 + } = params;
  20 + const alarmNotifyStatusMean = AlarmStatusMean[alarmNotifyStatus];
  21 +
  22 + const handleMarkRead = async (id: string) => {
  23 + try {
  24 + await clearOrAckAlarm(id, false);
  25 + } catch (error) {}
  26 + };
  27 +
  28 + let timeout: Nullable<NodeJS.Timer> = null;
  29 +
  30 + let currentNotifyId: Nullable<string> = null;
  31 +
  32 + const getAlarmLog = async () => {
  33 + try {
  34 + const { items = [] } =
  35 + (await getDeviceAlarm({ status: alarmNotifyStatus, page: 1, pageSize: 10 })) || {};
  36 +
  37 + if (items.length) {
  38 + const first = items.at(0)!;
  39 + const { deviceName, id, severity } = first;
  40 + if (currentNotifyId === id) return;
  41 + currentNotifyId = id;
  42 +
  43 + const key = `open-notify-${Date.now()}`;
  44 +
  45 + const severityMean = alarmLevel(severity);
  46 + notification.open({
  47 + message: '设备告警',
  48 + duration: null,
  49 + key,
  50 + description: h('div', {}, [
  51 + h('div', { style: { marginRight: '5px' } }, [
  52 + h('span', { style: { marginRight: '5px' } }, '设备:'),
  53 + h('span', {}, `[${deviceName}]`),
  54 + ]),
  55 + h('div', { style: { marginTop: '5px' } }, [
  56 + h('span', { style: { marginRight: '5px' } }, '告警状态:'),
  57 + h(Tag, { color }, () => `${alarmNotifyStatusMean}`),
  58 + ]),
  59 + h('div', { style: { marginTop: '5px' } }, [
  60 + h('span', { style: { marginRight: '5px' } }, '告警级别:'),
  61 + h(Tag, { color: '#f50' }, () => `${severityMean}`),
  62 + ]),
  63 + ]),
  64 + icon: h(ExclamationCircleOutlined, { style: { color: '#faa22d' } }),
  65 + onClose: () => (currentNotifyId = null),
  66 + btn: h(
  67 + Button,
  68 + {
  69 + type: 'primary',
  70 + size: 'small',
  71 + onClick: async () => {
  72 + await handleMarkRead(id);
  73 + notification.close(key);
  74 + },
  75 + },
  76 + () => '标记已读'
  77 + ),
  78 + });
  79 + }
  80 + } catch (error) {}
  81 + };
  82 +
  83 + const polling = () => {
  84 + timeout = setInterval(() => {
  85 + getAlarmLog();
  86 + }, interval);
  87 + };
  88 +
  89 + onMounted(() => {
  90 + polling();
  91 + });
  92 +
  93 + onUnmounted(() => {
  94 + clearInterval(timeout as NodeJS.Timer);
  95 + timeout = null;
  96 + });
  97 +}
... ...
... ... @@ -42,3 +42,8 @@ export interface UploadFileParams {
42 42 filename?: string;
43 43 [key: string]: any;
44 44 }
  45 +
  46 +export interface PaginationResult<T = Recordable> {
  47 + items: T[];
  48 + total: number;
  49 +}
... ...
... ... @@ -72,6 +72,7 @@ declare global {
72 72 VITE_USE_IMAGEMIN: boolean;
73 73 VITE_GENERATE_UI: string;
74 74 VITE_CONTENT_SECURITY_POLICY: boolean;
  75 + VITE_ALARM_NOTIFY_POLLING_INTERVAL_TIME: number;
75 76 }
76 77
77 78 declare function parseInt(s: string | number, radix?: number): number;
... ...