Browse Source

Merge dev into prod (keep prod config files)

liujia 1 month ago
parent
commit
5e69a05547

+ 2 - 2
.env.test

@@ -9,8 +9,8 @@ VITE_APP_HOST=https://radar-power.cn
 VITE_API_BASE_URL=/portal-service-server/web 
 # mqtt配置
 VITE_MQTT_HOST=wss://radar-power.cn:8084/mqtt
-VITE_MQTT_HOST_ALARM=wss://radar-power.cn:8084/mqtt
-VITE_MQTT_HOST_POINT=wss://radar-power.cn:8084/mqtt
+VITE_MQTT_HOST_ALARM=wss://cmd.radar-power.cn/mqtt/
+VITE_MQTT_HOST_POINT=wss://data.radar-power.cn/mqtt/
 # VITE_MQTT_HOST_ALARM=ws://cmd.radar-power.cn:8084/mqtt # 告警MQTT地址
 # VITE_MQTT_HOST_POINT=ws://data.radar-power.cn:8084/mqtt # 点位数据MQTT地址
 VITE_MQTT_USERNAME=admin

+ 12 - 0
CHANGELOG.md

@@ -1,4 +1,16 @@
 
+## v0.5.13 (2025-09-09)
+- feat: 添加告警计划的检测区域校验,删除冗余注释代码; (8581918)
+- feat: 隐藏注释 (df5d7fe)
+- feat: 设备详情雷达展示过滤; (1febd49)
+
+## v0.5.12 (2025-09-09)
+- feat: 告警计划检测区域坐标调整; (32e9494)
+- fix: 使用随机字符串作为MQTT客户端ID避免冲突 (96b70fa)
+
+## v0.5.11 (2025-09-08)
+- feat: 联调测试环境MQTT新地址; (16faf5e)
+
 ## v0.5.10 (2025-09-08)
 - feat: 设备详情的告警计划添加应用指定模板功能; (2e172d8)
 - fix: 调整解绑设备时的设备id的参数类型为string (1d98dbc)

+ 1 - 0
components.d.ts

