lvkun пре 3 година
родитељ
комит
2e6abdad6b

+ 32 - 0
src/api/iot/device.ts

@@ -99,3 +99,35 @@ export const getDeviceCount = (params: {modelId: string}) => {
     params
   })
 }
+
+/**
+ * 此函数检索特定设备的消息。
+ * @param params - `params` 对象是传递给 `getDeviceMsgPage` 函数的参数。它有一个属性“deviceId”,它是一个表示设备 ID
+ * 的字符串。此参数用于向“/toTransport/msg”端点发出 GET 请求以检索与以下内容相关的消息
+ * @returns `getDeviceMsgPage` 函数返回一个解析为 `IOT.API.DEVICE.Msg` 类型对象的
+ * Promise。根据传入的“deviceId”参数,此对象可能包含有关发送到特定设备的消息的信息。
+ */
+export const getDeviceMsgList = (params: {deviceId: string}) => {
+  return request<IOT.API.DEVICE.Msg[]>({
+    url: '/toTransport/msg',
+    method: 'GET',
+    params
+  })
+}
+
+// "deviceId": “”设备id 必需
+// "msgPayload":"" 消息类容 必需
+// "msgLabel":“”消息名称 必需
+// “topic":"" 主题 非必需
+/**
+ * 该函数向 IoT 系统中的设备添加消息。
+ * @param data - `data` 参数是一个包含以下属性的对象:
+ * @returns `addDeviceMsg` 函数返回一个解析为 `IOT.API.DEVICE.Msg` 类型对象的 Promise。
+ */
+export const addDeviceMsg = (data: {deviceId: string, msgPayload: string, msgLabel: string, topic: string}) => {
+  return request<IOT.API.DEVICE.Msg>({
+    url: '/toTransport/msg',
+    method: 'POST',
+    data
+  })
+}

+ 27 - 1
src/controller/iot/device.ts

@@ -1,4 +1,8 @@
-import { addDevice, addSubDevice, delDevice, delDeviceMul, delDeviceTag, getDeviceById, getDeviceCount, getDeviceList, getDeviceTag, getSubDeviceList, updateDeviceLabel } from '@/api/iot/device'
+import {
+  addDevice, addSubDevice, delDevice, delDeviceMul, delDeviceTag,
+  getDeviceById, getDeviceCount, getDeviceList, getDeviceMsgList, addDeviceMsg, getDeviceTag, getSubDeviceList, updateDeviceLabel
+} from '@/api/iot/device'
+import { DeviceMsgEnum } from '@/enum/common'
 import { message } from 'ant-design-vue'
 
 export class DeviceContriller {
@@ -24,6 +28,14 @@ export class DeviceContriller {
     ['DISABLED', { color: 'grey', name: '禁止连接', key: 'DISABLED' }]
   ])
 
