Przeglądaj źródła

🐞 fix: 设备监控解析文字

Zimo 17 godzin temu
rodzic
commit
f754dae6b8

+ 38 - 0
src/utils/useSocketBus.ts

@@ -77,3 +77,41 @@ export function useSocketBus(deviceCode: string) {
 
 
   return { ws, status, messages, on, off, sendEvent, open, close, onAny }
   return { ws, status, messages, on, off, sendEvent, open, close, onAny }
 }
 }
+
+export const formatIotValue = (val: any) => {
+  if (val === null || val === undefined) return { value: '0', isText: true }
+  const strVal = String(val)
+
+  if (!/\d/.test(strVal)) {
+    return { value: strVal, isText: true }
+  }
+
+  const match = strVal.match(/^(-?\d+(?:\.\d+)?)(.*)$/)
+
+  if (match) {
+    return {
+      value: match[1],
+      suffix: match[2] || '',
+      isText: false
+    }
+  }
+  return { value: strVal, isText: true }
+}
+
+export interface HeaderItem {
+  label: string
+  key: string
+  judgment?: boolean
+}
+
+export interface Dimensions {
+  identifier: string
+  name: string
+  value: string | number
+  color: string
+  bgHover: string
+  bgActive: string
+  response?: boolean
+  suffix?: string
+  isText?: boolean
+}

+ 90 - 45
src/views/oli-connection/monitoring/detail.vue

@@ -14,7 +14,7 @@ import { neonColors } from '@/utils/td-color'
 import dayjs from 'dayjs'
 import dayjs from 'dayjs'
 import * as echarts from 'echarts'
 import * as echarts from 'echarts'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
-import { useSocketBus } from '@/utils/useSocketBus'
+import { Dimensions, formatIotValue, HeaderItem, useSocketBus } from '@/utils/useSocketBus'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { rangeShortcuts } from '@/utils/formatTime'
 
 
 const { query } = useRoute()
 const { query } = useRoute()
@@ -72,6 +72,49 @@ onAny((msg) => {
   genderIntervalArr()
   genderIntervalArr()
 })
 })
 
 
