Zimo 14 часов назад
Родитель
Сommit
a538f575bb

+ 6 - 0
src/api/pms/device/index.ts

@@ -50,6 +50,12 @@ export interface IotDeviceVO {
 
 // 设备台账 API
 export const IotDeviceApi = {
+  saveMaxMin: async (data: any) => {
+    return await request.post({ url: `rq/iot-alarm-setting/save`, data })
+  },
+  deleteMaxMin: async (params: any) => {
+    return await request.delete({ url: `rq/iot-alarm-setting/delete`, params })
+  },
   getCompany: async (params: any) => {
     return await request.get({ url: `/rq/iot-device/company?id=` + params })
   },

+ 3 - 0
src/utils/useSocketBus.ts

@@ -114,4 +114,7 @@ export interface Dimensions {
   response?: boolean
   suffix?: string
   isText?: boolean
+  minValue?: number
+  maxValue?: number
+  id?: number
 }

+ 51 - 5
src/views/oli-connection/monitoring-board/chart.vue

@@ -24,6 +24,26 @@ const props = defineProps({
     type: String,
     required: true
   },
+  ifInline: {
+    type: String,
+    required: true
+  },
+  lastInlineTime: {
+    type: String,
+    required: true
+  },
+  deptName: {
+    type: String,
+    required: true
+  },
+  vehicleName: {
+    type: String,
+    required: true
+  },
+  carOnline: {
+    type: String,
+    required: true
+  },
   // isRealTime: {
   //   type: Boolean,
   //   default: true
@@ -551,7 +571,7 @@ async function initLoadChartData(real_time: boolean = true) {
 
       lastTsMap.value[name] = sorted.at(-1)?.ts ?? 0
 
-      genderIntervalArr(false)
+      genderIntervalArr()
 
       updateSingleSeries(name)
 
@@ -562,7 +582,7 @@ async function initLoadChartData(real_time: boolean = true) {
   }
 
   if (real_time) {
-    connect('ws://172.21.10.65:8083/mqtt', {}, handleMessageUpdate)
+    connect(`${import.meta.env.VITE_BASE_URL}/mqtt`, {}, handleMessageUpdate)
   }
 }
 
@@ -605,12 +625,38 @@ onUnmounted(() => {
     if (chart) chart.resize()
   })
 })
+
+const router = useRouter()
+
+function handleDetailClick() {
+  router.push({
+    name: 'MonitoringDetail',
+    query: {
+      id: props.id,
+      ifInline: props.ifInline,
+      carOnline: props.carOnline,
+      time: props.lastInlineTime,
+      name: props.deviceName,
+      code: props.deviceCode,
+      dept: props.deptName,
+      vehicle: props.vehicleName
+    }
+  })
+}
 </script>
 <template>
   <div class="h-100 rounded-lg chart-container flex flex-col">
-    <header class="chart-header">
-      <div class="title-icon"></div>
-      <div>{{ `${props.deviceCode}-${props.deviceName}` }}</div>
+    <header class="chart-header justify-between">
+      <div class="flex items-center">
+        <div class="title-icon"></div>
+        <div>{{ `${props.deviceCode}-${props.deviceName}` }}</div>
+      </div>
+      <el-button link type="primary" class="group" @click="handleDetailClick">
+        详情
+        <div
+          class="i-material-symbols:arrow-right-alt-rounded size-4 transition-transform group-hover:translate-x-1"
+        ></div>
+      </el-button>
     </header>
     <main
       class="flex-1 chart-main"

+ 11 - 1
src/views/oli-connection/monitoring-board/index.vue

@@ -16,8 +16,13 @@ const userDeptId = userStore.getUser.deptId
 
 interface DeviceData {
   id: number
+  ifInline: string
+  lastInlineTime: string
   deviceCode: string
   deviceName: string
+  deptName: string
+  vehicleName: string
+  carOnline: string
   mqttUrl: string
 }
 
@@ -126,6 +131,11 @@ async function handleDeviceChange(selectedIds: number[]) {
     if (option) {
       deviceList.value.push({
         id: id,
+        ifInline: option.raw.ifInline,
+        lastInlineTime: option.raw.lastInlineTime,
+        deptName: option.raw.deptName,
+        vehicleName: option.raw.vehicleName,
+        carOnline: option.raw.carOnline ?? '',
         deviceCode: option.raw.deviceCode,
         deviceName: option.raw.deviceName,
         mqttUrl: option.raw.mqttUrl
@@ -282,7 +292,7 @@ function handleRest() {
       </el-form>
     </div>
 
-    <div class="p-4 grid grid-cols-3 gap-6">
+    <div class="p-4 grid grid-cols-2 3xl:grid-cols-3 gap-6">
       <template v-for="item in deviceList" :key="item.id">
         <chart v-bind="item" :date="query.time" />
       </template>

+ 155 - 27
src/views/oli-connection/monitoring/detail.vue

@@ -8,7 +8,8 @@ import {
   CircleCloseFilled,
   DataLine,
   Crop,
-  FullScreen
+  FullScreen,
+  Setting
 } from '@element-plus/icons-vue'
 import { AnimatedCountTo } from '@/components/AnimatedCountTo'
 import { neonColors } from '@/utils/td-color'
@@ -19,6 +20,7 @@ import { Dimensions, formatIotValue, HeaderItem, useSocketBus } from '@/utils/us
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useFullscreen } from '@vueuse/core'
 import { snapdom } from '@zumer/snapdom'
+import { ElMessage } from 'element-plus'
 
 const { query } = useRoute()
 
@@ -180,26 +182,30 @@ async function loadDimensions() {
           value: value,
           suffix: suffix,
           isText: isText,
-          response: false
+          response: false,
+          id: item.alarmSettingId,
+          maxValue: Number(item.maxValue),
+          minValue: Number(item.minValue)
         }
       })
 
-    const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
-      .sort((a, b) => b.modelOrder - a.modelOrder)
-      .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
-        }
-      })
-
-    const rawList = [...gateway, ...car]
+    // const car = (((await IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))) as any[]) ?? [])
+    //   .sort((a, b) => b.modelOrder - a.modelOrder)
+    //   .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
+    //     }
+    //   })
+
+    // const rawList = [...gateway, ...car]
+    const rawList = [...gateway]
 
     const uniqueMap = new Map()
 
