ソースを参照

feat: 告警计划检测区域坐标调整;

liujia 1 ヶ月 前
コミット
32e9494

+ 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']

+ 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>

+ 133 - 27
src/views/device/detail/components/alarmPlanModal/index.vue

@@ -247,9 +247,51 @@
           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">
+            <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="resize-handle"
+                  :style="{
+                    backgroundColor: '#1890ff',
+                  }"
+                  @mousedown.stop="startResize(block, $event)"
+                >
+                </div>
+              </div>
+            </baseAreaViewer>
+
+            {{ blocks[0] }}
+
+            {{ getOriginPosition(props.area!.ranges, [0, 0]) }}
+
+            <!-- <div class="viewer">
               <div class="viewer-content">
                 <div
                   class="mapBox blockArea"
@@ -259,7 +301,6 @@
                     cursor: 'default',
                   }"
                 >
-                  <!-- 已创建区块 -->
                   <div
                     v-for="(block, blockIndex) in blocks"
                     :key="blockIndex"
@@ -289,7 +330,7 @@
                   </div>
                 </div>
               </div>
-            </div>
+            </div> -->
           </a-form-item-rest>
         </a-form-item>
 
@@ -315,13 +356,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 +412,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 +431,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 +457,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 +475,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 +489,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 +516,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 +555,7 @@ type FormState = {
 
 const formState = reactive<FormState>({
   planName: '',
-  region: [0, 0, 50, 50],
+  region: [originOffsetX, originOffsetY, 50, 50],
   eventType: null,
   thresholdTime: 300,
   mergeTime: 30,
@@ -596,11 +641,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 +660,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 +845,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 +909,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
@@ -860,6 +926,7 @@ const cancel = () => {
   formState.timeThreshold = 300
   formState.remark = ''
   planTemplateId.value = null
+  initBlocks()
 }
 
 const eventTypeList = ref<{ label: string; value: string }[]>([])
@@ -1060,6 +1127,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">

+ 1 - 1
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,