+// onAny((msg) => {
+//   if (!Array.isArray(msg) || msg.length === 0) return
+
+//   const valueMap = new Map<string, { value: string; isText: boolean; suffix?: string }>()
+
+//   for (const item of msg) {
+//     const { identity, modelName, readTime, logValue } = item
+
+//     const { value, isText, suffix } = formatIotValue(logValue)
+
+//     if (identity) {
+//       valueMap.set(identity, { value, isText, suffix })
+//     }
+
+//     if (modelName && chartData.value[modelName]) {
+//       chartData.value[modelName].push({
+//         ts: dayjs(readTime).valueOf(),
+//         value: Number(value)
+//       })
+
+//       updateSingleSeries(modelName)
+//     }
+//   }
+
+//   const updateDimensions = (list) => {
+//     list.forEach((item) => {
+//       const v = valueMap.get(item.identifier)
+//       if (v !== undefined) {
+//         item.value = v.value
+//         item.suffix = v.suffix
+//         item.isText = v.isText
+//       }
+//     })
+//   }
+
+//   updateDimensions(dimensions.value)
+//   updateDimensions(gatewayDimensions.value)
+//   updateDimensions(carDimensions.value)
+
+//   // 3️⃣ 统一一次调用
+//   genderIntervalArr()
+// })
+
 function hexToRgba(hex: string, alpha: number) {
 function hexToRgba(hex: string, alpha: number) {
   const r = parseInt(hex.slice(1, 3), 16)
   const r = parseInt(hex.slice(1, 3), 16)
   const g = parseInt(hex.slice(3, 5), 16)
   const g = parseInt(hex.slice(3, 5), 16)
@@ -79,12 +122,6 @@ function hexToRgba(hex: string, alpha: number) {
   return `rgba(${r}, ${g}, ${b}, ${alpha})`
   return `rgba(${r}, ${g}, ${b}, ${alpha})`
 }
 }
 
 
-interface HeaderItem {
-  label: string
-  key: keyof typeof data.value
-  judgment?: boolean
-}
-
 const headerCenterContent: HeaderItem[] = [
 const headerCenterContent: HeaderItem[] = [
   { label: '设备名称', key: 'deviceName' },
   { label: '设备名称', key: 'deviceName' },
   { label: '所属部门', key: 'dept' },
   { label: '所属部门', key: 'dept' },
@@ -99,16 +136,6 @@ const headerTagContent: HeaderItem[] = [
   { label: '北斗', key: 'carOnline', judgment: true }
   { label: '北斗', key: 'carOnline', judgment: true }
 ]
 ]
 
 
-interface Dimensions {
-  identifier: string
-  name: string
-  value: string | number
-  color: string
-  bgHover: string
-  bgActive: string
-  response?: boolean
-}
-
 const dimensions = ref<Dimensions[]>([])
 const dimensions = ref<Dimensions[]>([])
 const gatewayDimensions = ref<Dimensions[]>([])
 const gatewayDimensions = ref<Dimensions[]>([])
 const carDimensions = ref<Dimensions[]>([])
 const carDimensions = ref<Dimensions[]>([])
@@ -141,25 +168,36 @@ async function loadDimensions() {
   try {
   try {
     const gateway = (((await IotDeviceApi.getIotDeviceTds(Number(query.id))) as any[]) ?? [])
     const gateway = (((await IotDeviceApi.getIotDeviceTds(Number(query.id))) as any[]) ?? [])
       .sort((a, b) => b.modelOrder - a.modelOrder)
       .sort((a, b) => b.modelOrder - a.modelOrder)
-      .map((item) => ({
-        identifier: item.identifier,
-        name: item.modelName,
-        value: item.value,
-        response: false
-      }))
+      .map((item) => {
+        const { value, suffix, isText } = formatIotValue(item.value)
+        return {
+          identifier: item.identifier,
+          name: item.modelName,
+          value: value,
+          suffix: suffix,
+          isText: isText,
+          response: false
+        }
+      })
 
 
     const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
     const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
       .sort((a, b) => b.modelOrder - a.modelOrder)
       .sort((a, b) => b.modelOrder - a.modelOrder)
-      .map((item) => ({
-        identifier: item.identifier,
-        name: item.modelName,
-        value: item.value,
-        response: false
-      }))
+      .map((item) => {
+        const { value, suffix, isText } = formatIotValue(item.value)
+        console.log(`${item.modelName} :>> `, value)
+        return {
+          identifier: item.identifier,
+          name: item.modelName,
+          value: value,
+          suffix: suffix,
+          isText: isText,
+          response: false
+        }
+      })
 
 
     // 合并并分配霓虹色
     // 合并并分配霓虹色
     dimensions.value = [...gateway, ...car]
     dimensions.value = [...gateway, ...car]
-      .filter((item) => !disabledDimensions.value.includes(item.identifier))
+      // .filter((item) => !disabledDimensions.value.includes(item.identifier))
       .map((item, index) => {
       .map((item, index) => {
         const color = neonColors[index]
         const color = neonColors[index]
 
 
@@ -199,15 +237,14 @@ async function loadDimensions() {
 //       IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))
 //       IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))
 //     ])
 //     ])
 
 
-//     // 2. 创建一个 Map 用于快速查找 (Identifier -> Value)
-//     // 这样可以将复杂度从 O(N*M) 降低到 O(N)
-//     const newValueMap = new Map<string, any>()
+//     const newValueMap = new Map<string, { value: string; suffix?: string; isText?: boolean }>()
 
 
 //     const addToMap = (data: any[]) => {
 //     const addToMap = (data: any[]) => {
 //       if (!data) return
 //       if (!data) return
 //       data.forEach((item) => {
 //       data.forEach((item) => {
 //         if (item.identifier) {
 //         if (item.identifier) {
-//           newValueMap.set(item.identifier, item.value)
+//           const { value, suffix, isText } = formatIotValue(item.value)
+//           newValueMap.set(item.identifier, { value, suffix, isText })
 //         }
 //         }
 //       })
 //       })
 //     }
 //     }
@@ -215,28 +252,30 @@ async function loadDimensions() {
 //     addToMap(gatewayRes as any[])
 //     addToMap(gatewayRes as any[])
 //     addToMap(carRes as any[])
 //     addToMap(carRes as any[])
 
 
-//     // 3. 更新 dimensions.value (保留了之前的 color 和其他属性)
 //     dimensions.value.forEach((item) => {
 //     dimensions.value.forEach((item) => {
 //       if (newValueMap.has(item.identifier)) {
 //       if (newValueMap.has(item.identifier)) {
-//         item.value = newValueMap.get(item.identifier)
+//         const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+//         item.value = value ?? ''
+//         item.suffix = suffix
+//         item.isText = isText
 //       }
 //       }
 //     })
 //     })
 
 
-//     // 4. 如果还需要同步更新 gatewayDimensions 和 carDimensions
-//     // (假设这些是引用类型,如果它们引用的是同一个对象,上面更新 dimensions 时可能已经同步了。
-//     // 如果它们是独立的对象数组,则需要显式更新)
-
-//     // 更新 Gateway 原始列表
 //     gatewayDimensions.value.forEach((item) => {
 //     gatewayDimensions.value.forEach((item) => {
 //       if (newValueMap.has(item.identifier)) {
 //       if (newValueMap.has(item.identifier)) {
-//         item.value = newValueMap.get(item.identifier)
+//         const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+//         item.value = value ?? ''
+//         item.suffix = suffix
+//         item.isText = isText
 //       }
 //       }
 //     })
 //     })
 
 
-//     // 更新 Car 原始列表
 //     carDimensions.value.forEach((item) => {
 //     carDimensions.value.forEach((item) => {
 //       if (newValueMap.has(item.identifier)) {
 //       if (newValueMap.has(item.identifier)) {
-//         item.value = newValueMap.get(item.identifier)
+//         const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+//         item.value = value ?? ''
+//         item.suffix = suffix
+//         item.isText = isText
 //       }
 //       }
 //     })
 //     })
 //   } catch (error) {
 //   } catch (error) {
@@ -730,10 +769,11 @@ onUnmounted(() => {
 
 
           <div class="px-3 pb-4 pt-2 space-y-3">
           <div class="px-3 pb-4 pt-2 space-y-3">
             <div
             <div
+              :data-disabled="disabledDimensions.includes(item.identifier)"
               v-for="item in citem.value"
               v-for="item in citem.value"
               :key="item.identifier"
               :key="item.identifier"
               @click="handleClickSpec(item.name)"
               @click="handleClickSpec(item.name)"
-              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none"
+              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
               :class="{ 'is-active': selectedDimension[item.name] }"
               :class="{ 'is-active': selectedDimension[item.name] }"
               :style="{
               :style="{
                 '--theme-color': item.color,
                 '--theme-color': item.color,
@@ -757,10 +797,15 @@ onUnmounted(() => {
 
 
               <div class="flex items-baseline justify-between relative z-10">
               <div class="flex items-baseline justify-between relative z-10">
                 <animated-count-to
                 <animated-count-to
+                  v-if="!item.isText"
                   :value="Number(item.value)"
                   :value="Number(item.value)"
                   :duration="500"
                   :duration="500"
+                  :suffix="item.suffix"
                   class="text-lg font-bold font-mono tracking-tight text-slate-800"
                   class="text-lg font-bold font-mono tracking-tight text-slate-800"
                 />
                 />
+                <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
+                  {{ item.value }}
+                </span>
               </div>
               </div>
               <div
               <div
                 class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
                 class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"

+ 47 - 44
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -15,6 +15,7 @@ import dayjs from 'dayjs'
 import * as echarts from 'echarts'
 import * as echarts from 'echarts'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { rangeShortcuts } from '@/utils/formatTime'
+import { Dimensions, formatIotValue, HeaderItem } from '@/utils/useSocketBus'
 
 
 const { query } = useRoute()
 const { query } = useRoute()
 
 
@@ -35,12 +36,6 @@ function hexToRgba(hex: string, alpha: number) {
   return `rgba(${r}, ${g}, ${b}, ${alpha})`
   return `rgba(${r}, ${g}, ${b}, ${alpha})`
 }
 }
 
 
-interface HeaderItem {
-  label: string
-  key: keyof typeof data.value
-  judgment?: boolean
-}
-
 const headerCenterContent: HeaderItem[] = [
 const headerCenterContent: HeaderItem[] = [
   { label: '设备名称', key: 'deviceName' },
   { label: '设备名称', key: 'deviceName' },
   { label: '所属部门', key: 'dept' },
   { label: '所属部门', key: 'dept' },
@@ -55,16 +50,6 @@ const headerTagContent: HeaderItem[] = [
   { label: '北斗', key: 'carOnline', judgment: true }
   { label: '北斗', key: 'carOnline', judgment: true }
 ]
 ]
 
 
-interface Dimensions {
-  identifier: string
-  name: string
-  value: string | number
-  color: string
-  bgHover: string
-  bgActive: string
-  response?: boolean
-}
-
 const dimensions = ref<Dimensions[]>([])
 const dimensions = ref<Dimensions[]>([])
 const gatewayDimensions = ref<Dimensions[]>([])
 const gatewayDimensions = ref<Dimensions[]>([])
 const carDimensions = ref<Dimensions[]>([])
 const carDimensions = ref<Dimensions[]>([])
@@ -97,25 +82,36 @@ async function loadDimensions() {
   try {
   try {
     const gateway = (((await IotDeviceApi.getIotDeviceTds(Number(query.id))) as any[]) ?? [])
     const gateway = (((await IotDeviceApi.getIotDeviceTds(Number(query.id))) as any[]) ?? [])
       .sort((a, b) => b.modelOrder - a.modelOrder)
       .sort((a, b) => b.modelOrder - a.modelOrder)
-      .map((item) => ({
-        identifier: item.identifier,
-        name: item.modelName,
-        value: item.value,
-        response: false
-      }))
+      .map((item) => {
+        const { value, suffix, isText } = formatIotValue(item.value)
+        return {
+          identifier: item.identifier,
+          name: item.modelName,
+          value: value,
+          suffix: suffix,
+          isText: isText,
+          response: false
+        }
+      })
 
 
     const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
     const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
       .sort((a, b) => b.modelOrder - a.modelOrder)
       .sort((a, b) => b.modelOrder - a.modelOrder)
-      .map((item) => ({
-        identifier: item.identifier,
-        name: item.modelName,
-        value: item.value,
-        response: false
-      }))
+      .map((item) => {
+        const { value, suffix, isText } = formatIotValue(item.value)
+        console.log(`${item.modelName} :>> `, value)
+        return {
+          identifier: item.identifier,
+          name: item.modelName,
+          value: value,
+          suffix: suffix,
+          isText: isText,
+          response: false
+        }
+      })
 
 
     // 合并并分配霓虹色
     // 合并并分配霓虹色
     dimensions.value = [...gateway, ...car]
     dimensions.value = [...gateway, ...car]
-      .filter((item) => !disabledDimensions.value.includes(item.identifier))
+      // .filter((item) => !disabledDimensions.value.includes(item.identifier))
       .map((item, index) => {
       .map((item, index) => {
         const color = neonColors[index]
         const color = neonColors[index]
 
 
@@ -155,15 +151,14 @@ async function updateDimensionValues() {
       IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))
       IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))
     ])
     ])
 
 
-    // 2. 创建一个 Map 用于快速查找 (Identifier -> Value)
-    // 这样可以将复杂度从 O(N*M) 降低到 O(N)
-    const newValueMap = new Map<string, any>()
+    const newValueMap = new Map<string, { value: string; suffix?: string; isText?: boolean }>()
 
 
     const addToMap = (data: any[]) => {
     const addToMap = (data: any[]) => {
       if (!data) return
       if (!data) return
       data.forEach((item) => {
       data.forEach((item) => {
         if (item.identifier) {
         if (item.identifier) {
-          newValueMap.set(item.identifier, item.value)
+          const { value, suffix, isText } = formatIotValue(item.value)
+          newValueMap.set(item.identifier, { value, suffix, isText })
         }
         }
       })
       })
     }
     }
@@ -171,28 +166,30 @@ async function updateDimensionValues() {
     addToMap(gatewayRes as any[])
     addToMap(gatewayRes as any[])
     addToMap(carRes as any[])
     addToMap(carRes as any[])
 
 
-    // 3. 更新 dimensions.value (保留了之前的 color 和其他属性)
     dimensions.value.forEach((item) => {
     dimensions.value.forEach((item) => {
       if (newValueMap.has(item.identifier)) {
       if (newValueMap.has(item.identifier)) {
-        item.value = newValueMap.get(item.identifier)
+        const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+        item.value = value ?? ''
+        item.suffix = suffix
+        item.isText = isText
       }
       }
     })
     })
 
 
-    // 4. 如果还需要同步更新 gatewayDimensions 和 carDimensions
-    // (假设这些是引用类型,如果它们引用的是同一个对象,上面更新 dimensions 时可能已经同步了。
-    // 如果它们是独立的对象数组,则需要显式更新)
-
-    // 更新 Gateway 原始列表
     gatewayDimensions.value.forEach((item) => {
     gatewayDimensions.value.forEach((item) => {
       if (newValueMap.has(item.identifier)) {
       if (newValueMap.has(item.identifier)) {
-        item.value = newValueMap.get(item.identifier)
+        const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+        item.value = value ?? ''
+        item.suffix = suffix
+        item.isText = isText
       }
       }
     })
     })
 
 
-    // 更新 Car 原始列表
     carDimensions.value.forEach((item) => {
     carDimensions.value.forEach((item) => {
       if (newValueMap.has(item.identifier)) {
       if (newValueMap.has(item.identifier)) {
-        item.value = newValueMap.get(item.identifier)
+        const { value, suffix, isText } = newValueMap.get(item.identifier) ?? {}
+        item.value = value ?? ''
+        item.suffix = suffix
+        item.isText = isText
       }
       }
     })
     })
   } catch (error) {
   } catch (error) {
@@ -676,10 +673,11 @@ onUnmounted(() => {
 
 
           <div class="px-3 pb-4 pt-2 space-y-3">
           <div class="px-3 pb-4 pt-2 space-y-3">
             <div
             <div
+              :data-disabled="disabledDimensions.includes(item.identifier)"
               v-for="item in citem.value"
               v-for="item in citem.value"
               :key="item.identifier"
               :key="item.identifier"
               @click="handleClickSpec(item.name)"
               @click="handleClickSpec(item.name)"
-              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none"
+              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
               :class="{ 'is-active': selectedDimension[item.name] }"
               :class="{ 'is-active': selectedDimension[item.name] }"
               :style="{
               :style="{
                 '--theme-color': item.color,
                 '--theme-color': item.color,
@@ -703,10 +701,15 @@ onUnmounted(() => {
 
 
               <div class="flex items-baseline justify-between relative z-10">
               <div class="flex items-baseline justify-between relative z-10">
                 <animated-count-to
                 <animated-count-to
+                  v-if="!item.isText"
                   :value="Number(item.value)"
                   :value="Number(item.value)"
                   :duration="500"
                   :duration="500"
+                  :suffix="item.suffix"
                   class="text-lg font-bold font-mono tracking-tight text-slate-800"
                   class="text-lg font-bold font-mono tracking-tight text-slate-800"
                 />
                 />
+                <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
+                  {{ item.value }}
+                </span>
               </div>
               </div>
               <div
               <div
                 class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
                 class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"