Commit b7a782838ca2696ac9440d69a9c94b3cef37ea5d
1 parent
41e8ae43
feat: device detail add model of matter tab pane
Showing
4 changed files
with
274 additions
and
7 deletions
| @@ -16,12 +16,15 @@ | @@ -16,12 +16,15 @@ | ||
| 16 | @open-gateway-device="handleOpenGatewayDevice" | 16 | @open-gateway-device="handleOpenGatewayDevice" | 
| 17 | /> | 17 | /> | 
| 18 | </TabPane> | 18 | </TabPane> | 
| 19 | - <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | 19 | + <TabPane v-if="deviceDetail?.deviceType !== 'GATEWAY'" key="modelOfMatter" tab="物模型"> | 
| 20 | + <ModelOfMatter :deviceDetail="deviceDetail" /> | ||
| 21 | + </TabPane> | ||
| 22 | + <!-- <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | ||
| 20 | <RealTimeData :deviceDetail="deviceDetail" /> | 23 | <RealTimeData :deviceDetail="deviceDetail" /> | 
| 21 | </TabPane> | 24 | </TabPane> | 
| 22 | <TabPane key="7" tab="历史数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | 25 | <TabPane key="7" tab="历史数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | 
| 23 | <HistoryData :deviceDetail="deviceDetail" /> | 26 | <HistoryData :deviceDetail="deviceDetail" /> | 
| 24 | - </TabPane> | 27 | + </TabPane> --> | 
| 25 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> | 28 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> | 
| 26 | <CommandIssuance :deviceDetail="deviceDetail" /> | 29 | <CommandIssuance :deviceDetail="deviceDetail" /> | 
| 27 | </TabPane> | 30 | </TabPane> | 
| @@ -50,13 +53,14 @@ | @@ -50,13 +53,14 @@ | ||
| 50 | 53 | ||
| 51 | import { Tabs } from 'ant-design-vue'; | 54 | import { Tabs } from 'ant-design-vue'; | 
| 52 | import Detail from '../tabs/Detail.vue'; | 55 | import Detail from '../tabs/Detail.vue'; | 
| 53 | - import RealTimeData from '../tabs/RealTimeData.vue'; | 56 | + // import RealTimeData from '../tabs/RealTimeData.vue'; | 
| 54 | import Alarm from '../tabs/Alarm.vue'; | 57 | import Alarm from '../tabs/Alarm.vue'; | 
| 55 | import ChildDevice from '../tabs/ChildDevice.vue'; | 58 | import ChildDevice from '../tabs/ChildDevice.vue'; | 
| 56 | import TBoxDetail from '../tabs/TBoxDetail.vue'; | 59 | import TBoxDetail from '../tabs/TBoxDetail.vue'; | 
| 57 | import CommandIssuance from '../tabs/CommandIssuance.vue'; | 60 | import CommandIssuance from '../tabs/CommandIssuance.vue'; | 
| 58 | import { getDeviceDetail } from '/@/api/device/deviceManager'; | 61 | import { getDeviceDetail } from '/@/api/device/deviceManager'; | 
| 59 | - import HistoryData from '../tabs/HistoryData.vue'; | 62 | + // import HistoryData from '../tabs/HistoryData.vue'; | 
| 63 | + import ModelOfMatter from '../tabs/ModelOfMatter.vue'; | ||
| 60 | export default defineComponent({ | 64 | export default defineComponent({ | 
| 61 | name: 'DeviceModal', | 65 | name: 'DeviceModal', | 
| 62 | components: { | 66 | components: { | 
| @@ -64,12 +68,13 @@ | @@ -64,12 +68,13 @@ | ||
| 64 | Tabs, | 68 | Tabs, | 
| 65 | TabPane: Tabs.TabPane, | 69 | TabPane: Tabs.TabPane, | 
| 66 | Detail, | 70 | Detail, | 
| 67 | - RealTimeData, | 71 | + // RealTimeData, | 
| 68 | Alarm, | 72 | Alarm, | 
| 69 | ChildDevice, | 73 | ChildDevice, | 
| 70 | CommandIssuance, | 74 | CommandIssuance, | 
| 71 | TBoxDetail, | 75 | TBoxDetail, | 
| 72 | - HistoryData, | 76 | + // HistoryData, | 
| 77 | + ModelOfMatter, | ||
| 73 | }, | 78 | }, | 
| 74 | emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], | 79 | emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], | 
| 75 | setup(_props, { emit }) { | 80 | setup(_props, { emit }) { | 
| @@ -23,6 +23,7 @@ | @@ -23,6 +23,7 @@ | ||
| 23 | 23 | ||
| 24 | const props = defineProps<{ | 24 | const props = defineProps<{ | 
| 25 | deviceDetail: DeviceDetail; | 25 | deviceDetail: DeviceDetail; | 
| 26 | + attr?: string; | ||
| 26 | }>(); | 27 | }>(); | 
| 27 | 28 | ||
| 28 | const chartRef = ref(); | 29 | const chartRef = ref(); | 
| @@ -119,6 +120,10 @@ | @@ -119,6 +120,10 @@ | ||
| 119 | const { tbDeviceId } = props.deviceDetail || {}; | 120 | const { tbDeviceId } = props.deviceDetail || {}; | 
| 120 | try { | 121 | try { | 
| 121 | deviceAttrs.value = (await getDeviceDataKeys(tbDeviceId)) || []; | 122 | deviceAttrs.value = (await getDeviceDataKeys(tbDeviceId)) || []; | 
| 123 | + | ||
| 124 | + if (props.attr) { | ||
| 125 | + method.setFieldsValue({ keys: props.attr }); | ||
| 126 | + } | ||
| 122 | } catch (error) {} | 127 | } catch (error) {} | 
| 123 | }; | 128 | }; | 
| 124 | 129 | 
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { nextTick, reactive, ref, unref } from 'vue'; | ||
| 3 | + import { List, Button, Tooltip, Card } from 'ant-design-vue'; | ||
| 4 | + import { PageWrapper } from '/@/components/Page'; | ||
| 5 | + import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons-vue'; | ||
| 6 | + import { BasicTable, useTable } from '/@/components/Table'; | ||
| 7 | + import { realTimeDataColumns } from '../../config/detail.config'; | ||
| 8 | + import { useWebSocket } from '@vueuse/core'; | ||
| 9 | + import { getAuthCache } from '/@/utils/auth'; | ||
| 10 | + import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum'; | ||
| 11 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 12 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
| 13 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
| 14 | + import HistoryData from './HistoryData.vue'; | ||
| 15 | + import { BasicModal, useModal } from '/@/components/Modal'; | ||
| 16 | + | ||
| 17 | + interface ReceiveMessage { | ||
| 18 | + data: { | ||
| 19 | + [key: string]: [number, string][]; | ||
| 20 | + }; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + interface DataSource { | ||
| 24 | + key?: string; | ||
| 25 | + value?: string; | ||
| 26 | + time?: number; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + const props = defineProps<{ | ||
| 30 | + deviceDetail: Record<'tbDeviceId', string>; | ||
| 31 | + }>(); | ||
| 32 | + | ||
| 33 | + const grid = { | ||
| 34 | + gutter: 8, | ||
| 35 | + column: 3, | ||
| 36 | + }; | ||
| 37 | + | ||
| 38 | + const token = getAuthCache(JWT_TOKEN_KEY); | ||
| 39 | + | ||
| 40 | + const socketInfo = reactive({ | ||
| 41 | + origin: `${import.meta.env.VITE_WEB_SOCKET}${token}`, | ||
| 42 | + attr: undefined as string | undefined, | ||
| 43 | + originData: [] as DataSource[], | ||
| 44 | + dataSource: [] as DataSource[], | ||
| 45 | + sendValue: { | ||
| 46 | + tsSubCmds: [ | ||
| 47 | + { | ||
| 48 | + entityType: 'DEVICE', | ||
| 49 | + entityId: props.deviceDetail!.tbDeviceId || 'd2526c70-60a9-11ed-ba9c-6b98bfcc8255', | ||
| 50 | + scope: 'LATEST_TELEMETRY', | ||
| 51 | + cmdId: 1, | ||
| 52 | + }, | ||
| 53 | + ], | ||
| 54 | + }, | ||
| 55 | + }); | ||
| 56 | + | ||
| 57 | + const [registerForm, { getFieldsValue }] = useForm({ | ||
| 58 | + schemas: [ | ||
| 59 | + { | ||
| 60 | + field: 'value', | ||
| 61 | + label: '键/值', | ||
| 62 | + component: 'Input', | ||
| 63 | + colProps: { span: 8 }, | ||
| 64 | + componentProps: { | ||
| 65 | + placeholder: '请输入键/值', | ||
| 66 | + }, | ||
| 67 | + }, | ||
| 68 | + ], | ||
| 69 | + labelWidth: 100, | ||
| 70 | + compact: true, | ||
| 71 | + showAdvancedButton: true, | ||
| 72 | + submitFunc: async () => { | ||
| 73 | + try { | ||
| 74 | + const { value } = getFieldsValue() || {}; | ||
| 75 | + if (!value) setTableData(socketInfo.originData); | ||
| 76 | + const data = unref(socketInfo.originData).filter( | ||
| 77 | + (item) => item.key?.includes(value) || item.value?.includes(value) | ||
| 78 | + ); | ||
| 79 | + await nextTick(); | ||
| 80 | + socketInfo.dataSource = data; | ||
| 81 | + setTableData(data); | ||
| 82 | + } catch (error) {} | ||
| 83 | + }, | ||
| 84 | + resetFunc: async () => { | ||
| 85 | + try { | ||
| 86 | + socketInfo.dataSource = socketInfo.originData; | ||
| 87 | + setTableData(socketInfo.originData); | ||
| 88 | + } catch (error) {} | ||
| 89 | + }, | ||
| 90 | + }); | ||
| 91 | + | ||
| 92 | + const [registerTable, { setTableData }] = useTable({ | ||
| 93 | + columns: realTimeDataColumns, | ||
| 94 | + showTableSetting: true, | ||
| 95 | + bordered: true, | ||
| 96 | + showIndexColumn: false, | ||
| 97 | + }); | ||
| 98 | + | ||
| 99 | + const [registerModal, { openModal }] = useModal(); | ||
| 100 | + | ||
| 101 | + enum Mode { | ||
| 102 | + TABLE = 'table', | ||
| 103 | + CARD = 'card', | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + const mode = ref<Mode>(Mode.CARD); | ||
| 107 | + | ||
| 108 | + const switchMode = async (value: Mode) => { | ||
| 109 | + mode.value = value; | ||
| 110 | + await nextTick(); | ||
| 111 | + setTableData(socketInfo.dataSource); | ||
| 112 | + }; | ||
| 113 | + | ||
| 114 | + const { createMessage } = useMessage(); | ||
| 115 | + | ||
| 116 | + const { send, close, data } = useWebSocket(socketInfo.origin, { | ||
| 117 | + onConnected() { | ||
| 118 | + send(JSON.stringify(socketInfo.sendValue)); | ||
| 119 | + }, | ||
| 120 | + async onMessage() { | ||
| 121 | + try { | ||
| 122 | + const value = JSON.parse(unref(data)) as ReceiveMessage; | ||
| 123 | + if (value) { | ||
| 124 | + const { data } = value; | ||
| 125 | + const keys = Object.keys(data); | ||
| 126 | + socketInfo.originData = socketInfo.dataSource = keys.map((key) => { | ||
| 127 | + const [time, value] = data[key].at(0) || []; | ||
| 128 | + return { key, value, time }; | ||
| 129 | + }); | ||
| 130 | + | ||
| 131 | + await nextTick(); | ||
| 132 | + setTableData(socketInfo.dataSource); | ||
| 133 | + } | ||
| 134 | + } catch (error) {} | ||
| 135 | + }, | ||
| 136 | + onDisconnected() { | ||
| 137 | + console.log('断开连接了'); | ||
| 138 | + close(); | ||
| 139 | + }, | ||
| 140 | + onError() { | ||
| 141 | + createMessage.error('webSocket连接超时,请联系管理员'); | ||
| 142 | + }, | ||
| 143 | + }); | ||
| 144 | + | ||
| 145 | + const handleShowDetail = (record: DataSource) => { | ||
| 146 | + const { key } = record; | ||
| 147 | + socketInfo.attr = key; | ||
| 148 | + openModal(true); | ||
| 149 | + }; | ||
| 150 | +</script> | ||
| 151 | + | ||
| 152 | +<template> | ||
| 153 | + <PageWrapper | ||
| 154 | + dense | ||
| 155 | + content-class="flex flex-col bg-transparent p-4" | ||
| 156 | + :content-style="{ backgroundColor: '#F0F2F5' }" | ||
| 157 | + > | ||
| 158 | + <section class="flex flex-col justify-between w-full bg-light-50 pt-3 mb-4"> | ||
| 159 | + <div class="flex-auto"> | ||
| 160 | + <BasicForm @register="registerForm" /> | ||
| 161 | + </div> | ||
| 162 | + </section> | ||
| 163 | + <section class="bg-light-50"> | ||
| 164 | + <div v-show="mode === Mode.CARD" class="flex h-70px items-center justify-end p-2"> | ||
| 165 | + <Tooltip title="卡片模式"> | ||
| 166 | + <Button | ||
| 167 | + :class="[mode === Mode.CARD && '!bg-blue-500 svg:text-light-50']" | ||
| 168 | + class="!p-2 !children:flex flex justify-center items-center border-r-0" | ||
| 169 | + @click="switchMode(Mode.CARD)" | ||
| 170 | + > | ||
| 171 | + <AppstoreOutlined /> | ||
| 172 | + </Button> | ||
| 173 | + </Tooltip> | ||
| 174 | + | ||
| 175 | + <Tooltip title="列表模式"> | ||
| 176 | + <Button | ||
| 177 | + class="!p-2 !children:flex flex justify-center items-center" | ||
| 178 | + @click="switchMode(Mode.TABLE)" | ||
| 179 | + > | ||
| 180 | + <BarsOutlined /> | ||
| 181 | + </Button> | ||
| 182 | + </Tooltip> | ||
| 183 | + </div> | ||
| 184 | + <List | ||
| 185 | + v-if="mode === Mode.CARD" | ||
| 186 | + class="list-mode !px-2" | ||
| 187 | + :data-source="socketInfo.dataSource" | ||
| 188 | + :grid="grid" | ||
| 189 | + > | ||
| 190 | + <template #renderItem="{ item }"> | ||
| 191 | + <List.Item> | ||
| 192 | + <Card class="shadow-md"> | ||
| 193 | + <template #title> | ||
| 194 | + <span class="text-base font-normal">{{ item.key }}</span> | ||
| 195 | + </template> | ||
| 196 | + <template #extra> | ||
| 197 | + <Button type="link" class="!p-0" @click="handleShowDetail(item)">历史数据</Button> | ||
| 198 | + </template> | ||
| 199 | + <section> | ||
| 200 | + <div class="flex font-bold text-lg mb-4"> | ||
| 201 | + <div>{{ item.value }}</div> | ||
| 202 | + </div> | ||
| 203 | + <div class="text-dark-800 text-xs">{{ formatToDateTime(item.time) }}</div> | ||
| 204 | + </section> | ||
| 205 | + </Card> | ||
| 206 | + </List.Item> | ||
| 207 | + </template> | ||
| 208 | + </List> | ||
| 209 | + </section> | ||
| 210 | + | ||
| 211 | + <BasicTable v-if="mode === Mode.TABLE" @register="registerTable"> | ||
| 212 | + <template #toolbar> | ||
| 213 | + <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2"> | ||
| 214 | + <Tooltip title="卡片模式"> | ||
| 215 | + <Button | ||
| 216 | + class="!p-2 !children:flex flex justify-center items-center border-r-0" | ||
| 217 | + @click="switchMode(Mode.CARD)" | ||
| 218 | + > | ||
| 219 | + <AppstoreOutlined /> | ||
| 220 | + </Button> | ||
| 221 | + </Tooltip> | ||
| 222 | + | ||
| 223 | + <Tooltip title="列表模式"> | ||
| 224 | + <Button | ||
| 225 | + :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']" | ||
| 226 | + class="!p-2 !children:flex flex justify-center items-center" | ||
| 227 | + @click="switchMode(Mode.TABLE)" | ||
| 228 | + > | ||
| 229 | + <BarsOutlined /> | ||
| 230 | + </Button> | ||
| 231 | + </Tooltip> | ||
| 232 | + </div> | ||
| 233 | + </template> | ||
| 234 | + </BasicTable> | ||
| 235 | + <BasicModal @register="registerModal" width="50%" destroy-on-close> | ||
| 236 | + <HistoryData :deviceDetail="props.deviceDetail" :attr="socketInfo.attr" /> | ||
| 237 | + </BasicModal> | ||
| 238 | + </PageWrapper> | ||
| 239 | +</template> | ||
| 240 | + | ||
| 241 | +<style scoped lang="less"> | ||
| 242 | + .list-mode:deep(.ant-card-head) { | ||
| 243 | + border-bottom: 0; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + .list-mode:deep(.ant-card-body) { | ||
| 247 | + padding-top: 0; | ||
| 248 | + } | ||
| 249 | +</style> |