@@ -224,9 +230,9 @@ async function loadDimensions() {
     gatewayDimensions.value = dimensions.value.filter((d) =>
       gateway.some((g) => g.identifier === d.identifier)
     )
-    carDimensions.value = dimensions.value.filter((d) =>
-      car.some((c) => c.identifier === d.identifier)
-    )
+    // carDimensions.value = dimensions.value.filter((d) =>
+    //   car.some((c) => c.identifier === d.identifier)
+    // )
 
     selectedDimension.value = Object.fromEntries(dimensions.value.map((item) => [item.name, false]))
     if (dimensions.value.length > 0) {
@@ -592,7 +598,7 @@ async function initLoadChartData(real_time: boolean = true) {
 
       lastTsMap.value[name] = sorted.at(-1)?.ts ?? 0
 
-      genderIntervalArr(true)
+      genderIntervalArr()
 
       updateSingleSeries(name)
 
@@ -710,6 +716,29 @@ onUnmounted(() => {
 const targetArea = ref(null)
 
 const { toggle, isFullscreen } = useFullscreen(targetArea)
+
+async function handleSave(item: Dimensions) {
+  const body = {
+    minValue: item.minValue,
+    maxValue: item.maxValue,
+    deviceId: query.id,
+    propertyCode: item.identifier,
+    alarmProperty: item.name,
+    deviceName: data.value.deviceName,
+    id: item.id
+  }
+
+  const res = await IotDeviceApi.saveMaxMin(body)
+
+  if (res.id) item.id = res.id
+}
+async function handleReset(item: Dimensions) {
+  item.minValue = undefined
+  item.maxValue = undefined
+
+  await IotDeviceApi.deleteMaxMin({ id: item.id })
+  item.id = undefined
+}
 </script>
 
 <template>
@@ -755,7 +784,7 @@ const { toggle, isFullscreen } = useFullscreen(targetArea)
         </template>
       </div>
     </div>
-    <div ref="targetArea" class="h-full min-h-0 relative">
+    <div ref="targetArea" class="relative">
       <div class="flex flex-col gap-4 h-full">
         <template v-for="citem in dimensionsContent" :key="citem.label">
           <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
@@ -797,14 +826,63 @@ const { toggle, isFullscreen } = useFullscreen(targetArea)
                     >
                       {{ item.name }}
                     </span>
-                    <div
+                    <el-popover placement="bottom" :width="280" trigger="click">
+                      <template #reference>
+                        <el-button class="group" link>
+                          <el-icon
+                            class="transition-transform duration-500 group-hover:rotate-180"
+                            :size="16"
+                          >
+                            <Setting />
+                          </el-icon>
+                        </el-button>
+                      </template>
+
+                      <div class="flex flex-col gap-3">
+                        <div class="text-sm font-bold text-gray-700 pb-1 border-b border-gray-100">
+                          设置范围
+                        </div>
+
+                        <div class="grid grid-cols-[auto_1fr] gap-y-3 gap-x-2 items-center">
+                          <span class="text-xs text-gray-500 text-right">最小值:</span>
+                          <el-input-number
+                            v-model="item.minValue"
+                            size="default"
+                            class="!w-full"
+                            placeholder="Min"
+                            :controls="false"
+                            align="left"
+                          />
+
+                          <span class="text-xs text-gray-500 text-right">最大值:</span>
+                          <el-input-number
+                            v-model="item.maxValue"
+                            size="default"
+                            class="!w-full"
+                            placeholder="Max"
+                            :controls="false"
+                            align="left"
+                          />
+                        </div>
+
+                        <div class="flex justify-end gap-2 pt-1">
+                          <el-button size="small" text bg @click="handleReset(item)">
+                            重置
+                          </el-button>
+                          <el-button size="small" type="primary" @click="handleSave(item)">
+                            保存
+                          </el-button>
+                        </div>
+                      </div>
+                    </el-popover>
+                    <!-- <div
                       class="size-2 rounded-full transition-all duration-300 shadow-sm"
                       :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
                       :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
-                    ></div>
+                    ></div> -->
                   </div>
 
-                  <div class="flex items-baseline justify-between relative z-9">
+                  <!-- <div class="flex items-baseline justify-between relative z-9">
                     <animated-count-to
                       v-if="!item.isText"
                       :value="Number(item.value)"
@@ -815,6 +893,56 @@ const { toggle, isFullscreen } = useFullscreen(targetArea)
                     <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
                       {{ item.value }}
                     </span>
+                  </div> -->
+
+                  <div class="flex items-center justify-between relative z-9 mt-1">
+                    <div class="flex-1 mr-2">
+                      <animated-count-to
+                        v-if="!item.isText"
+                        :value="Number(item.value)"
+                        :duration="500"
+                        :suffix="item.suffix"
+                        class="text-2xl font-black font-mono tracking-tight text-slate-800 leading-none"
+                      />
+                      <span
+                        v-else
+                        class="text-2xl font-black font-mono tracking-tight text-slate-800 leading-none"
+                      >
+                        {{ item.value }}
+                      </span>
+                    </div>
+
+                    <div v-if="item.minValue || item.maxValue" class="flex gap-1.5 items-center">
+                      <div
+                        v-if="item.maxValue"
+                        class="flex items-center px-2 py-1 rounded-md bg-emerald-50/80 border border-solid border-emerald-100/80 shadow-sm transition-all duration-300 hover:bg-emerald-100 hover:border-emerald-200"
+                      >
+                        <div
+                          class="flex items-center justify-center w-4 h-4 mr-1 rounded-full bg-emerald-100 text-emerald-600 group-hover/max:bg-white group-hover/max:scale-110 transition-all"
+                        >
+                          <div class="i-material-symbols:arrow-upward-alt-rounded"></div>
+                        </div>
+                        <span class="text-[10px] font-bold text-emerald-400/80 mr-1.5">MAX</span>
+                        <span class="text-sm font-bold font-mono text-emerald-700">{{
+                          item.maxValue
+                        }}</span>
+                      </div>
+
+                      <div
+                        v-if="item.minValue"
+                        class="flex items-center px-2 py-0.5 rounded-md bg-rose-50/80 border border-solid border-rose-100/80 shadow-sm transition-all duration-300 hover:bg-rose-100 hover:border-rose-200"
+                      >
+                        <div
+                          class="flex items-center justify-center w-4 h-4 mr-1 rounded-full bg-rose-100 text-rose-600 group-hover/min:bg-white group-hover/min:scale-110 transition-all"
+                        >
+                          <div class="i-material-symbols:arrow-downward-alt-rounded"></div>
+                        </div>
+                        <span class="text-[10px] font-bold text-rose-400/80 mr-1.5">MIN</span>
+                        <span class="text-sm font-bold font-mono text-rose-700">{{
+                          item.minValue
+                        }}</span>
+                      </div>
+                    </div>
                   </div>
                   <div
                     class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
@@ -832,7 +960,7 @@ const { toggle, isFullscreen } = useFullscreen(targetArea)
           </template>
         </template>
         <div
-          class="flex-1 rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
+          class="flex-1 min-h-200 rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
         >
           <header class="flex items-center justify-between mb-4">
             <h3 class="flex items-center gap-2">

+ 2 - 1
uno.config.ts

@@ -13,7 +13,8 @@ export default defineConfig({
       md: '768px',
       lg: '1024px',
       xl: '1280px',
-      '2xl': '1920px'
+      '2xl': '1920px',
+      '3xl': '2000px'
     }
   },
   // ...UnoCSS options