Commit 69e227acdd81d26253751ad1a2fe7b9a97dda826

Authored by xp.Huang
2 parents a3c5f2fb ee270b63

Merge branch 'sqy_dev' into 'main'

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

See merge request huang/yun-teng-iot-front!52
... ... @@ -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 +};
... ...
... ... @@ -15,7 +15,6 @@
15 15 :title="getMergeProps.title"
16 16 @dblclick="handleTitleDbClick"
17 17 />
18   - <slot name="titleSlot" v-if="!getMergeProps.title"></slot>
19 18 </template>
20 19
21 20 <template #footer v-if="!$slots.footer">
... ...
  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>
... ...
... ... @@ -86,6 +86,7 @@
86 86 }
87 87 };
88 88 const copyDeviceToken = () => {
  89 + // TODO:此处需要动态修改
89 90 clipboardRef.value = props.deviceDetail.deviceToken;
90 91 if (unref(clipboardRef)) {
91 92 createMessage.success('复制成功~');
... ...
... ... @@ -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 {
... ...
... ... @@ -18,3 +18,9 @@ export interface DeviceType {
18 18 tenantCode: string;
19 19 updateTime: string;
20 20 }
  21 +
  22 +export interface socketDataType {
  23 + key: string;
  24 + value: string;
  25 + time: number;
  26 +}
... ...