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 | 16 | @open-gateway-device="handleOpenGatewayDevice" |
17 | 17 | /> |
18 | 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 | 23 | <RealTimeData :deviceDetail="deviceDetail" /> |
21 | 24 | </TabPane> |
22 | 25 | <TabPane key="7" tab="历史数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> |
23 | 26 | <HistoryData :deviceDetail="deviceDetail" /> |
24 | - </TabPane> | |
27 | + </TabPane> --> | |
25 | 28 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> |
26 | 29 | <CommandIssuance :deviceDetail="deviceDetail" /> |
27 | 30 | </TabPane> |
... | ... | @@ -50,13 +53,14 @@ |
50 | 53 | |
51 | 54 | import { Tabs } from 'ant-design-vue'; |
52 | 55 | import Detail from '../tabs/Detail.vue'; |
53 | - import RealTimeData from '../tabs/RealTimeData.vue'; | |
56 | + // import RealTimeData from '../tabs/RealTimeData.vue'; | |
54 | 57 | import Alarm from '../tabs/Alarm.vue'; |
55 | 58 | import ChildDevice from '../tabs/ChildDevice.vue'; |
56 | 59 | import TBoxDetail from '../tabs/TBoxDetail.vue'; |
57 | 60 | import CommandIssuance from '../tabs/CommandIssuance.vue'; |
58 | 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 | 64 | export default defineComponent({ |
61 | 65 | name: 'DeviceModal', |
62 | 66 | components: { |
... | ... | @@ -64,12 +68,13 @@ |
64 | 68 | Tabs, |
65 | 69 | TabPane: Tabs.TabPane, |
66 | 70 | Detail, |
67 | - RealTimeData, | |
71 | + // RealTimeData, | |
68 | 72 | Alarm, |
69 | 73 | ChildDevice, |
70 | 74 | CommandIssuance, |
71 | 75 | TBoxDetail, |
72 | - HistoryData, | |
76 | + // HistoryData, | |
77 | + ModelOfMatter, | |
73 | 78 | }, |
74 | 79 | emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], |
75 | 80 | setup(_props, { emit }) { | ... | ... |
... | ... | @@ -23,6 +23,7 @@ |
23 | 23 | |
24 | 24 | const props = defineProps<{ |
25 | 25 | deviceDetail: DeviceDetail; |
26 | + attr?: string; | |
26 | 27 | }>(); |
27 | 28 | |
28 | 29 | const chartRef = ref(); |
... | ... | @@ -119,6 +120,10 @@ |
119 | 120 | const { tbDeviceId } = props.deviceDetail || {}; |
120 | 121 | try { |
121 | 122 | deviceAttrs.value = (await getDeviceDataKeys(tbDeviceId)) || []; |
123 | + | |
124 | + if (props.attr) { | |
125 | + method.setFieldsValue({ keys: props.attr }); | |
126 | + } | |
122 | 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> | ... | ... |