+  static deviceMag = new Map([
+    [DeviceMsgEnum.QUEUED, { name: '队列中', key: DeviceMsgEnum.QUEUED }],
+    [DeviceMsgEnum.SENT, { name: '已发送', key: DeviceMsgEnum.SENT }],
+    [DeviceMsgEnum.DELIVERED, { name: '设备收到', key: DeviceMsgEnum.DELIVERED }],
+    [DeviceMsgEnum.TIMEOUT, { name: '超时', key: DeviceMsgEnum.TIMEOUT }],
+    [DeviceMsgEnum.FAILED, { name: '失败', key: DeviceMsgEnum.FAILED }]
+  ])
+
   static async page (params: IOT.API.DEVICE.QueryPamars) {
     const { data: _data, sum } = await getDeviceList(params)
     const data = _data.map(item => {
@@ -57,10 +69,12 @@ export class DeviceContriller {
     return data
   }
 
+  /** 分页获取子设备列表 */
   static async pageSub (id: string, params: IOT.API.DEVICE.QueryPamars) {
     return await getSubDeviceList(id, params)
   }
 
+  /** 新增子设备 */
   static async postSub (data: IOT.API.DEVICE.SubBodyParams) {
     await addSubDevice(data)
     message.success('新增成功')
@@ -79,6 +93,7 @@ export class DeviceContriller {
     await updateDeviceLabel(data)
   }
 
+  /** 设备统计 */
   static async statistics (params: {modelId: string}) {
     const { data } = await getDeviceCount(params)
 
@@ -91,4 +106,15 @@ export class DeviceContriller {
       }
     })
   }
+
+  /** 消息下发 获取列表 */
+  static async listDeviceMsg (params: {deviceId: string}) {
+    const { data } = await getDeviceMsgList(params)
+    return data
+  }
+
+  /** 消息下发 下发消息 */
+  static async addDeviceMsg (data: {deviceId: string, msgPayload: string, msgLabel: string, topic: string}) {
+    return await addDeviceMsg(data)
+  }
 }

+ 9 - 0
src/enum/common.ts

@@ -27,3 +27,12 @@ export enum DeviceAuthTypeEnum {
   'SECRET' = 'SECRET',
   'X509CERT' = 'X509CERT'
 }
+
+export enum DeviceMsgEnum {
+  'QUEUED' = 'QUEUED', //  队列中
+  'SENT' = 'SENT', // 已发送
+  'DELIVERED' = 'DELIVERED', // 设备收到
+  'SUCCESSFUL' = 'SUCCESSFUL', // 成功
+  'TIMEOUT' = 'TIMEOUT', // 超时
+  'FAILED' = 'FAILED', // 失败
+}

+ 0 - 4
src/layout/components/Sidebar/index.vue

@@ -40,10 +40,6 @@ console.log('appRouter.router.sider.route:', appRouter.router.sider.route)
 
 const collapsed = ref<boolean>(false)
 
-// const selectedKeys2 = ref<string[]>([appRouter.router.sider!.selectPath])
-// const openKeys = ref<string[]>(appRouter.router.sider!.openKeys)
-
-// const selectedKeys2 = ref<string[]>([appRouter.router.sider!.selectPath])
 const selectedKeys2 = ref<string[]>([router.currentRoute.value.path])
 const openKeys = ref<string[]>(appRouter.router.sider!.openKeys)
 

+ 139 - 0
src/pages/Iot/device/components/cloudview.vue

@@ -0,0 +1,139 @@
+<template>
+  <a-card
+    style="width: 100%"
+    :tab-list="tabListNoTitle"
+    :active-tab-key="state.tasActive"
+    @tabChange="onTabChange"
+  >
+    <template v-if="state.tasActive == 'msg'" >
+      <a-alert :message="tipMessage" type="info" show-icon />
+      <div class="subtitle" >注意:平台为每个设备默认最多保存20条消息,超过20条后,后续的消息会替换下发最早的消息。</div>
+      <a-row>
+        <a-col> <a-button type="primary" @click="state.visible = true">下发消息</a-button>  </a-col>
+      </a-row>
+      <a-table
+        :columns="columns"
+        :dataSource="state.msgDataSource"
+        :loading="state.loading"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'action'">
+            <a @click="openDetailModal(record)" >详情</a>
+          </template>
+        </template>
+      </a-table>
+    </template>
+    <template v-if="state.tasActive == 'cmd'" >
+
+    </template>
+  </a-card>
+
+  <modal-pro
+    :title="modalTitle"
+    :visible="state.visible"
+    @cancel="state.visible = false"
+    @ok="ok"
+  >
+
+  </modal-pro>
+</template>
+
+<script lang="ts" setup >
+
+import { DeviceContriller } from '@/controller'
+import { computed, onMounted, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+
+const msg = '消息下发不依赖产品模型,平台会以异步方式(消息下发后无需等待设备侧回复响应)下发消息给设备。当前仅MQTT设备支持消息下发。'
+const cmdMsg = '如果设备所属产品定义了命令功能,则您可以通过应用调用平台接口或者操作下面的“下发命令”按钮下发命令。当前MQTT设备仅支持同步命令下发,NB设备仅支持异步命令下发 。'
+const modalTitle = 'modal-pro'
+const tabListNoTitle = [
+  {
+    key: 'msg',
+    tab: '消息下发'
+  },
+  {
+    key: 'cmd',
+    tab: '命令下发'
+  }
+]
+
+const columns = [
+  {
+    title: '状态',
+    dataIndex: 'status'
+  },
+  {
+    title: '消息名称',
+    dataIndex: 'msgLabel'
+  },
+  {
+    title: '消息id',
+    dataIndex: 'msgId'
+  },
+  {
+    title: '消息内容',
+    dataIndex: 'msgPayload'
+  },
+  {
+    title: '消息创建时间',
+    dataIndex: 'createAt'
+  },
+  {
+    title: '状态',
+    dataIndex: 'status'
+  },
+  {
+    title: '操纵',
+    key: 'action'
+  }
+]
+
+const route = useRoute()
+const deviceId = route.query.id as string
+
+const tipMessage = computed(() => state.tasActive === 'msg' ? msg : cmdMsg)
+
+const state = reactive<{
+  tasActive: 'msg' | 'cmd',
+  msgDataSource: IOT.API.DEVICE.Msg[],
+  visible: boolean,
+  loading: boolean,
+  detailVisible: boolean,
+  msgDetail: IOT.API.DEVICE.Msg | {}
+}>({
+  tasActive: 'msg',
+  msgDataSource: [],
+  visible: false,
+  loading: false,
+  detailVisible: false,
+  msgDetail: {}
+})
+
+const ok = () => {
+
+}
+
+const openDetailModal = (record: IOT.API.DEVICE.Msg) => {
+  state.detailVisible = true
+  state.msgDetail = record
+}
+
+const getDeviceMsgList = async () => {
+  state.msgDataSource = await DeviceContriller.listDeviceMsg({ deviceId })
+}
+
+const onTabChange = (value: 'msg' | 'cmd') => state.tasActive = value
+
+onMounted(() => {
+  getDeviceMsgList()
+})
+</script>
+
+<style lang="less" scoped >
+@import '~@/styles/theme.less';
+.subtitle {
+  color: @sublabel-color;
+  margin: 20px 0px;
+}
+</style>

+ 53 - 5
src/pages/Iot/device/components/overview.vue

@@ -1,9 +1,44 @@
 <template>
-  <div class="overview" >
-    <a-row>
-      <a-col><a-space><span>{{state.deviceDetail?.deviceCode}}</span><a-button type="primary" @click="state.visible = true" >修改</a-button></a-space></a-col>
+
+    <a-row  align="middle" style="width: 100%;height: 68px" class="title">
+      <a-col  >
+        <a-space :size="50" >
+          <span>{{state.deviceDetail?.deviceCode}}<a-button type="primary" @click="state.visible = true" >修改</a-button></span>
+          <span>{{state.deviceDetail?.deviceStatus}}</span>
+          <a-tag style="scale: 1.2;" >
+            <span>所属产品:{{state.deviceDetail?.modelLabel}}</span>
+          </a-tag>
+        </a-space>
+        </a-col>
+    </a-row>
+    <a-descriptions
+      :column="{ xs: 1, sm: 1, md: 2, lg: 2, xl: 2}"
+      :labelStyle="{color: '#8a8e99'}"
+      :contentStyle="{fontSize: '12px'}"
+      style="margin-top: 20px;"
+    >
+        <a-descriptions-item label="设备标识码">{{state.deviceDetail?.modelLabel}}</a-descriptions-item>
+        <a-descriptions-item label="设备ID">{{state.deviceDetail?.id}}</a-descriptions-item>
+        <a-descriptions-item label="认证类型">{{state.deviceDetail?.deviceNodeType}}</a-descriptions-item>
+        <a-descriptions-item label="设备密钥">{{state.deviceDetail?.deviceCode}}</a-descriptions-item>
+        <a-descriptions-item label="注册时间">{{state.deviceDetail?.lastActivityTs}}</a-descriptions-item>
+        <a-descriptions-item label="节点类型">{{  state.deviceDetail?.deviceNodeType === 'GATEWAY' ? '直连' : '非直连'}}</a-descriptions-item>
+    </a-descriptions>
+
+    <a-row  justify="space-between" align="middle" style="width: 100%;height: 68px; margin-bottom: 10px;" class="title">
+      <a-col>最新上报数据</a-col>
+      <a-col><a-button type="primary" >查看全部属性</a-button></a-col>
+    </a-row>
+
+    <a-row :gutter="[8, 8]" >
+      <a-col :lg="8" :md="8" :sm="24"  v-for="item in 4" :key="item" >
+        <div class="data">
+          <div>NH3</div>
+          <div> {{`<test>`}}</div>
+          <div>2023/04/26 22:05:03</div>
+        </div>
+      </a-col>
     </a-row>
-  </div>
 
   <modal-pro
     label="hellp"
@@ -15,7 +50,7 @@
 </template>
 
 <script lang="ts" setup >
-import { DeviceContriller } from '@/controller'
+import { CommonController, DeviceContriller } from '@/controller'
 import { onMounted, reactive } from 'vue'
 import { useRoute } from 'vue-router'
 
@@ -38,4 +73,17 @@ onMounted(async () => {
 </script>
 
 <style lang="less" scoped >
+@import '~@/styles/theme.less';
+.title {
+  background-color: @bg-color-1;
+  padding-left: 40px;
+}
+.data {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
+  align-items: center;
+  height: 100px;
+}
+
 </style>

+ 23 - 5
src/pages/Iot/device/detail.vue

@@ -5,16 +5,18 @@
         v-for="item in tabs"
         :key="item.key"
         :tab="item.name"
-      >
-        <over-view />
-      </a-tab-pane>
+      />
     </a-tabs>
+    <OverView  v-if="state.tabsActive === '1'"/>
+    <CloudView   v-if="state.tabsActive === '2'"/>
   </a-card>
 </template>
 
 <script lang="ts" setup >
-import { reactive } from 'vue'
+import { computed, reactive } from 'vue'
 import OverView from './components/overview.vue'
+import CloudView from './components/cloudview.vue'
+
 const tabs = [
   {
     name: '概述',
@@ -43,8 +45,24 @@ const tabs = [
 ]
 
 const state = reactive({
-  tabsActive: '1'
+  tabsActive: '2'
 })
+
+const domainCom = () => {
+  switch (state.tabsActive) {
+    case '1':
+      return OverView
+
+    case '2':
+
+      return CloudView
+    default:
+      return CloudView
+  }
+}
+
+console.log('domainCom:', domainCom())
+
 </script>
 
 <style lang="less" scoped >

+ 4 - 1
src/styles/theme.less

@@ -6,4 +6,7 @@
 
 @label-color: #8a8e99; 
 
-@sublabel-color: hsla(0,0%,40%,.4);
+@sublabel-color: hsla(0,0%,40%,.4);
+
+
+@bg-color-1: rgba(16,103,238,.04);

+ 10 - 0
src/type/iot.d.ts

@@ -116,6 +116,16 @@ declare namespace IOT {
         'CONNECT': string,
         'DISCONNECT': string
       }
+
+      interface Msg {
+        'id': string,
+        'deviceId': string,
+        'status': 'SENT', // 消息状态  值有QUEUED 队列中,SENT 已发送,DELIVERED 设备收到 SUCCESSFUL 成功 TIMEOUT 超时 FAILED 失败
+        'msgPayload': string,
+        'msgLabel': string,
+        'topic': string,
+        'msgId': number
+      }
     }
 
     namespace EVENT {