Zimo 1 tydzień temu
rodzic
commit
9e93985c67

+ 8 - 1
src/api/pms/maotu/index.ts

@@ -17,7 +17,14 @@ export type WebtopoProjectLinkedDeviceVO = {
   deviceCode?: string
   deviceName?: string
   deviceStatus?: string | null
-  pointParams?: unknown[]
+  pointParams?: WebtopoProjectLinkedDevicePointParamVO[]
+}
+
+export type WebtopoProjectLinkedDevicePointParamVO = {
+  paramName?: string
+  paramCode?: string
+  paramValue?: unknown
+  paramUnit?: string
 }
 
 export type WebtopoProjectDetailVO = WebtopoProjectVO & {

+ 9 - 49
src/views/maotu/components/DeviceBindPanel.vue

@@ -41,8 +41,7 @@
                 clearable
                 class="w-1/1"
                 placeholder="请选择设备属性"
-                :disabled="!bindItem.editing || !bindItem.deviceId"
-                :loading="isDevicePropsLoading(bindItem.deviceId)">
+                :disabled="!bindItem.editing || !bindItem.deviceId">
                 <el-option
                   v-for="deviceProp in getDeviceProps(bindItem.deviceId)"
                   :key="deviceProp.value"
@@ -93,7 +92,6 @@
 </template>
 
 <script setup lang="ts">
-import { IotDeviceApi } from '@/api/pms/device'
 import type { WebtopoProjectLinkedDeviceVO } from '@/api/pms/maotu'
 import type { IDoneJson, IDoneJsonDeviceBind } from '@/components/mt-edit/store/types'
 import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
@@ -103,16 +101,6 @@ type DeviceBindRow = IDoneJsonDeviceBind & {
   editing?: boolean
 }
 
-type DevicePropOption = {
-  label: string
-  value: string
-}
-
-type DevicePropResponseItem = {
-  modelName?: string
-  identifier?: string
-}
-
 type Props = {
   item: IDoneJson
   devices?: WebtopoProjectLinkedDeviceVO[]
@@ -127,8 +115,6 @@ const formRef = ref<FormInstance>()
 const formModel = reactive({
   deviceBinds: [] as DeviceBindRow[]
 })
-const devicePropsMap = ref<Record<number, DevicePropOption[]>>({})
-const devicePropsLoading = ref<number[]>([])
 
 const rules: Record<string, FormItemRule | FormItemRule[]> = {
   deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
@@ -174,12 +160,6 @@ async function openDrawer() {
     editing: false
   }))
   drawerVisible.value = true
-
-  await Promise.all(
-    formModel.deviceBinds
-      .filter((item) => item.deviceId)
-      .map((item) => loadDeviceProps(item.deviceId!))
-  )
 }
 
 function addBind() {
@@ -197,38 +177,18 @@ function addBind() {
   })
 }
 
