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> |