Commit 77d82afe75d723223c8ae9935b2e44c68ab3baa2

Authored by ww
1 parent 84ebe0b7

pref: 优化设备详情物模型数据,结构体展示

... ... @@ -12,6 +12,7 @@ import { PageEnum } from '/@/enums/pageEnum';
12 12 import { useGo } from '/@/hooks/web/usePage';
13 13 import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail';
14 14 import { findDictItemByCode } from '/@/api/system/dict';
  15 +import { isNullOrUnDef } from '/@/utils/is';
15 16
16 17 // 设备详情的描述
17 18 export const descSchema = (emit: EmitType): DescItem[] => {
... ... @@ -104,8 +105,11 @@ export const realTimeDataColumns: BasicColumn[] = [
104 105 },
105 106 {
106 107 title: '值',
107   - dataIndex: 'value',
  108 + dataIndex: 'rawValue',
108 109 width: 160,
  110 + format(text) {
  111 + return isNullOrUnDef(text) ? '--' : text;
  112 + },
109 113 },
110 114 {
111 115 title: '最后更新时间',
... ...
  1 +import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel';
  2 +import { DataType, Specs } from '/@/api/device/model/modelOfMatterModel';
  3 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  4 +import { isArray } from '/@/utils/is';
  5 +
  6 +export interface StructValueItemType extends BaseAdditionalInfo {
  7 + name: string;
  8 + key: string;
  9 + value?: string | number | boolean;
  10 +}
  11 +
  12 +export interface BaseAdditionalInfo {
  13 + unit?: string;
  14 + boolClose?: string;
  15 + boolOpen?: string;
  16 + unitName?: string;
  17 +}
  18 +
  19 +export interface SocketInfoDataSourceItemType extends BaseAdditionalInfo {
  20 + accessMode: string;
  21 + key: string;
  22 + name: string;
  23 + type: DataTypeEnum;
  24 + detail: DeviceModelOfMatterAttrs;
  25 + time?: number;
  26 + value?: string | number | boolean | Record<string, StructValueItemType>;
  27 + expand?: boolean;
  28 + showHistoryDataButton?: boolean;
  29 + rawValue?: any;
  30 +}
  31 +
  32 +export function buildTableDataSourceByObjectModel(
  33 + models: DeviceModelOfMatterAttrs[]
  34 +): SocketInfoDataSourceItemType[] {
  35 + function getAdditionalInfoByDataType(dataType?: DataType) {
  36 + const { specs } = dataType || {};
  37 + if (isArray(specs)) return {};
  38 + const { unit, boolClose, boolOpen, unitName } = (specs as Partial<Specs>) || {};
  39 + return { unit, boolClose, boolOpen, unitName };
  40 + }
  41 +
  42 + return models.map((item) => {
  43 + const { accessMode, identifier, name, detail } = item;
  44 + const { dataType } = detail;
  45 + const { type, specs } = dataType || {};
  46 +
  47 + const res = {
  48 + accessMode,
  49 + name,
  50 + key: identifier,
  51 + type: type!,
  52 + detail: item,
  53 + };
  54 +
  55 + if (isArray(specs) && type === DataTypeEnum.STRUCT) {
  56 + const value: Record<string, StructValueItemType> = specs
  57 + .filter((item) => item.dataType?.type !== DataTypeEnum.STRUCT)
  58 + .reduce((prev, next) => {
  59 + const { identifier, functionName, dataType } = next;
  60 + return {
  61 + ...prev,
  62 + [identifier]: {
  63 + key: identifier!,
  64 + name: functionName!,
  65 + type: dataType?.type,
  66 + ...getAdditionalInfoByDataType(dataType),
  67 + },
  68 + };
  69 + }, {});
  70 +
  71 + Object.assign(res, { value });
  72 + } else {
  73 + Object.assign(res, getAdditionalInfoByDataType(dataType));
  74 + }
  75 +
  76 + return res;
  77 + });
  78 +}
... ...
... ... @@ -13,8 +13,7 @@
13 13 import HistoryData from './HistoryData.vue';
14 14 import { BasicModal, useModal } from '/@/components/Modal';
15 15 import { getDeviceAttrs } from '/@/api/device/deviceManager';
16   - import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
17   - import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  16 + import { DeviceRecord } from '/@/api/device/model/deviceModel';
18 17 import { isArray, isNull, isObject } from '/@/utils/is';
19 18 import { useGlobSetting } from '/@/hooks/setting';
20 19 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
... ... @@ -23,8 +22,14 @@
23 22 import { ObjectModelCommandDeliveryModal } from './ObjectModelCommandDeliveryModal';
24 23 import { ModalParamsType } from '/#/utils';
25 24 import { AreaChartOutlined } from '@ant-design/icons-vue';
26   - import { SvgIcon } from '/@/components/Icon';
  25 + import { SvgIcon, Icon } from '/@/components/Icon';
27 26 import { DataTypeEnum } from '/@/enums/objectModelEnum';
  27 + import {
  28 + buildTableDataSourceByObjectModel,
  29 + SocketInfoDataSourceItemType,
  30 + StructValueItemType,
  31 + } from './ModelOfMatter.config';
  32 + import { useJsonParse } from '/@/hooks/business/useJsonParse';
28 33
29 34 interface ReceiveMessage {
30 35 data: {
... ... @@ -32,17 +37,6 @@
32 37 };
33 38 }
34 39
35   - interface DataSource {
36   - key?: string;
37   - value?: string;
38   - time?: number;
39   - type?: string;
40   - boolClose?: string;
41   - boolOpen?: string;
42   - name?: string;
43   - detail: DeviceModelOfMatterAttrs;
44   - }
45   -
46 40 const props = defineProps<{
47 41 deviceDetail: DeviceRecord;
48 42 }>();
... ... @@ -70,13 +64,14 @@
70 64 cmdId: 0,
71 65 origin: `${socketUrl}${token}`,
72 66 attr: undefined as string | undefined,
73   - dataSource: [] as DataSource[],
  67 + dataSource: [] as SocketInfoDataSourceItemType[],
  68 + rawDataSource: [] as SocketInfoDataSourceItemType[],
74 69 message: {} as ReceiveMessage['data'],
75   - attrKeys: [] as DeviceModelOfMatterAttrs[],
76   - filterAttrKeys: [] as DeviceModelOfMatterAttrs[],
  70 + attrKeys: [] as string[],
  71 + filterAttrKeys: [] as string[],
77 72 });
78 73
79   - const getPaginationAttrkey = computed<DeviceModelOfMatterAttrs[]>(() => {
  74 + const getPaginationAttrkey = computed(() => {
80 75 const { current = 1, pageSize = 10 } = pagination;
81 76 return (
82 77 socketInfo.filterAttrKeys && socketInfo.filterAttrKeys.length
... ... @@ -104,9 +99,7 @@
104 99 entityId: props.deviceDetail!.tbDeviceId,
105 100 scope: 'LATEST_TELEMETRY',
106 101 cmdId: socketInfo.cmdId,
107   - keys: unref(getPaginationAttrkey)
108   - .map((item) => item.identifier)
109   - .join(','),
  102 + keys: unref(getPaginationAttrkey).join(','),
110 103 },
111 104 ],
112 105 };
... ... @@ -120,9 +113,7 @@
120 113 entityId: props.deviceDetail!.tbDeviceId,
121 114 scope: 'LATEST_TELEMETRY',
122 115 cmdId: socketInfo.cmdId,
123   - keys: unref(getPaginationAttrkey)
124   - .map((item) => item.identifier)
125   - .join(','),
  116 + keys: unref(getPaginationAttrkey).join(','),
126 117 },
127 118 ],
128 119 };
... ... @@ -150,12 +141,14 @@
150 141 pagination.current = 1;
151 142
152 143 socketInfo.filterAttrKeys = value
153   - ? unref(socketInfo.attrKeys).filter(
154   - (item) =>
155   - item.identifier?.toUpperCase().includes(value.toUpperCase()) ||
156   - item.name?.toUpperCase().includes(value.toUpperCase())
157   - )
158   - : socketInfo.attrKeys;
  144 + ? unref(socketInfo.rawDataSource)
  145 + .filter(
  146 + (item) =>
  147 + item.key?.toUpperCase().includes(value.toUpperCase()) ||
  148 + item.name?.toUpperCase().includes(value.toUpperCase())
  149 + )
  150 + .map((item) => item.key)
  151 + : socketInfo.rawDataSource.map((item) => item.key);