@@ -57,6 +57,7 @@ declare module 'vue' {
     ATooltip: typeof import('ant-design-vue/es')['Tooltip']
     ATree: typeof import('ant-design-vue/es')['Tree']
     AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
+    BaseAreaViewer: typeof import('./src/components/baseAreaViewer/index.vue')['default']
     BaseCard: typeof import('./src/components/baseCard/index.vue')['default']
     BaseModal: typeof import('./src/components/baseModal/index.vue')['default']
     BasePagination: typeof import('./src/components/basePagination/index.vue')['default']

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ln-web",
-  "version": "0.5.10",
+  "version": "0.5.13",
   "private": true,
   "type": "module",
   "scripts": {

+ 111 - 0
src/components/baseAreaViewer/index.vue

@@ -0,0 +1,111 @@
+<template>
+  <div
+    class="radarArea"
+    :style="{
+      width: `${width}px`,
+      height: `${length}px`,
+    }"
+  >
+    <furniture-icon
+      v-for="(item, index) in localFurnitureItems"
+      :key="`furniture-${index}-${item.type}`"
+      :icon="item.type"
+      :width="item.width"
+      :height="item.length"
+      :style="{
+        left: `${item.left}px`,
+        top: `${item.top}px`,
+        position: 'absolute',
+        rotate: `${item.rotate}deg`,
+        cursor: 'default',
+        pointerEvents: 'none',
+      }"
+      :draggable="false"
+    />
+    <slot></slot>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { getOriginPosition } from '@/utils'
+
+defineOptions({
+  name: 'BaseAreaViewer',
+})
+
+/**
+ * 组件Props定义(明确类型约束,提高可维护性)
+ */
+interface FurnitureItem {
+  name: string
+  type: string
+  width: number
+  length: number
+  left: number
+  top: number
+  rotate: number
+  x?: number
+  y?: number
+}
+
+const props = withDefaults(
+  defineProps<{
+    width: number
+    length: number
+    furnitureItems: FurnitureItem[]
+    ranges?: number[]
+  }>(),
+  {
+    furnitureItems: () => [],
+  }
+)
+
+const { originOffsetX, originOffsetY, radarX, radarY, radarWidth, radarHeight } = getOriginPosition(
+  props?.ranges ?? [],
+  [0, 0]
+)
+
+const localFurnitureItems = ref<FurnitureItem[]>([
+  {
+    name: '雷达',
+    type: 'radar',
+    width: radarWidth,
+    length: radarHeight,
+    top: radarY,
+    left: radarX,
+    x: originOffsetX,
+    y: originOffsetY,
+    rotate: 0,
+  },
+  ...props.furnitureItems,
+])
+</script>
+
+<style scoped lang="less">
+.radarArea {
+  position: relative;
+  background-image:
+    linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
+    linear-gradient(to right, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
+  background-size: 20px 20px;
+  border: 1px solid rgba(0, 0, 0, 0.8);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+  flex-shrink: 0;
+}
+
+/* 目标点样式(从原代码提取并优化) */
+.target-dot {
+  &__number {
+    color: #fff;
+    font-size: 12px;
+    font-weight: 600;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    pointer-events: none;
+  }
+}
+</style>

+ 3 - 3
src/layout/index.vue

@@ -128,7 +128,7 @@ const initMqttManager = async () => {
       host: import.meta.env.VITE_MQTT_HOST_ALARM,
       username: import.meta.env.VITE_MQTT_USERNAME,
       password: import.meta.env.VITE_MQTT_PASSWORD,
-      clientId: `mqtt_client_${Math.random().toString(16).slice(2, 8)}`,
+      clientId: `web_mqtt_cmd${Math.random().toString(16).slice(2)}`,
     }
     mqttClient = mqtt.connect(mqttConfig.host, {
       clientId: mqttConfig.clientId,
@@ -159,14 +159,14 @@ const initMqttManager = async () => {
         else console.log('✅ 已发送参数:', messageData)
       })
       // 订阅所有主题
-      mqttClient?.subscribe(`/mps/client/connect/`, (err) => {
+      mqttClient?.subscribe(`/mps/client/connect/`, { qos: 2 }, (err) => {
         if (err) {
           console.error('MQTT订阅失败', err)
         } else {
           console.log(`🔥已订阅主题 /mps/client/connect`)
         }
       })
-      mqttClient?.subscribe(`/mps/wb_${userStore.userInfo?.userId}/notice`, (err) => {
+      mqttClient?.subscribe(`/mps/wb_${userStore.userInfo?.userId}/notice`, { qos: 2 }, (err) => {
         if (err) {
           console.error('MQTT订阅失败', err)
         } else {

+ 127 - 78
src/views/device/detail/components/alarmPlanModal/index.vue

@@ -247,49 +247,45 @@
           name="region"
           style="user-select: none"
         >
-          <span style="user-select: none">框选区域 {{ formState.region }}</span>
+          <div>雷达检测区域:{{ props.area!.ranges }}</div>
+          <div style="user-select: none">
+            <span>告警框选区域: {{ formState.region }}</span>
+            <a-button type="link" style="margin-left: 16px" @click="resetBlocks"> 重置 </a-button>
+          </div>
           <a-form-item-rest>
-            <div class="viewer">
-              <div class="viewer-content">
+            <baseAreaViewer
+              :width="areaWidth"
+              :length="areaHeight"
+              :furnitureItems="furnitureItems"
+              :ranges="props?.area?.ranges ?? []"
+              class="blockArea"
+            >
+              <div
+                v-for="(block, blockIndex) in blocks"
+                :key="blockIndex"
+                class="block-item"
+                :style="{
+                  left: `${block.x}px`,
+                  top: `${block.y}px`,
+                  width: `${block.width}px`,
+                  height: `${block.height}px`,
+                  border: `2px solid #1890ff`,
+                  position: 'absolute',
+                  cursor: 'move',
+                  backgroundColor: 'rgba(24, 144, 255, 0.1)',
+                }"
+                @mousedown="startDrag(block, $event)"
+              >
                 <div
-                  class="mapBox blockArea"
+                  class="resize-handle"
                   :style="{
-                    width: `${areaWidth}px`,
-                    height: `${areaHeight}px`,
-                    cursor: 'default',
+                    backgroundColor: '#1890ff',
                   }"
+                  @mousedown.stop="startResize(block, $event)"
                 >
-                  <!-- 已创建区块 -->
-                  <div
-                    v-for="(block, blockIndex) in blocks"
-                    :key="blockIndex"
-                    class="block-item"
-                    :style="{
-                      left: `${block.x}px`,
-                      top: `${block.y}px`,
-                      width: `${block.width}px`,
-                      height: `${block.height}px`,
-                      border: `2px solid #1890ff`,
-                      position: 'absolute',
-                      cursor: 'move',
-                      backgroundColor: 'rgba(24, 144, 255, 0.1)',
-                    }"
-                    @mousedown="startDrag(block, $event)"
-                    @mousemove="drag(block)"
-                    @mouseup="endDrag(block)"
-                  >
-                    <div
-                      class="resize-handle"
-                      :style="{
-                        backgroundColor: '#1890ff',
-                      }"
-                      @mousedown.stop="startResize(block, $event)"
-                    >
-                    </div>
-                  </div>
                 </div>
               </div>
-            </div>
+            </baseAreaViewer>
           </a-form-item-rest>
         </a-form-item>
 
@@ -315,13 +311,15 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch, computed } from 'vue'
+import { ref, reactive, watch, computed, nextTick } from 'vue'
 import { message, type FormInstance, type SelectProps } from 'ant-design-vue'
 import * as alarmApi from '@/api/alarm'
 import { getOriginPosition } from '@/utils/index'
 import type { AlarmPlanParams } from '@/api/alarm/types'
 import dayjs from 'dayjs'
 import { debounce } from 'lodash-es'
+import * as roomApi from '@/api/room'
+import type { Furniture } from '@/api/room/types'
 
 defineOptions({
   name: 'AlarmPlanModal',
@@ -369,9 +367,10 @@ type Props = {
   data?: AlarmPlan // 编辑数据
   area?: {
     width: number
-    height: number
+    length: number
     ranges: number[]
   }
+  devId?: string // 设备ID
 }
 const emit = defineEmits<{
   (e: 'update:open', value: boolean): void
@@ -387,21 +386,17 @@ const props = withDefaults(defineProps<Props>(), {
   data: undefined,
 })
 
-// const modelTitle = computed(() => {
-//   return props.alarmPlanId ? '编辑告警计划' : '新增告警计划'
-// })
-
 // 检测区域宽度
 const areaWidth = computed(() => {
-  return Math.abs(props.area?.width || 0)
+  return Math.abs(props.area?.length || 0)
 })
 // 检测区域高度
 const areaHeight = computed(() => {
-  return Math.abs(props.area?.height || 0)
+  return Math.abs(props.area?.width || 0)
 })
 
 // 获取原点坐标
-const { originX, originY } = getOriginPosition(props.area!.ranges, [0, 0])
+const { originOffsetX, originOffsetY } = getOriginPosition(props.area!.ranges, [0, 0])
 
 interface BlockItem {
   // 本地用
@@ -417,7 +412,8 @@ const blocks = ref<BlockItem[]>([])
 
 // 区块拖动
 const startDrag = (block: BlockItem, e: MouseEvent) => {
-  console.log('startDrag', block)
+  const { originX, originY } = getOriginPosition(props.area!.ranges, [0, 0])
+
   e.stopPropagation()
   const container = document.querySelector('.blockArea') as HTMLElement
   const rect = container.getBoundingClientRect()
@@ -434,6 +430,9 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
     block.y = Math.max(0, Math.min(newY, containerHeight - block.height))
     block.ox = block.x - originX
     block.oy = originY - block.y
+    // block.ox = block.x - originOffsetX
+    // block.oy = originOffsetY - block.y
+    formState.region = [block.ox, block.oy, block.width, block.height]
   }
 
   const upHandler = () => {
@@ -445,13 +444,13 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
   document.addEventListener('mouseup', upHandler)
 }
 
-const drag = (block: BlockItem) => {
-  formState.region = [block.ox, block.oy, block.width, block.height]
-}
+// const drag = (block: BlockItem) => {
+//   formState.region = [block.ox, block.oy, block.width, block.height]
+// }
 
-const endDrag = (block: BlockItem) => {
-  formState.region = [block.ox, block.oy, block.width, block.height]
-}
+// const endDrag = (block: BlockItem) => {
+//   formState.region = [block.ox, block.oy, block.width, block.height]
+// }
 
 // 获取容器边界
 const getContainerRect = () => {
@@ -472,6 +471,7 @@ const startResize = (block: BlockItem, e: MouseEvent) => {
     // 限制最小尺寸和容器边界
     block.width = Math.max(0, Math.min(initialWidth + deltaX, rect.width - block.x))
     block.height = Math.max(0, Math.min(initialHeight + deltaY, rect.height - block.y))
+    formState.region = [block.ox, block.oy, block.width, block.height]
   }
 
   const upHandler = () => {
@@ -510,7 +510,7 @@ type FormState = {
 
 const formState = reactive<FormState>({
   planName: '',
-  region: [0, 0, 50, 50],
+  region: [originOffsetX, originOffsetY, 50, 50],
   eventType: null,
   thresholdTime: 300,
   mergeTime: 30,
@@ -526,7 +526,7 @@ const formState = reactive<FormState>({
   timeThreshold: 300,
 })
 
-const planTemplateId = ref<number>()
+const planTemplateId = ref<number | null>()
 const planTemplate = ref<AlarmPlan>()
 
 const planTemplateOptions = ref<SelectProps['options']>([])
@@ -596,11 +596,18 @@ const handleSearch = (val: string) => {
   fetchTemplateList(val)
 }
 
-const initBlocks = () => {
+const initBlocks = async () => {
+  const { originX, originY, originOffsetX, originOffsetY } = getOriginPosition(
+    props.area!.ranges,
+    [0, 0]
+  )
+  console.log('🚀🚀🚀🚀initBlocks', originX, originY, originOffsetX, originOffsetY)
+  await nextTick()
+  formState.region = [0, 0, 50, 50]
   blocks.value = [
     {
-      x: 0,
-      y: 0,
+      x: originX,
+      y: originY,
       ox: formState.region[0],
       oy: formState.region[1],
       width: formState.region[2],
@@ -608,7 +615,10 @@ const initBlocks = () => {
     },
   ]
 }
-initBlocks()
+
+const resetBlocks = () => {
+  initBlocks()
+}
 
 const thresholdTimeFormat = ref<'s' | 'min' | 'hour' | 'day'>('s') // 触发阈值 额外选择器
 const timeThresholdFormat = ref<'s' | 'min' | 'hour' | 'day'>('s') // 异常消失时间阈值 额外选择器
@@ -790,7 +800,18 @@ watch(
       formState.region = echoFormState(val).region
       formState.enable = echoFormState(val).enable
       formState.linkagePushWechatService = echoFormState(val).linkagePushWechatService
-      initBlocks()
+      const { originOffsetX, originOffsetY } = getOriginPosition(props.area!.ranges, [0, 0])
+      blocks.value = [
+        {
+          x: formState.region[0] - originOffsetX,
+          y: originOffsetY - formState.region[1],
+          ox: formState.region[0],
+          oy: formState.region[1],
+          width: formState.region[2],
+          height: formState.region[3],
+        },
+      ]
+      console.log('🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥')
     }
     if (value && props.type === 'plan') {
       fetchTemplateList()
@@ -843,7 +864,7 @@ const cancel = () => {
   emit('update:open', false)
   // 重置表单
   formState.planName = ''
-  formState.region = [0, 0, 50, 50]
+  formState.region = [originOffsetX, originOffsetY, 50, 50]
   formState.eventType = null
   formState.thresholdTime = 300
   formState.mergeTime = 30
@@ -859,6 +880,8 @@ const cancel = () => {
   formState.count = 3
   formState.timeThreshold = 300
   formState.remark = ''
+  planTemplateId.value = null
+  initBlocks()
 }
 
 const eventTypeList = ref<{ label: string; value: string }[]>([])
@@ -977,27 +1000,14 @@ const submit = () => {
         return
       }
       if (formState.effectTimeRanges.length === 0) {
-        message.warn('请选择生效方式的范围')
+        message.warn('请选择生效方式')
+        return
+      }
+      if ([1, 2, 3, 9].includes(formState.eventType as number) && formState.region.length !== 4) {
+        message.warn('请选择检测区域')
         return
       }
-      // if ([1, 2, 3, 9].includes(formState.eventType as number) && formState.region.length !== 4) {
-      //   message.warn('请选择检测区域')
-      //   return
-      // }
-
       submitLoading.value = true
-      // alarmApi
-      //   .saveAlarmPlan(params)
-      //   .then((res) => {
-      //     console.log('添加成功', res)
-      //     submitLoading.value = false
-      //     message.success('添加成功')
-      //     emit('success')
-      //     cancel()
-      //   })
-      //   .catch(() => {
-      //     submitLoading.value = false
-      //   })
       if (props.type === 'plan') savePlan(params)
       if (props.type === 'template') saveTemplate(params)
     })
@@ -1059,6 +1069,45 @@ const saveTemplate = async (params: AlarmPlanParams) => {
     message.error('添加失败')
   }
 }
+
+const furnitureItems = ref<Furniture[]>([])
+const devId = ref<string | undefined>(props.devId)
+/**
+ * 获取房间布局
+ */
+const fetchRoomLayout = async () => {
+  console.log('fetchRoomLayout', devId.value)
+  if (!devId.value) {
+    furnitureItems.value = []
+    return
+  }
+  try {
+    const res = await roomApi.queryRoomInfo({
+      devId: devId.value,
+    })
+    console.log('✅获取到房间布局信息', res)
+    if (!res) return
+    const { furnitures } = res.data
+    if (furnitures) {
+      // 添加接口返回的家具数据
+      furnitures!.forEach((item) => {
+        furnitureItems.value.push({
+          ...item,
+          width: item.width || 45,
+          length: item.length || 45,
+          top: item.top || 0,
+          left: item.left || 0,
+          rotate: item.rotate || 0,
+          x: item.x || 0,
+          y: item.y || 0,
+        })
+      })
+    }
+  } catch (error) {
+    console.error('❌获取房间布局信息失败', error)
+  }
+}
+fetchRoomLayout()
 </script>
 
 <style scoped lang="less">

+ 3 - 2
src/views/device/detail/index.vue

@@ -308,7 +308,7 @@
         :data="alarmPlanDataWithType"
         :area="{
           width: (detailState.width as number) ?? 0,
-          height: (detailState.length as number) ?? 0,
+          length: (detailState.length as number) ?? 0,
           ranges: [
             (detailState.xxStart as number) ?? 0,
             (detailState.xxEnd as number) ?? 0,
@@ -481,6 +481,7 @@ const fetchDeviceDetail = async () => {
         [0, 0]
       )
 
+    furnitureItems.value = furnitureItems.value.filter((item) => item.type !== 'radar')
     // 添加雷达图标
     furnitureItems.value.unshift({
       name: '雷达',
@@ -557,7 +558,7 @@ onMounted(() => {
     host: import.meta.env.VITE_MQTT_HOST_POINT,
     username: import.meta.env.VITE_MQTT_USERNAME,
     password: import.meta.env.VITE_MQTT_PASSWORD,
-    clientId: `mqtt_client_${Math.random().toString(16).slice(2, 8)}`,
+    clientId: `web_mqtt_data${Math.random().toString(16).slice(2)}`,
   }
   mqttClient = mqtt.connect(mqttConfig.host, {
     clientId: mqttConfig.clientId,