Commit ee270b6365f0f7a81b53966ffb23241381a3ff10

Authored by sqy
1 parent a3c5f2fb

feat:实时数据,告警...

@@ -103,3 +103,15 @@ export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => { @@ -103,3 +103,15 @@ export const getDeviceAlarm = (params?: DeviceProfileQueryParam) => {
103 params, 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 +};
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 :title="getMergeProps.title" 15 :title="getMergeProps.title"
16 @dblclick="handleTitleDbClick" 16 @dblclick="handleTitleDbClick"
17 /> 17 />
18 - <slot name="titleSlot" v-if="!getMergeProps.title"></slot>  
19 </template> 18 </template>
20 19
21 <template #footer v-if="!$slots.footer"> 20 <template #footer v-if="!$slots.footer">
  1 +import { formatToDateTime } from '/@/utils/dateUtil';
1 import { FormSchema } from '/@/components/Form'; 2 import { FormSchema } from '/@/components/Form';
2 import { BasicColumn } from '/@/components/Table'; 3 import { BasicColumn } from '/@/components/Table';
3 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 4 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  5 +import {} from '';
4 6
5 export const columns: BasicColumn[] = [ 7 export const columns: BasicColumn[] = [
6 { 8 {
@@ -48,78 +50,183 @@ export const realTimeDataSearchSchemas: FormSchema[] = [ @@ -48,78 +50,183 @@ export const realTimeDataSearchSchemas: FormSchema[] = [
48 field: 'key', 50 field: 'key',
49 label: '键 / 值', 51 label: '键 / 值',
50 component: 'Input', 52 component: 'Input',
51 - colProps: { span: 12 }, 53 + colProps: { span: 8 },
52 }, 54 },
53 ]; 55 ];
54 export const realTimeDataColumns: BasicColumn[] = [ 56 export const realTimeDataColumns: BasicColumn[] = [
55 { 57 {
56 - title: '最后更新时间',  
57 - dataIndex: 'update',  
58 - width: 120,  
59 - },  
60 - {  
61 title: '键', 58 title: '键',
62 - dataIndex: 'label', 59 + dataIndex: 'key',
63 width: 100, 60 width: 100,
64 }, 61 },
65 { 62 {
66 title: '值', 63 title: '值',
67 - dataIndex: 'name', 64 + dataIndex: 'value',
68 width: 160, 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 export const alarmSearchSchemas: FormSchema[] = [ 76 export const alarmSearchSchemas: FormSchema[] = [
74 { 77 {
75 - field: 'icon', 78 + field: 'status',
76 label: '告警状态', 79 label: '告警状态',
77 component: 'Select', 80 component: 'Select',
78 colProps: { span: 6 }, 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 label: '告警类型', 105 label: '告警类型',
83 component: 'Input', 106 component: 'Input',
84 colProps: { span: 6 }, 107 colProps: { span: 6 },
85 }, 108 },
86 { 109 {
87 - field: 'icon', 110 + field: 'startTime',
88 label: ' ', 111 label: ' ',
89 component: 'DatePicker', 112 component: 'DatePicker',
  113 + componentProps: {
  114 + format: (value) => dayjs(value).valueOf(),
  115 + },
90 colProps: { span: 6 }, 116 colProps: { span: 6 },
91 }, 117 },
92 ]; 118 ];
93 export const alarmColumns: BasicColumn[] = [ 119 export const alarmColumns: BasicColumn[] = [
94 { 120 {
95 title: '告警时间', 121 title: '告警时间',
96 - dataIndex: 'aaa', 122 + dataIndex: 'createdTime',
97 width: 120, 123 width: 120,
98 }, 124 },
99 { 125 {
100 title: '告警设备', 126 title: '告警设备',
101 - dataIndex: 'label', 127 + dataIndex: 'deviceName',
102 width: 100, 128 width: 100,
103 }, 129 },
104 { 130 {
105 title: '类型', 131 title: '类型',
106 - dataIndex: 'ccc', 132 + dataIndex: 'type',
107 width: 160, 133 width: 160,
108 }, 134 },
109 { 135 {
110 title: '告警级别', 136 title: '告警级别',
111 - dataIndex: 'ddd', 137 + dataIndex: 'severity',
112 width: 160, 138 width: 160,
  139 + format: (text) => alarmLevel(text),
113 }, 140 },
114 { 141 {
115 title: '状态', 142 title: '状态',
116 - dataIndex: 'eee', 143 + dataIndex: 'status',
  144 + format: (text) => statusType(text),
117 width: 160, 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,15 +6,16 @@
6 :destroyOnClose="true" 6 :destroyOnClose="true"
7 @close="closeDrawer" 7 @close="closeDrawer"
8 :title="deviceDetail.name" 8 :title="deviceDetail.name"
  9 + width="78%"
9 > 10 >
10 <Tabs v-model:activeKey="activeKey" :size="size" type="card"> 11 <Tabs v-model:activeKey="activeKey" :size="size" type="card">
11 <TabPane key="1" tab="详情" 12 <TabPane key="1" tab="详情"
12 ><Detail ref="deviceDetailRef" :deviceDetail="deviceDetail" 13 ><Detail ref="deviceDetailRef" :deviceDetail="deviceDetail"
13 /></TabPane> 14 /></TabPane>
14 <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'" 15 <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"
15 - ><RealTimeData 16 + ><RealTimeData :deviceDetail="deviceDetail"
16 /></TabPane> 17 /></TabPane>
17 - <TabPane key="3" tab="告警"><Alarm :id="tbDeviceId" /></TabPane> 18 + <TabPane key="3" tab="告警"><Alarm :id="deviceDetail.id" /></TabPane>
18 <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'" 19 <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"
19 ><ChildDevice 20 ><ChildDevice
20 /></TabPane> 21 /></TabPane>
@@ -49,13 +50,12 @@ @@ -49,13 +50,12 @@
49 const size = ref('small'); 50 const size = ref('small');
50 const deviceDetailRef = ref(); 51 const deviceDetailRef = ref();
51 const deviceDetail: any = ref({}); 52 const deviceDetail: any = ref({});
52 - const tbDeviceId = ref(''); 53 +
53 // 详情回显 54 // 详情回显
54 const [register] = useDrawerInner(async (data) => { 55 const [register] = useDrawerInner(async (data) => {
55 - const { id, tbDeviceId: tbId } = data; 56 + const { id } = data;
56 const res = await getDeviceDetail(id); 57 const res = await getDeviceDetail(id);
57 deviceDetail.value = res; 58 deviceDetail.value = res;
58 - tbDeviceId.value = tbId;  
59 const { latitude, longitude, address } = res.deviceInfo; 59 const { latitude, longitude, address } = res.deviceInfo;
60 if (latitude) { 60 if (latitude) {
61 deviceDetailRef.value.initMap(longitude, latitude, address); 61 deviceDetailRef.value.initMap(longitude, latitude, address);
@@ -72,7 +72,6 @@ @@ -72,7 +72,6 @@
72 closeDrawer, 72 closeDrawer,
73 deviceDetail, 73 deviceDetail,
74 deviceDetailRef, 74 deviceDetailRef,
75 - tbDeviceId,  
76 }; 75 };
77 }, 76 },
78 }); 77 });
@@ -56,7 +56,7 @@ @@ -56,7 +56,7 @@
56 const DeviceStep1Ref = ref<InstanceType<typeof DeviceStep1>>(); 56 const DeviceStep1Ref = ref<InstanceType<typeof DeviceStep1>>();
57 const DeviceStep2Ref = ref<InstanceType<typeof DeviceStep2>>(); 57 const DeviceStep2Ref = ref<InstanceType<typeof DeviceStep2>>();
58 const { createMessage } = useMessage(); 58 const { createMessage } = useMessage();
59 - const current = ref(1); 59 + const current = ref(0);
60 const isUpdate = ref<Boolean>(); 60 const isUpdate = ref<Boolean>();
61 const deviceInfo = ref({}); 61 const deviceInfo = ref({});
62 const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备')); 62 const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备'));
1 <template> 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 </template> 18 </template>
4 <script lang="ts"> 19 <script lang="ts">
5 import { defineComponent } from 'vue'; 20 import { defineComponent } from 'vue';
6 - import { BasicTable, useTable } from '/@/components/Table'; 21 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
7 import { alarmColumns, alarmSearchSchemas } from '../../config/detail.config'; 22 import { alarmColumns, alarmSearchSchemas } from '../../config/detail.config';
8 import { getDeviceAlarm } from '/@/api/device/deviceManager'; 23 import { getDeviceAlarm } from '/@/api/device/deviceManager';
  24 + import AlarmDetailModal from '../modal/AlarmDetailModal.vue';
  25 + import { useModal } from '/@/components/Modal';
9 export default defineComponent({ 26 export default defineComponent({
10 name: 'DeviceManagement', 27 name: 'DeviceManagement',
11 components: { 28 components: {
12 BasicTable, 29 BasicTable,
  30 + TableAction,
  31 + AlarmDetailModal,
13 }, 32 },
14 props: { 33 props: {
15 id: { 34 id: {
@@ -26,15 +45,33 @@ @@ -26,15 +45,33 @@
26 schemas: alarmSearchSchemas, 45 schemas: alarmSearchSchemas,
27 }, 46 },
28 useSearchForm: true, 47 useSearchForm: true,
29 - showTableSetting: true,  
30 bordered: true, 48 bordered: true,
31 showIndexColumn: false, 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 return { 64 return {
36 registerTable, 65 registerTable,
  66 + registerDetailModal,
  67 + handleDetail,
37 }; 68 };
38 }, 69 },
39 }); 70 });
40 </script> 71 </script>
  72 +
  73 +<style>
  74 + .aaa {
  75 + position: absolute;
  76 + }
  77 +</style>
@@ -86,6 +86,7 @@ @@ -86,6 +86,7 @@
86 } 86 }
87 }; 87 };
88 const copyDeviceToken = () => { 88 const copyDeviceToken = () => {
  89 + // TODO:此处需要动态修改
89 clipboardRef.value = props.deviceDetail.deviceToken; 90 clipboardRef.value = props.deviceDetail.deviceToken;
90 if (unref(clipboardRef)) { 91 if (unref(clipboardRef)) {
91 createMessage.success('复制成功~'); 92 createMessage.success('复制成功~');
@@ -2,18 +2,42 @@ @@ -2,18 +2,42 @@
2 <BasicTable @register="registerTable" /> 2 <BasicTable @register="registerTable" />
3 </template> 3 </template>
4 <script lang="ts"> 4 <script lang="ts">
5 - import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; 5 + import { defineComponent, reactive } from 'vue';
6 import { BasicTable, useTable } from '/@/components/Table'; 6 import { BasicTable, useTable } from '/@/components/Table';
7 import { realTimeDataColumns, realTimeDataSearchSchemas } from '../../config/detail.config'; 7 import { realTimeDataColumns, realTimeDataSearchSchemas } from '../../config/detail.config';
8 import { useWebSocket } from '@vueuse/core'; 8 import { useWebSocket } from '@vueuse/core';
9 import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum'; 9 import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum';
10 import { getAuthCache } from '/@/utils/auth'; 10 import { getAuthCache } from '/@/utils/auth';
  11 + import type { socketDataType } from '../../types';
  12 + import { useMessage } from '/@/hooks/web/useMessage';
11 export default defineComponent({ 13 export default defineComponent({
12 - name: 'DeviceManagement', 14 + name: 'RealTimeData',
13 components: { 15 components: {
14 BasicTable, 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 const [registerTable] = useTable({ 41 const [registerTable] = useTable({
18 columns: realTimeDataColumns, 42 columns: realTimeDataColumns,
19 formConfig: { 43 formConfig: {
@@ -21,48 +45,52 @@ @@ -21,48 +45,52 @@
21 schemas: realTimeDataSearchSchemas, 45 schemas: realTimeDataSearchSchemas,
22 }, 46 },
23 useSearchForm: true, 47 useSearchForm: true,
24 - showTableSetting: true, 48 + showTableSetting: false,
25 bordered: true, 49 bordered: true,
26 showIndexColumn: false, 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 return { 96 return {
@@ -18,3 +18,9 @@ export interface DeviceType { @@ -18,3 +18,9 @@ export interface DeviceType {
18 tenantCode: string; 18 tenantCode: string;
19 updateTime: string; 19 updateTime: string;
20 } 20 }
  21 +
  22 +export interface socketDataType {
  23 + key: string;
  24 + value: string;
  25 + time: number;
  26 +}