-async function onDeviceChange(bindItem: DeviceBindRow) {
+function onDeviceChange(bindItem: DeviceBindRow) {
   bindItem.deviceProp = ''
-  if (bindItem.deviceId) {
-    await loadDeviceProps(bindItem.deviceId)
-  }
-}
-
-async function loadDeviceProps(deviceId: number) {
-  if (devicePropsMap.value[deviceId] || devicePropsLoading.value.includes(deviceId)) {
-    return
-  }
-
-  devicePropsLoading.value.push(deviceId)
-  try {
-    const data = ((await IotDeviceApi.getIotDeviceTds(deviceId)) || []) as DevicePropResponseItem[]
-    devicePropsMap.value[deviceId] = data
-      .filter((item) => item.identifier)
-      .map((item) => ({
-        label: item.modelName || item.identifier!,
-        value: item.identifier!
-      }))
-  } finally {
-    devicePropsLoading.value = devicePropsLoading.value.filter((id) => id !== deviceId)
-  }
 }
 
 function getDeviceProps(deviceId?: number) {
-  return deviceId ? devicePropsMap.value[deviceId] || [] : []
-}
-
-function isDevicePropsLoading(deviceId?: number) {
-  return !!deviceId && devicePropsLoading.value.includes(deviceId)
+  const device = props.devices.find((item) => item.id === deviceId)
+  return (device?.pointParams || [])
+    .filter((item) => item.paramCode)
+    .map((item) => ({
+      label: item.paramName || item.paramCode!,
+      value: item.paramCode!
+    }))
 }
 
 async function saveBind(index: number) {

+ 89 - 2
src/views/maotu/preview.vue

@@ -3,27 +3,108 @@
 </template>
 <script setup lang="ts">
 import { WebtopoProjectApi, parseWebtopoDataModel } from '@/api/pms/maotu'
+import { IotDeviceApi } from '@/api/pms/device'
 import type { IExportJson } from '@/components/mt-edit/components/types'
 import { MtPreview } from '@/export'
 import { ElMessage } from 'element-plus'
-import { nextTick, onMounted, ref } from 'vue'
+import { nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { useRoute } from 'vue-router'
 
 const route = useRoute()
 const MtPreviewRef = ref<InstanceType<typeof MtPreview>>()
+const previewData = ref<IExportJson>()
+const pollingTimer = ref<number>()
+const polling = ref(false)
 
 const getProjectId = () => {
   const id = Number(route.params.id)
   return Number.isFinite(id) && id > 0 ? id : undefined
 }
 
+type DevicePointData = {
+  identifier?: string
+  value?: unknown
+}
+
 const setPreviewData = async (dataModel: unknown) => {
   if (!dataModel) {
     return
   }
 
+  previewData.value = dataModel as IExportJson
   await nextTick()
-  MtPreviewRef.value?.setImportJson(dataModel as IExportJson)
+  MtPreviewRef.value?.setImportJson(previewData.value)
+}
+
+const getDeviceBindItems = () => {
+  return (previewData.value?.json || []).flatMap((item) =>
+    (item.deviceBinds || [])
+      .filter((bind) => bind.deviceId && bind.deviceProp && bind.nodeProp)
+      .map((bind) => ({
+        itemId: item.id,
+        deviceId: bind.deviceId!,
+        deviceProp: bind.deviceProp!,
+        nodeProp: bind.nodeProp!
+      }))
+  )
+}
+
+const refreshDeviceBindValues = async () => {
+  if (polling.value) {
+    return
+  }
+
+  const bindItems = getDeviceBindItems()
+  const deviceIds = Array.from(new Set(bindItems.map((item) => item.deviceId)))
+  if (!bindItems.length || !deviceIds.length) {
+    return
+  }
+
+  polling.value = true
+  try {
+    const deviceDataList = await Promise.all(
+      deviceIds.map(async (deviceId) => {
+        const data = ((await IotDeviceApi.getIotDeviceTds(deviceId)) || []) as DevicePointData[]
+        return { deviceId, data }
+      })
+    )
+    const valueMap = new Map<string, unknown>()
+    deviceDataList.forEach(({ deviceId, data }) => {
+      data.forEach((item) => {
+        if (item.identifier) {
+          valueMap.set(`${deviceId}:${item.identifier}`, item.value)
+        }
+      })
+    })
+
+    MtPreviewRef.value?.setItemAttrs(
+      bindItems
+        .filter((item) => valueMap.has(`${item.deviceId}:${item.deviceProp}`))
+        .map((item) => ({
+          id: item.itemId,
+          key: item.nodeProp,
+          val: valueMap.get(`${item.deviceId}:${item.deviceProp}`)
+        }))
+    )
+  } finally {
+    polling.value = false
+  }
+}
+
+const startDeviceBindPolling = () => {
+  if (pollingTimer.value) {
+    clearInterval(pollingTimer.value)
+  }
+
+  refreshDeviceBindValues()
+  pollingTimer.value = window.setInterval(refreshDeviceBindValues, 5000)
+}
+
+const stopDeviceBindPolling = () => {
+  if (pollingTimer.value) {
+    clearInterval(pollingTimer.value)
+    pollingTimer.value = undefined
+  }
 }
 
 const loadProject = async () => {
@@ -31,12 +112,14 @@ const loadProject = async () => {
   if (!id) {
     const exportJson = sessionStorage.getItem('exportJson')
     await setPreviewData(parseWebtopoDataModel(exportJson))
+    startDeviceBindPolling()
     return
   }
 
   try {
     const data = await WebtopoProjectApi.getWebtopoProject(id)
     await setPreviewData(data?.dataModel)
+    startDeviceBindPolling()
   } catch {
     // ElMessage.error('拓扑数据加载失败')
   }
@@ -53,4 +136,8 @@ const onEventCallBack = (type: string, item_id: string) => {
 onMounted(() => {
   loadProject()
 })
+
+onUnmounted(() => {
+  stopDeviceBindPolling()
+})
 </script>