Commit 69e227acdd81d26253751ad1a2fe7b9a97dda826
Merge branch 'sqy_dev' into 'main'
feat:实时数据,告警... See merge request huang/yun-teng-iot-front!52
Showing
10 changed files
with
323 additions
and
73 deletions
... | ... | @@ -103,3 +103,15 @@ export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => { |
103 | 103 | params, |
104 | 104 | }); |
105 | 105 | }; |
106 | + | |
107 | +// 如果是flag为true 就是清除报警,否则是处理报警 | |
108 | +export const clearOrAckAlarm = (id: string, flag: boolean) => { | |
109 | + return defHttp.post( | |
110 | + { | |
111 | + url: `${DeviceManagerApi.DEVICE_ALARM_URL}/${id}/${flag ? 'clear' : 'ack'}`, | |
112 | + }, | |
113 | + { | |
114 | + joinPrefix: false, | |
115 | + } | |
116 | + ); | |
117 | +}; | ... | ... |
1 | +import { formatToDateTime } from '/@/utils/dateUtil'; | |
1 | 2 | import { FormSchema } from '/@/components/Form'; |
2 | 3 | import { BasicColumn } from '/@/components/Table'; |
3 | 4 | import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; |
5 | +import {} from ''; | |
4 | 6 | |
5 | 7 | export const columns: BasicColumn[] = [ |
6 | 8 | { |
... | ... | @@ -48,78 +50,183 @@ export const realTimeDataSearchSchemas: FormSchema[] = [ |
48 | 50 | field: 'key', |
49 | 51 | label: '键 / 值', |
50 | 52 | component: 'Input', |
51 | - colProps: { span: 12 }, | |
53 | + colProps: { span: 8 }, | |
52 | 54 | }, |
53 | 55 | ]; |
54 | 56 | export const realTimeDataColumns: BasicColumn[] = [ |
55 | 57 | { |
56 | - title: '最后更新时间', | |
57 | - dataIndex: 'update', | |
58 | - width: 120, | |
59 | - }, | |
60 | - { | |
61 | 58 | title: '键', |
62 | - dataIndex: 'label', | |
59 | + dataIndex: 'key', | |
63 | 60 | width: 100, |
64 | 61 | }, |
65 | 62 | { |
66 | 63 | title: '值', |
67 | - dataIndex: 'name', | |
64 | + dataIndex: 'value', | |
68 | 65 | width: 160, |
69 | 66 | }, |
67 | + { | |
68 | + title: '最后更新时间', | |
69 | + dataIndex: 'time', | |
70 | + width: 120, | |
71 | + format: (text) => formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'), | |
72 | + }, | |
70 | 73 | ]; |
71 | 74 | |
72 | 75 | // 告警 |
73 | 76 | export const alarmSearchSchemas: FormSchema[] = [ |
74 | 77 | { |
75 | - field: 'icon', | |
78 | + field: 'status', | |
76 | 79 | label: '告警状态', |
77 | 80 | component: 'Select', |
78 | 81 | colProps: { span: 6 }, |
82 | + componentProps: { | |
83 | + options: [ | |
84 | + { | |
85 | + label: '清除未确认', | |
86 | + value: 'CLEARED_UNACK', | |
87 | + }, | |
88 | + { | |
89 | + label: '激活未确认', | |
90 | + value: 'ACTIVE_UNACK', | |
91 | + }, | |
92 | + { | |
93 | + label: '清除已确认', | |
94 | + value: 'CLEARED_ACK', | |
95 | + }, | |
96 | + { | |
97 | + label: '激活已确认', | |
98 | + value: 'ACTIVE_ACK', | |
99 | + }, | |
100 | + ], | |
101 | + }, | |
79 | 102 | }, |
80 | 103 | { |
81 | - field: 'icon', | |
104 | + field: 'type', | |
82 | 105 | label: '告警类型', |
83 | 106 | component: 'Input', |
84 | 107 | colProps: { span: 6 }, |
85 | 108 | }, |
86 | 109 | { |
87 | - field: 'icon', | |
110 | + field: 'startTime', | |
88 | 111 | label: ' ', |
89 | 112 | component: 'DatePicker', |
113 | + componentProps: { | |
114 | + format: (value) => dayjs(value).valueOf(), | |
115 | + }, | |
90 | 116 | colProps: { span: 6 }, |
91 | 117 | }, |
92 | 118 | ]; |
93 | 119 | export const alarmColumns: BasicColumn[] = [ |
94 | 120 | { |
95 | 121 | title: '告警时间', |
96 | - dataIndex: 'aaa', | |
122 | + dataIndex: 'createdTime', | |
97 | 123 | width: 120, |
98 | 124 | }, |
99 | 125 | { |
100 | 126 | title: '告警设备', |
101 | - dataIndex: 'label', | |
127 | + dataIndex: 'deviceName', | |
102 | 128 | width: 100, |
103 | 129 | }, |
104 | 130 | { |
105 | 131 | title: '类型', |
106 | - dataIndex: 'ccc', | |
132 | + dataIndex: 'type', | |
107 | 133 | width: 160, |
108 | 134 | }, |
109 | 135 | { |
110 | 136 | title: '告警级别', |
111 | - dataIndex: 'ddd', | |
137 | + dataIndex: 'severity', | |
112 | 138 | width: 160, |
139 | + format: (text) => alarmLevel(text), | |
113 | 140 | }, |
114 | 141 | { |
115 | 142 | title: '状态', |
116 | - dataIndex: 'eee', | |
143 | + dataIndex: 'status', | |
144 | + format: (text) => statusType(text), | |
117 | 145 | width: 160, |
118 | 146 | }, |
147 | +]; | |
148 | +const alarmLevel = (type: string): string => { | |
149 | + if (type === 'CRITICAL') { | |
150 | + return '危险'; | |
151 | + } else if (type === 'MAJOR') { | |
152 | + return '重要'; | |
153 | + } else if (type === 'MINOR') { | |
154 | + return '次要'; | |
155 | + } else if (type === 'WARNING') { | |
156 | + return '警告'; | |
157 | + } else { | |
158 | + return '不确定'; | |
159 | + } | |
160 | +}; | |
161 | +const statusType = (type: string): string => { | |
162 | + if (type === 'CLEARED_UNACK') { | |
163 | + return '清除未确认'; | |
164 | + } else if (type === 'CLEARED_ACK') { | |
165 | + return '清除已确认'; | |
166 | + } else if (type === 'ACTIVE_ACK') { | |
167 | + return '激活已确认'; | |
168 | + } else { | |
169 | + return '激活未确认'; | |
170 | + } | |
171 | +}; | |
172 | + | |
173 | +export const alarmSchemasForm: FormSchema[] = [ | |
119 | 174 | { |
120 | - title: '操作', | |
121 | - dataIndex: 'name', | |
122 | - width: 160, | |
175 | + field: 'deviceName', | |
176 | + label: '告警设备', | |
177 | + component: 'Input', | |
178 | + componentProps: { | |
179 | + disabled: true, | |
180 | + }, | |
181 | + }, | |
182 | + | |
183 | + { | |
184 | + field: 'startTs', | |
185 | + label: '开始时间', | |
186 | + component: 'Input', | |
187 | + componentProps: { | |
188 | + disabled: true, | |
189 | + }, | |
190 | + }, | |
191 | + { | |
192 | + field: 'endTs', | |
193 | + label: '结束时间', | |
194 | + component: 'Input', | |
195 | + componentProps: { | |
196 | + disabled: true, | |
197 | + }, | |
198 | + }, | |
199 | + { | |
200 | + field: 'type', | |
201 | + label: '告警类型', | |
202 | + component: 'Input', | |
203 | + componentProps: { | |
204 | + disabled: true, | |
205 | + }, | |
206 | + }, | |
207 | + { | |
208 | + field: 'severity', | |
209 | + label: '严重程度', | |
210 | + component: 'Input', | |
211 | + componentProps: { | |
212 | + disabled: true, | |
213 | + }, | |
214 | + }, | |
215 | + { | |
216 | + field: 'status', | |
217 | + label: '状态', | |
218 | + component: 'Input', | |
219 | + componentProps: { | |
220 | + disabled: true, | |
221 | + }, | |
222 | + }, | |
223 | + { | |
224 | + field: 'details', | |
225 | + label: '详情', | |
226 | + component: 'InputTextArea', | |
227 | + componentProps: { | |
228 | + disabled: true, | |
229 | + }, | |
123 | 230 | }, |
124 | 231 | ]; |
125 | 232 | ... | ... |
1 | +<template> | |
2 | + <BasicModal | |
3 | + v-bind="$attrs" | |
4 | + title="告警详情" | |
5 | + @register="registerModal" | |
6 | + :canFullscreen="false" | |
7 | + :footer="null" | |
8 | + > | |
9 | + <BasicForm @register="registerForm" /> | |
10 | + <div class="flex justify-end"> | |
11 | + <a-button type="primary" class="mr-4" @click="handleAlarm">处理</a-button> | |
12 | + <a-button type="danger" @click="clearAlarm">清除</a-button> | |
13 | + </div> | |
14 | + </BasicModal> | |
15 | +</template> | |
16 | + | |
17 | +<script lang="ts"> | |
18 | + import { defineComponent, ref, unref } from 'vue'; | |
19 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | |
20 | + import { BasicForm, useForm } from '/@/components/Form'; | |
21 | + import { alarmSchemasForm } from '../../config/detail.config'; | |
22 | + import { clearOrAckAlarm } from '/@/api/device/deviceManager'; | |
23 | + export default defineComponent({ | |
24 | + name: 'AlarmDetailDrawer', | |
25 | + components: { | |
26 | + BasicModal, | |
27 | + BasicForm, | |
28 | + }, | |
29 | + setup() { | |
30 | + const [registerForm, { setFieldsValue, resetFields }] = useForm({ | |
31 | + showActionButtonGroup: false, | |
32 | + schemas: alarmSchemasForm, | |
33 | + }); | |
34 | + const alarmId = ref(''); | |
35 | + const [registerModal, { closeModal }] = useModalInner(async (data) => { | |
36 | + await resetFields(); | |
37 | + await setFieldsValue(data); | |
38 | + alarmId.value = data.id; | |
39 | + console.log(data); | |
40 | + }); | |
41 | + // 处理报警 | |
42 | + const handleAlarm = async () => { | |
43 | + await clearOrAckAlarm(unref(alarmId), false); | |
44 | + closeModal(); | |
45 | + }; | |
46 | + // 清除报警 | |
47 | + const clearAlarm = async () => { | |
48 | + await clearOrAckAlarm(unref(alarmId), true); | |
49 | + closeModal(); | |
50 | + }; | |
51 | + return { | |
52 | + registerModal, | |
53 | + registerForm, | |
54 | + clearAlarm, | |
55 | + handleAlarm, | |
56 | + }; | |
57 | + }, | |
58 | + }); | |
59 | +</script> | |
60 | + | |
61 | +<style lang="less" scoped></style> | ... | ... |
... | ... | @@ -6,15 +6,16 @@ |
6 | 6 | :destroyOnClose="true" |
7 | 7 | @close="closeDrawer" |
8 | 8 | :title="deviceDetail.name" |
9 | + width="78%" | |
9 | 10 | > |
10 | 11 | <Tabs v-model:activeKey="activeKey" :size="size" type="card"> |
11 | 12 | <TabPane key="1" tab="详情" |
12 | 13 | ><Detail ref="deviceDetailRef" :deviceDetail="deviceDetail" |
13 | 14 | /></TabPane> |
14 | 15 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'" |
15 | - ><RealTimeData | |
16 | + ><RealTimeData :deviceDetail="deviceDetail" | |
16 | 17 | /></TabPane> |
17 | - <TabPane key="3" tab="告警"><Alarm :id="tbDeviceId" /></TabPane> | |
18 | + <TabPane key="3" tab="告警"><Alarm :id="deviceDetail.id" /></TabPane> | |
18 | 19 | <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'" |
19 | 20 | ><ChildDevice |
20 | 21 | /></TabPane> |
... | ... | @@ -49,13 +50,12 @@ |
49 | 50 | const size = ref('small'); |
50 | 51 | const deviceDetailRef = ref(); |
51 | 52 | const deviceDetail: any = ref({}); |
52 | - const tbDeviceId = ref(''); | |
53 | + | |
53 | 54 | // 详情回显 |
54 | 55 | const [register] = useDrawerInner(async (data) => { |
55 | - const { id, tbDeviceId: tbId } = data; | |
56 | + const { id } = data; | |
56 | 57 | const res = await getDeviceDetail(id); |
57 | 58 | deviceDetail.value = res; |
58 | - tbDeviceId.value = tbId; | |
59 | 59 | const { latitude, longitude, address } = res.deviceInfo; |
60 | 60 | if (latitude) { |
61 | 61 | deviceDetailRef.value.initMap(longitude, latitude, address); |
... | ... | @@ -72,7 +72,6 @@ |
72 | 72 | closeDrawer, |
73 | 73 | deviceDetail, |
74 | 74 | deviceDetailRef, |
75 | - tbDeviceId, | |
76 | 75 | }; |
77 | 76 | }, |
78 | 77 | }); | ... | ... |
... | ... | @@ -56,7 +56,7 @@ |
56 | 56 | const DeviceStep1Ref = ref<InstanceType<typeof DeviceStep1>>(); |
57 | 57 | const DeviceStep2Ref = ref<InstanceType<typeof DeviceStep2>>(); |
58 | 58 | const { createMessage } = useMessage(); |
59 | - const current = ref(1); | |
59 | + const current = ref(0); | |
60 | 60 | const isUpdate = ref<Boolean>(); |
61 | 61 | const deviceInfo = ref({}); |
62 | 62 | const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备')); | ... | ... |
1 | 1 | <template> |
2 | - <BasicTable @register="registerTable" /> | |
2 | + <div> | |
3 | + <BasicTable @register="registerTable"> | |
4 | + <template #action="{ record }"> | |
5 | + <TableAction | |
6 | + :actions="[ | |
7 | + { | |
8 | + label: '详情', | |
9 | + icon: 'ant-design:eye-outlined', | |
10 | + onClick: handleDetail.bind(null, record), | |
11 | + }, | |
12 | + ]" | |
13 | + /> | |
14 | + </template> | |
15 | + </BasicTable> | |
16 | + <AlarmDetailModal @register="registerDetailModal" /> | |
17 | + </div> | |
3 | 18 | </template> |
4 | 19 | <script lang="ts"> |
5 | 20 | import { defineComponent } from 'vue'; |
6 | - import { BasicTable, useTable } from '/@/components/Table'; | |
21 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
7 | 22 | import { alarmColumns, alarmSearchSchemas } from '../../config/detail.config'; |
8 | 23 | import { getDeviceAlarm } from '/@/api/device/deviceManager'; |
24 | + import AlarmDetailModal from '../modal/AlarmDetailModal.vue'; | |
25 | + import { useModal } from '/@/components/Modal'; | |
9 | 26 | export default defineComponent({ |
10 | 27 | name: 'DeviceManagement', |
11 | 28 | components: { |
12 | 29 | BasicTable, |
30 | + TableAction, | |
31 | + AlarmDetailModal, | |
13 | 32 | }, |
14 | 33 | props: { |
15 | 34 | id: { |
... | ... | @@ -26,15 +45,33 @@ |
26 | 45 | schemas: alarmSearchSchemas, |
27 | 46 | }, |
28 | 47 | useSearchForm: true, |
29 | - showTableSetting: true, | |
30 | 48 | bordered: true, |
31 | 49 | showIndexColumn: false, |
32 | - beforeFetch: (data) => Reflect.set(data, 'deviceId', props.id), | |
50 | + beforeFetch: (data) => { | |
51 | + Reflect.set(data, 'deviceId', props.id); | |
52 | + }, | |
53 | + actionColumn: { | |
54 | + width: 200, | |
55 | + title: '操作', | |
56 | + slots: { customRender: 'action' }, | |
57 | + fixed: 'right', | |
58 | + }, | |
33 | 59 | }); |
34 | - | |
60 | + const [registerDetailModal, { openModal }] = useModal(); | |
61 | + const handleDetail = (record: Recordable) => { | |
62 | + openModal(true, record); | |
63 | + }; | |
35 | 64 | return { |
36 | 65 | registerTable, |
66 | + registerDetailModal, | |
67 | + handleDetail, | |
37 | 68 | }; |
38 | 69 | }, |
39 | 70 | }); |
40 | 71 | </script> |
72 | + | |
73 | +<style> | |
74 | + .aaa { | |
75 | + position: absolute; | |
76 | + } | |
77 | +</style> | ... | ... |
... | ... | @@ -2,18 +2,42 @@ |
2 | 2 | <BasicTable @register="registerTable" /> |
3 | 3 | </template> |
4 | 4 | <script lang="ts"> |
5 | - import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; | |
5 | + import { defineComponent, reactive } from 'vue'; | |
6 | 6 | import { BasicTable, useTable } from '/@/components/Table'; |
7 | 7 | import { realTimeDataColumns, realTimeDataSearchSchemas } from '../../config/detail.config'; |
8 | 8 | import { useWebSocket } from '@vueuse/core'; |
9 | 9 | import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum'; |
10 | 10 | import { getAuthCache } from '/@/utils/auth'; |
11 | + import type { socketDataType } from '../../types'; | |
12 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
11 | 13 | export default defineComponent({ |
12 | - name: 'DeviceManagement', | |
14 | + name: 'RealTimeData', | |
13 | 15 | components: { |
14 | 16 | BasicTable, |
15 | 17 | }, |
16 | - setup(_) { | |
18 | + props: { | |
19 | + deviceDetail: { | |
20 | + type: Object, | |
21 | + required: true, | |
22 | + }, | |
23 | + }, | |
24 | + setup(props) { | |
25 | + const token = getAuthCache(JWT_TOKEN_KEY); | |
26 | + const state = reactive({ | |
27 | + server: `ws://101.133.234.90:8080/api/ws/plugins/telemetry?token=${token}`, | |
28 | + sendValue: JSON.stringify({ | |
29 | + tsSubCmds: [ | |
30 | + { | |
31 | + entityType: 'DEVICE', | |
32 | + entityId: props.deviceDetail.tbDeviceId, | |
33 | + scope: 'LATEST_TELEMETRY', | |
34 | + cmdId: 1, | |
35 | + }, | |
36 | + ], | |
37 | + }), | |
38 | + recordList: Array<socketDataType>(), | |
39 | + }); | |
40 | + const { createMessage } = useMessage(); | |
17 | 41 | const [registerTable] = useTable({ |
18 | 42 | columns: realTimeDataColumns, |
19 | 43 | formConfig: { |
... | ... | @@ -21,48 +45,52 @@ |
21 | 45 | schemas: realTimeDataSearchSchemas, |
22 | 46 | }, |
23 | 47 | useSearchForm: true, |
24 | - showTableSetting: true, | |
48 | + showTableSetting: false, | |
25 | 49 | bordered: true, |
26 | 50 | showIndexColumn: false, |
51 | + dataSource: state.recordList, | |
27 | 52 | }); |
28 | - const token = getAuthCache(JWT_TOKEN_KEY); | |
29 | 53 | |
30 | - const state = reactive({ | |
31 | - server: `ws://192.168.10.139:8080/api/ws/plugins/telemetry?token=${token}`, | |
32 | - sendValue: '', | |
33 | - recordList: [] as { id: number; time: number; res: string }[], | |
34 | - }); | |
35 | - const { status, data, send, close, open } = useWebSocket(state.server, { | |
36 | - autoReconnect: false, | |
37 | - heartbeat: true, | |
38 | - }); | |
39 | - onMounted(() => { | |
40 | - open(); | |
41 | - send( | |
42 | - JSON.stringify({ | |
43 | - attrSubCmds: [], | |
44 | - tsSubCmds: [ | |
45 | - { | |
46 | - entityType: 'DEVICE', | |
47 | - entityId: '3199a500-6302-11ec-ba36-417bcc842c0a', | |
48 | - scope: 'LATEST_TELEMETRY', | |
49 | - cmdId: 1, | |
50 | - }, | |
51 | - ], | |
52 | - historyCmds: [], | |
53 | - entityDataCmds: [], | |
54 | - entityDataUnsubscribeCmds: [], | |
55 | - alarmDataCmds: [], | |
56 | - alarmDataUnsubscribeCmds: [], | |
57 | - entityCountCmds: [], | |
58 | - entityCountUnsubscribeCmds: [], | |
59 | - }) | |
60 | - ); | |
61 | - | |
62 | - console.log(JSON.parse(data.value)); | |
63 | - }); | |
64 | - onUnmounted(() => { | |
65 | - close(); | |
54 | + const { send } = useWebSocket(state.server, { | |
55 | + onConnected() { | |
56 | + send(state.sendValue); | |
57 | + }, | |
58 | + onMessage(_, e) { | |
59 | + const { data } = JSON.parse(e.data); | |
60 | + console.log('来新消息了'); | |
61 | + const newArray: any = []; | |
62 | + for (const key in data) { | |
63 | + const newData = data[key].flat(1); | |
64 | + let obj = { | |
65 | + key, | |
66 | + time: newData[0], | |
67 | + value: newData[1], | |
68 | + }; | |
69 | + if (state.recordList.length === 0) { | |
70 | + state.recordList.push(obj); | |
71 | + } else { | |
72 | + newArray.push(obj); | |
73 | + } | |
74 | + } | |
75 | + newArray.forEach((item) => { | |
76 | + let flag = false; | |
77 | + state.recordList.forEach((item1) => { | |
78 | + if (item.key === item1.key) { | |
79 | + item1.time = item.time; | |
80 | + item1.value = item.value; | |
81 | + console.log('哈哈哈', item, item1, state.recordList); | |
82 | + flag = true; | |
83 | + } | |
84 | + }); | |
85 | + if (!flag) { | |
86 | + state.recordList.unshift(item); | |
87 | + } | |
88 | + }); | |
89 | + }, | |
90 | + onDisconnected() {}, | |
91 | + onError() { | |
92 | + createMessage.error('webSocket连接超时,请联系管理员'); | |
93 | + }, | |
66 | 94 | }); |
67 | 95 | |
68 | 96 | return { | ... | ... |