|
|
@@ -14,7 +14,7 @@ import { neonColors } from '@/utils/td-color'
|
|
|
import dayjs from 'dayjs'
|
|
|
import * as echarts from 'echarts'
|
|
|
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'
|
|
|
|
|
|
const { query } = useRoute()
|
|
|
@@ -72,6 +72,49 @@ onAny((msg) => {
|
|
|
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) {
|
|
|
const r = parseInt(hex.slice(1, 3), 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})`
|
|
|
}
|
|
|
|
|
|
-interface HeaderItem {
|
|
|
- label: string
|
|
|
- key: keyof typeof data.value
|
|
|
- judgment?: boolean
|
|
|
-}
|
|
|
-
|
|
|
const headerCenterContent: HeaderItem[] = [
|
|
|
{ label: '设备名称', key: 'deviceName' },
|
|
|
{ label: '所属部门', key: 'dept' },
|
|
|
@@ -99,16 +136,6 @@ const headerTagContent: HeaderItem[] = [
|
|
|
{ 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 gatewayDimensions = ref<Dimensions[]>([])
|
|
|
const carDimensions = ref<Dimensions[]>([])
|
|
|
@@ -141,36 +168,55 @@ async function loadDimensions() {
|
|
|
try {
|
|
|
const gateway = (((await IotDeviceApi.getIotDeviceTds(Number(query.id))) as any[]) ?? [])
|
|
|
.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[]) ?? [])
|
|
|
.sort((a, b) => b.modelOrder - a.modelOrder)
|
|
|
- .map((item) => ({
|
|
|
- identifier: item.identifier,
|
|
|
- name: item.modelName,
|
|
|
- value: item.value,
|
|
|
- response: false
|
|
|
- }))
|
|
|
-
|
|
|
- // 合并并分配霓虹色
|
|
|
- dimensions.value = [...gateway, ...car]
|
|
|
- .filter((item) => !disabledDimensions.value.includes(item.identifier))
|
|
|
- .map((item, index) => {
|
|
|
- const color = neonColors[index]
|
|
|
-
|
|
|
+ .map((item) => {
|
|
|
+ const { value, suffix, isText } = formatIotValue(item.value)
|
|
|
+ console.log(`${item.modelName} :>> `, value)
|
|
|
return {
|
|
|
- ...item,
|
|
|
- color: color,
|
|
|
- bgHover: hexToRgba(color, 0.08),
|
|
|
- bgActive: hexToRgba(color, 0.12)
|
|
|
+ identifier: item.identifier,
|
|
|
+ name: item.modelName,
|
|
|
+ value: value,
|
|
|
+ suffix: suffix,
|
|
|
+ isText: isText,
|
|
|
+ response: false
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ const rawList = [...gateway, ...car]
|
|
|
+
|
|
|
+ const uniqueMap = new Map()
|
|
|
+
|
|
|
+ rawList.forEach((item) => {
|
|
|
+ const uniqueKey = `${item.identifier}|${item.name}`
|
|
|
+
|
|
|
+ // if (!uniqueMap.has(uniqueKey)) {
|
|
|
+ uniqueMap.set(uniqueKey, item)
|
|
|
+ // }
|
|
|
+ })
|
|
|
+
|
|
|
+ dimensions.value = Array.from(uniqueMap.values()).map((item, index) => {
|
|
|
+ const color = neonColors[index % neonColors.length]
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ color: color,
|
|
|
+ bgHover: hexToRgba(color, 0.08),
|
|
|
+ bgActive: hexToRgba(color, 0.12)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
gatewayDimensions.value = dimensions.value.filter((d) =>
|
|
|
gateway.some((g) => g.identifier === d.identifier)
|
|
|
)
|
|
|
@@ -199,15 +245,14 @@ async function loadDimensions() {
|
|
|
// 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[]) => {
|
|
|
// if (!data) return
|
|
|
// data.forEach((item) => {
|
|
|
// 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 +260,30 @@ async function loadDimensions() {
|
|
|
// addToMap(gatewayRes as any[])
|
|
|
// addToMap(carRes as any[])
|
|
|
|
|
|
-// // 3. 更新 dimensions.value (保留了之前的 color 和其他属性)
|
|
|
// dimensions.value.forEach((item) => {
|
|
|
// 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) => {
|
|
|
// 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) => {
|
|
|
// 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) {
|
|
|
@@ -730,10 +777,11 @@ onUnmounted(() => {
|
|
|
|
|
|
<div class="px-3 pb-4 pt-2 space-y-3">
|
|
|
<div
|
|
|
+ :data-disabled="disabledDimensions.includes(item.identifier)"
|
|
|
v-for="item in citem.value"
|
|
|
:key="item.identifier"
|
|
|
@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] }"
|
|
|
:style="{
|
|
|
'--theme-color': item.color,
|
|
|
@@ -757,10 +805,15 @@ onUnmounted(() => {
|
|
|
|
|
|
<div class="flex items-baseline justify-between relative z-10">
|
|
|
<animated-count-to
|
|
|
+ v-if="!item.isText"
|
|
|
:value="Number(item.value)"
|
|
|
:duration="500"
|
|
|
+ :suffix="item.suffix"
|
|
|
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
|
|
|
class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
|