Showing
10 changed files
with
173 additions
and
10 deletions
@@ -42,3 +42,5 @@ VITE_GLOB_CONFIGURATION = /thingskit-drawio | @@ -42,3 +42,5 @@ VITE_GLOB_CONFIGURATION = /thingskit-drawio | ||
42 | # Content Security Policy | 42 | # Content Security Policy |
43 | VITE_CONTENT_SECURITY_POLICY = false | 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,3 +42,6 @@ VITE_GLOB_CONFIGURATION = /thingskit-scada | ||
42 | 42 | ||
43 | # Content Security Policy | 43 | # Content Security Policy |
44 | VITE_CONTENT_SECURITY_POLICY = false | 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,6 +8,7 @@ | ||
8 | 8 | ||
9 | <script lang="ts" setup> | 9 | <script lang="ts" setup> |
10 | import { ConfigProvider } from 'ant-design-vue'; | 10 | import { ConfigProvider } from 'ant-design-vue'; |
11 | + import { useAlarmNotify } from './views/alarm/log/hook/useAlarmNotify'; | ||
11 | import { AppProvider } from '/@/components/Application'; | 12 | import { AppProvider } from '/@/components/Application'; |
12 | import { useTitle } from '/@/hooks/web/useTitle'; | 13 | import { useTitle } from '/@/hooks/web/useTitle'; |
13 | import { useLocale } from '/@/locales/useLocale'; | 14 | import { useLocale } from '/@/locales/useLocale'; |
@@ -15,4 +16,5 @@ | @@ -15,4 +16,5 @@ | ||
15 | const { getAntdLocale } = useLocale(); | 16 | const { getAntdLocale } = useLocale(); |
16 | 17 | ||
17 | useTitle(); | 18 | useTitle(); |
18 | -</script> | ||
19 | + useAlarmNotify(); | ||
20 | +</script> |
@@ -6,6 +6,8 @@ import { | @@ -6,6 +6,8 @@ import { | ||
6 | DeviceQueryParam, | 6 | DeviceQueryParam, |
7 | } from '/@/api/device/model/deviceModel'; | 7 | } from '/@/api/device/model/deviceModel'; |
8 | import { ChildDeviceParams } from './model/deviceModel'; | 8 | import { ChildDeviceParams } from './model/deviceModel'; |
9 | +import { PaginationResult } from '/#/axios'; | ||
10 | +import { AlarmLogItem } from './model/deviceConfigModel'; | ||
9 | enum DeviceManagerApi { | 11 | enum DeviceManagerApi { |
10 | /** | 12 | /** |
11 | * 设备URL | 13 | * 设备URL |
@@ -94,7 +96,7 @@ export const getDeviceDetail = (id: string) => { | @@ -94,7 +96,7 @@ export const getDeviceDetail = (id: string) => { | ||
94 | 96 | ||
95 | // 查询设备详情中的告警 | 97 | // 查询设备详情中的告警 |
96 | export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => { | 98 | export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => { |
97 | - return defHttp.get({ | 99 | + return defHttp.get<PaginationResult<AlarmLogItem>>({ |
98 | url: DeviceManagerApi.DEVICE_ALARM_URL, | 100 | url: DeviceManagerApi.DEVICE_ALARM_URL, |
99 | params, | 101 | params, |
100 | }); | 102 | }); |
@@ -165,3 +165,37 @@ export interface IDeviceConfigAddOrEditModel { | @@ -165,3 +165,37 @@ export interface IDeviceConfigAddOrEditModel { | ||
165 | updateTime?: '2021-12-15T02:17:26.645Z'; | 165 | updateTime?: '2021-12-15T02:17:26.645Z'; |
166 | updater?: string; | 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 | import { BasicPageParams } from '/@/api/model/baseModel'; | 1 | import { BasicPageParams } from '/@/api/model/baseModel'; |
2 | +import { AlarmStatus } from '/@/views/alarm/log/config/detail.config'; | ||
2 | export enum DeviceState { | 3 | export enum DeviceState { |
3 | INACTIVE = 'INACTIVE', | 4 | INACTIVE = 'INACTIVE', |
4 | ONLINE = 'ONLINE', | 5 | ONLINE = 'ONLINE', |
@@ -17,6 +18,7 @@ export type DeviceParam = { | @@ -17,6 +18,7 @@ export type DeviceParam = { | ||
17 | }; | 18 | }; |
18 | export type DeviceProfileParam = { | 19 | export type DeviceProfileParam = { |
19 | name?: string; | 20 | name?: string; |
21 | + status?: AlarmStatus; | ||
20 | }; | 22 | }; |
21 | export type DeviceId = { | 23 | export type DeviceId = { |
22 | id?: string; | 24 | id?: string; |
@@ -2,6 +2,21 @@ import { alarmLevel, statusType } from '/@/views/device/list/config/detail.confi | @@ -2,6 +2,21 @@ import { alarmLevel, statusType } from '/@/views/device/list/config/detail.confi | ||
2 | import { FormSchema } from '/@/components/Form'; | 2 | import { FormSchema } from '/@/components/Form'; |
3 | import { BasicColumn } from '/@/components/Table'; | 3 | import { BasicColumn } from '/@/components/Table'; |
4 | import moment from 'moment'; | 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 | export const alarmSearchSchemas: FormSchema[] = [ | 20 | export const alarmSearchSchemas: FormSchema[] = [ |
6 | { | 21 | { |
7 | field: 'status', | 22 | field: 'status', |
@@ -11,20 +26,20 @@ export const alarmSearchSchemas: FormSchema[] = [ | @@ -11,20 +26,20 @@ export const alarmSearchSchemas: FormSchema[] = [ | ||
11 | componentProps: { | 26 | componentProps: { |
12 | options: [ | 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 | }, |
src/views/alarm/log/hook/useAlarmNotify.ts
0 → 100644
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,3 +42,8 @@ export interface UploadFileParams { | ||
42 | filename?: string; | 42 | filename?: string; |
43 | [key: string]: any; | 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,6 +72,7 @@ declare global { | ||
72 | VITE_USE_IMAGEMIN: boolean; | 72 | VITE_USE_IMAGEMIN: boolean; |
73 | VITE_GENERATE_UI: string; | 73 | VITE_GENERATE_UI: string; |
74 | VITE_CONTENT_SECURITY_POLICY: boolean; | 74 | VITE_CONTENT_SECURITY_POLICY: boolean; |
75 | + VITE_ALARM_NOTIFY_POLLING_INTERVAL_TIME: number; | ||
75 | } | 76 | } |
76 | 77 | ||
77 | declare function parseInt(s: string | number, radix?: number): number; | 78 | declare function parseInt(s: string | number, radix?: number): number; |