Commit b7a782838ca2696ac9440d69a9c94b3cef37ea5d

Authored by ww
1 parent 41e8ae43

feat: device detail add model of matter tab pane

1 1 {
2   - "i18n-ally.localesPaths": ["src/locales", "src/locales/lang", "public/resource/tinymce/langs"]
  2 + "i18n-ally.localesPaths": [
  3 + "src/locales",
  4 + "src/locales/lang",
  5 + "public/resource/tinymce/langs"
  6 + ],
  7 + "cSpell.words": [
  8 + "unref",
  9 + "VITE"
  10 + ]
3 11 }
... ...
... ... @@ -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>
... ...