159 152
160 153 await nextTick();
161 154
... ... @@ -206,58 +199,10 @@
206 199
207 200 const { createMessage } = useMessage();
208 201
209   - const getUnit = (record: StructJSON) => {
210   - const { dataType } = record;
211   - const { specs, type } = dataType! || {};
212   - const unitName = isObject(specs)
213   - ? (specs as Specs).unitName && (specs as Specs).unit
214   - ? `${(specs as Specs).unitName}`
215   - : ''
216   - : '';
217   - const { boolClose, boolOpen } = (specs || {}) as Specs;
218   -
219   - return { unit: unitName, boolClose, boolOpen, type };
220   - };
221   -
222   - const isStructAndTextType = (type: DataTypeEnum) => {
223   - return [DataTypeEnum.STRUCT, DataTypeEnum.STRING].includes(type);
224   - };
225   -
226 202 const setDataSource = () => {
227   - socketInfo.dataSource = (
228   - socketInfo.filterAttrKeys && socketInfo.filterAttrKeys.length
229   - ? socketInfo.filterAttrKeys
230   - : socketInfo.attrKeys
231   - ).map((item) => {
232   - const { identifier: key, name, detail, accessMode } = item;
233   - const { unit, boolClose, boolOpen, type } = getUnit(detail);
234   - const dataInfo = socketInfo.filterAttrKeys.find((item) => item.identifier === key);
235   -
236   - let time: number | undefined;
237   - let value: any | undefined;
238   - const message = socketInfo.message[key];
239   - if (message) {
240   - const [attrTime, attrValue] = message.at(0) || [];
241   - time = attrTime;
242   - value = attrValue;
243   - }
244   -
245   - return {
246   - key,
247   - value,
248   - time,
249   - name,
250   - unit,
251   - type,
252   - boolClose,
253   - boolOpen,
254   - accessMode,
255   - showHistoryDataButton: !isStructAndTextType(
256   - dataInfo?.detail.dataType?.type as unknown as DataTypeEnum
257   - ),
258   - detail: toRaw(item),
259   - };
260   - });
  203 + socketInfo.dataSource = socketInfo.filterAttrKeys.length
  204 + ? socketInfo.rawDataSource.filter((item) => socketInfo.filterAttrKeys.includes(item.key))
  205 + : socketInfo.rawDataSource;
261 206 };
262 207
263 208 const { send, close, data, open } = useWebSocket(socketInfo.origin, {
... ... @@ -272,9 +217,36 @@
272 217 if (value) {
273 218 const { data } = value;
274 219 const keys = Object.keys(data);
275   - keys.forEach((key) => {
276   - socketInfo.message[key] = value.data[key];
277   - });
  220 +
  221 + for (const key of keys) {
  222 + const item = socketInfo.rawDataSource.find((item) => item.key === key);
  223 + if (!item) continue;
  224 + const { type } = item;
  225 + const [firstItem] = data[key] || [];
  226 + const [time, value] = firstItem;
  227 +
  228 + item.rawValue = value;
  229 +
  230 + if (type === DataTypeEnum.STRUCT) {
  231 + const { flag, value: structJSON } = useJsonParse(value);
  232 + if (!flag || !isObject(item.value)) continue;
  233 +
  234 + const structKeys = Object.keys(structJSON);
  235 +
  236 + structKeys.forEach((key) => {
  237 + if ((item.value as Record<string, StructValueItemType>)?.[key]) {
  238 + (item.value as Record<string, StructValueItemType>)[key].value = structJSON[key];
  239 + }
  240 + });
  241 +
  242 + item.time = time;
  243 +
  244 + continue;
  245 + }
  246 +
  247 + item.value = value;
  248 + item.time = time;
  249 + }
278 250
279 251 setDataSource();
280 252
... ... @@ -296,7 +268,7 @@
296 268 setTableData(socketInfo.dataSource.filter((item) => !isNull(item.value)));
297 269 }
298 270
299   - const handleShowDetail = (record: DataSource) => {
  271 + const handleShowDetail = (record: SocketInfoDataSourceItemType) => {
300 272 const { key } = record;
301 273 socketInfo.attr = key;
302 274 openModal(true);
... ... @@ -305,23 +277,24 @@
305 277 onMounted(async () => {
306 278 const { deviceProfileId } = props.deviceDetail;
307 279 const value = await getDeviceAttrs({ deviceProfileId });
308   - socketInfo.attrKeys = isArray(value) ? value : [];
  280 + socketInfo.attrKeys = isArray(value) ? value.map((item) => item.identifier) : [];
  281 + socketInfo.rawDataSource = buildTableDataSourceByObjectModel(value);
309 282 setDataSource();
310 283 open();
311 284 });
312 285
313   - const formatValue = (item: DataSource) => {
  286 + const formatValue = (item: SocketInfoDataSourceItemType) => {
314 287 return item.type === DataTypeEnum.BOOL
315 288 ? !isNull(item.value)
316 289 ? !!Number(item.value)
317 290 ? item.boolOpen
318 291 : item.boolClose
319 292 : '--'
320   - : item.value || '--';
  293 + : (item.value as string) || '--';
321 294 };
322 295
323 296 const [register, { openModal: openSendCommandModal }] = useModal();
324   - const handleSendCommandModal = (data: DataSource) => {
  297 + const handleSendCommandModal = (data: SocketInfoDataSourceItemType) => {
325 298 openSendCommandModal(true, {
326 299 mode: DataActionModeEnum.READ,
327 300 record: { ...toRaw(data.detail), deviceDetail: props.deviceDetail as any },
... ... @@ -369,7 +342,7 @@
369 342 :grid="grid"
370 343 :pagination="pagination"
371 344 >
372   - <template #renderItem="{ item }">
  345 + <template #renderItem="{ item }: { item: SocketInfoDataSourceItemType }">
373 346 <List.Item>
374 347 <Card class="shadow-md">
375 348 <template #title>
... ... @@ -377,6 +350,16 @@
377 350 </template>
378 351 <template #extra>
379 352 <Space>
  353 + <Tooltip
  354 + :title="item.expand ? '收起' : '展开'"
  355 + v-if="item.type === DataTypeEnum.STRUCT"
  356 + >
  357 + <Icon
  358 + :icon="item.expand ? 'ant-design:up-outlined' : 'ant-design:down-outlined'"
  359 + class="cursor-pointer svg:text-blue-500"
  360 + @click="item.expand = !item.expand"
  361 + />
  362 + </Tooltip>
380 363 <Tooltip title="属性下发">
381 364 <SvgIcon
382 365 name="send-command"
... ... @@ -396,14 +379,49 @@
396 379 </Space>
397 380 </template>
398 381 <section class="min-h-16 flex flex-col justify-between">
399   - <div class="flex font-bold text-lg mb-4 gap-2">
  382 + <div
  383 + v-if="item.type !== DataTypeEnum.STRUCT"
  384 + class="flex font-bold text-lg mb-4 gap-2"
  385 + >
400 386 <Tooltip :title="formatValue(item)" placement="topLeft">
401 387 <div class="truncate">{{ formatValue(item) }}</div>
402 388 </Tooltip>
403   - <div class="text-xs flex items-center">{{ item.unit }}</div>
  389 + <div class="text-xs flex items-center">{{ item.unitName }}</div>
  390 + </div>
  391 + <div
  392 + class="mb-4 overflow-hidden flex flex-col gap-2 relative"
  393 + v-if="item.type == DataTypeEnum.STRUCT && isObject(item.value)"
  394 + :style="{ height: item.expand ? 'fit-content' : '28px' }"
  395 + >
  396 + <div
  397 + v-for="key in Object.keys(item.value)"
  398 + :key="key"
  399 + class="cursor-pointer flex justify-between items-center gap-2"
  400 + >
  401 + <div class="font-medium truncate">{{ item.value[key].name }}</div>
  402 + <div class="flex-auto font-bold text-lg text-left ml-4 truncate">
  403 + <Tooltip :title="formatValue(item)" placement="topLeft">
  404 + <span>
  405 + {{ formatValue(item.value[key] as SocketInfoDataSourceItemType) }}
  406 + </span>
  407 + </Tooltip>
  408 + <span class="text-xs font-normal ml-2">
  409 + {{ item.value[key].unitName }}
  410 + </span>
  411 + </div>
  412 + </div>
  413 + <Tooltip title="点击展开">
  414 + <div
  415 + v-show="!item.expand"
  416 + class="absolute top-2 right-0 text-blue-400 cursor-pointer text-xs"
  417 + @click="item.expand = !item.expand"
  418 + >
  419 + 更多
  420 + </div>
  421 + </Tooltip>
404 422 </div>
405 423 <div class="text-dark-800 text-xs">
406   - {{ item.value ? formatToDateTime(item.time, 'YYYY-MM-DD HH:mm:ss') : '--' }}
  424 + {{ item.time ? formatToDateTime(item.time, 'YYYY-MM-DD HH:mm:ss') : '--' }}
407 425 </div>
408 426 </section>
409 427 </Card>
... ... @@ -461,6 +479,26 @@
461 479 .device-things-model-table-mode:deep(.ant-table-placeholder) {
462 480 height: auto;
463 481 }
  482 +
  483 + .model-collapse {
  484 + border: none;
  485 +
  486 + :deep(.ant-collapse-header) {
  487 + display: none;
  488 + }
  489 +
  490 + :deep(.ant-collapse-item) {
  491 + border: none;
  492 + }
  493 +
  494 + :deep(.ant-collapse-content) {
  495 + border: none;
  496 + }
  497 +
  498 + :deep(.ant-collapse-content-box) {
  499 + padding: 0;
  500 + }
  501 + }
464 502 </style>
465 503
466 504 <style>
... ...