Преглед изворни кода

fix(区域配置): 修复区域配置保存失败时未清空缓存的问题

修复获取房间布局信息失败时未清空家具和子区域缓存的问题
修复子区域类型定义错误(isActice -> isActive)
添加子区域像素坐标相关字段
重构区域配置保存逻辑,使用标准化数据结构
移除EditableFurniture组件中未使用的代码
优化RadarEditor组件与store的交互逻辑
重构EditableSubregion组件,简化代码并优化性能
liujia пре 6 дана
родитељ
комит
958a6cc

+ 7 - 3
src/api/room/types.ts

@@ -88,15 +88,19 @@ export interface LocalSubRegionItem extends SubRegionItem {
   nanoid: string // 子区域唯一标识
   isDraging: boolean // 正在拖动
   isResizing: boolean // 正在调整大小
-  isActice: boolean // 是否选中
+  isActive: boolean // 是否选中
   isTracking: boolean // 是否开启区域跟踪  0-否,1-是 对应 trackPresence 字段
   isFalling: boolean // 是否屏蔽区域跌倒检测  0-否,1-是 对应 excludeFalling 字段
   isBed: boolean // 是否是床 本地判断使用
+  pixelX: number // 子区域在画布中的x坐标
+  pixelY: number // 子区域在画布中的y坐标
+  pixelWidth: number // 子区域在画布中的宽度
+  pixelHeight: number // 子区域在画布中的高度
 }
 
 export interface RoomData {
   roomId?: string // 房间ID
   devId?: number | string // 设备ID
-  subRegions?: SubRegions[] | null // 屏蔽子区域信息
-  furnitures?: Furniture[] | null // 家具信息
+  subRegions?: SubRegionItem[] | null // 屏蔽子区域信息
+  furnitures?: FurnitureItem[] | null // 家具信息
 }

+ 5 - 90
src/components/EditableFurniture/index.vue

@@ -1,10 +1,5 @@
 <template>
-  <div
-    class="editable-furniture"
-    :style="containerStyle"
-    @mousedown.stop="startDrag"
-    @dblclick.stop="showPanel = !showPanel"
-  >
+  <div class="editable-furniture" :style="containerStyle" @mousedown.stop="startDrag">
     <div class="furniture-rotated-container" :style="rotatedContainerStyle">
       <div class="furniture-wrapper">
         <furnitureIcon :icon="localItem.type" :width="localItem.width" :height="localItem.length" />
@@ -14,10 +9,8 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, watch, ref, computed, onMounted, nextTick, type CSSProperties } from 'vue'
-import type { FurnitureItem } from '@/types/radar'
-import furnitureIcon from '../furnitureIcon/index.vue'
-import type { FurnitureType } from '@/api/room/types'
+import { reactive, watch, computed, onMounted, nextTick, type CSSProperties } from 'vue'
+import type { FurnitureItem, FurnitureType, LocalFurnitureItem } from '@/api/room/types'
 
 defineOptions({ name: 'EditableFurniture' })
 
@@ -37,14 +30,10 @@ const props = withDefaults(defineProps<Props>(), {
 })
 
 const emit = defineEmits<{
-  (e: 'update', item: FurnitureItem): void
+  (e: 'update', item: LocalFurnitureItem): void
   (e: 'delete', nanoid: string): void
 }>()
 
-const showPanel = ref(false)
-// const nudgeStep = ref(5)
-// const showDebug = ref(true) // 调试模式
-
 // 坐标转换函数
 const geoToPixel = (geoX: number, geoY: number): { x: number; y: number } => {
   return {
@@ -123,7 +112,6 @@ const localItem = reactive<FurnitureItem>({
 
 // 像素位置和边界框
 const pixelPosition = reactive({ left: 0, top: 0 })
-const panelPosition = reactive({ left: 0, top: 0 })
 
 // 计算属性
 const boundingBox = computed(() => calculateBoundingBox())
@@ -153,7 +141,7 @@ const rotatedContainerStyle = computed(
       width: `${localItem.width}px`,
       height: `${localItem.length}px`,
       transform: `rotate(${localItem.rotate}deg)`,
-      transformOrigin: '0 0', // 以左上角为旋转中心
+      transformOrigin: '0 0',
     }) as CSSProperties
 )
 
@@ -181,7 +169,6 @@ watch(
   () => {
     nextTick(() => {
       updatePixelPosition()
-      setPanelInitialPosition()
     })
   },
   { deep: true }
@@ -220,12 +207,6 @@ const updateGeoPosition = () => {
   })
 }
 
-// 初始化面板位置
-const setPanelInitialPosition = () => {
-  panelPosition.left = Math.round(boundingBox.value.width + 20)
-  panelPosition.top = Math.round(-10)
-}
-
 // 组件挂载
 onMounted(() => {
   console.log('EditableFurniture mounted (top-left基准):', {
@@ -242,31 +223,10 @@ onMounted(() => {
 
   nextTick(() => {
     updatePixelPosition()
-    setPanelInitialPosition()
     emit('update', { ...localItem })
   })
 })
 
-// 通过地理坐标更新
-// const updateByGeoCoords = () => {
-//   updatePixelPosition()
-//   emit('update', { ...localItem })
-// }
-
-// 尺寸变化处理
-// const onSizeChange = () => {
-//   updatePixelPosition()
-//   setPanelInitialPosition()
-//   emit('update', { ...localItem })
-// }
-
-// 旋转变化处理
-// const onRotateChange = () => {
-//   updatePixelPosition()
-//   setPanelInitialPosition()
-//   emit('update', { ...localItem })
-// }
-
 // 拖拽家具
 const startDrag = (e: MouseEvent) => {
   console.log('Start drag furniture (top-left):', localItem.nanoid)
@@ -296,51 +256,6 @@ const startDrag = (e: MouseEvent) => {
   window.addEventListener('mouseup', onUp)
   document.body.style.userSelect = 'none'
 }
-
-// 面板拖拽
-// const startPanelDrag = (e: MouseEvent) => {
-//   e.preventDefault()
-//   e.stopPropagation()
-
-//   const startX = e.clientX
-//   const startY = e.clientY
-//   const initLeft = panelPosition.left
-//   const initTop = panelPosition.top
-
-//   const onMove = (ev: MouseEvent) => {
-//     panelPosition.left = initLeft + (ev.clientX - startX)
-//     panelPosition.top = initTop + (ev.clientY - startY)
-//   }
-
-//   const onUp = () => {
-//     window.removeEventListener('mousemove', onMove)
-//     window.removeEventListener('mouseup', onUp)
-//   }
-
-//   window.addEventListener('mousemove', onMove)
-//   window.addEventListener('mouseup', onUp)
-// }
-
-// 微调功能
-// const nudge = (direction: 'left' | 'right' | 'up' | 'down') => {
-//   const step = nudgeStep.value
-//   switch (direction) {
-//     case 'left':
-//       pixelPosition.left -= step
-//       break
-//     case 'right':
-//       pixelPosition.left += step
-//       break
-//     case 'up':
-//       pixelPosition.top -= step
-//       break
-//     case 'down':
-//       pixelPosition.top += step
-//       break
-//   }
-//   updateGeoPosition()
-//   emit('update', { ...localItem })
-// }
 </script>
 
 <style scoped lang="less">

+ 203 - 380
src/components/EditableSubregion/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="editable-subregion">
     <div
+      ref="containerRef"
       class="mapBox blockArea"
       :style="{
         width: `${canvasSize}px`,
@@ -24,21 +25,18 @@
 
       <!-- 已创建区块 -->
       <div
-        v-for="(block, blockIndex) in blocks"
-        :key="block.id"
+        v-for="(block, index) in roomStore.localSubRegions"
+        :key="block.nanoid"
         class="block-item"
         :style="getBlockStyle(block)"
         @mousedown.self="startDrag(block, $event)"
-        @click="selectBlock(block)"
       >
         <div
           class="resize-handle"
-          :style="{
-            backgroundColor: block?.isBed ? '#1abc1a' : '#1890ff',
-          }"
+          :style="{ backgroundColor: block.isBed ? '#1abc1a' : '#1890ff' }"
           @mousedown.stop="startResize(block, $event)"
         >
-          {{ blockIndex + 1 }}
+          {{ index + 1 }}
         </div>
       </div>
     </div>
@@ -46,459 +44,292 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch, onMounted } from 'vue'
+import { ref, computed, onMounted, type CSSProperties, watch, nextTick } from 'vue'
 import { message } from 'ant-design-vue'
 import { nanoid } from 'nanoid'
-import type { SubRegions } from '@/api/room/types'
+import { useRoomStore } from '@/stores/room'
+import type { LocalSubRegionItem } from '@/api/room/types'
 
 defineOptions({ name: 'EditableSubregion' })
+const roomStore = useRoomStore()
 
+// ✅ props
 interface Props {
-  ranges: [number, number, number, number] // 区域范围 [xStart, xEnd, yStart, yEnd]
-  angle: number // 设备方向
-  canvasSize?: number // 画布尺寸
-  subRegions?: SubRegions[]
+  angle: number
+  canvasSize?: number
   editable?: boolean
   hasBed?: boolean
 }
-
-interface BlockItem {
-  // 基础属性
-  id: string
-  isDragging: boolean
-  isResizing: boolean
-  isActive: boolean
-  isTracking: boolean
-  isFalling: boolean
-  isBed?: boolean
-
-  // 地理坐标(基于新坐标系)
-  geoX: number // 区块中心X坐标(向右为正)
-  geoY: number // 区块中心Y坐标(向上为正)
-  geoWidth: number // 区块宽度(地理坐标)
-  geoHeight: number // 区块高度(地理坐标)
-
-  // 像素坐标(用于显示)
-  pixelX: number
-  pixelY: number
-  pixelWidth: number
-  pixelHeight: number
-
-  // 接口数据
-  startXx: number
-  stopXx: number
-  startYy: number
-  stopYy: number
-  startZz: number
-  stopZz: number
-  isLowSnr: number
-  isDoor: number
-  presenceEnterDuration: number
-  presenceExitDuration: number
-  trackPresence: number
-  excludeFalling: number
-}
-
 const props = withDefaults(defineProps<Props>(), {
   canvasSize: 500,
   editable: false,
   hasBed: false,
 })
-
 const emit = defineEmits<{
-  (e: 'update:subRegions', regions: SubRegions[]): void
+  // (e: 'update:subRegions', regions: LocalSubRegionItem[]): void
   (e: 'create'): void
-  (e: 'update', regions: SubRegions[]): void
+  (e: 'update', regions: LocalSubRegionItem[]): void
 }>()
 
-// 调试日志
-console.log('EditableSubregion mounted with props:', {
-  ranges: props.ranges,
-  canvasSize: props.canvasSize,
-  editable: props.editable,
-  subRegions: props.subRegions,
-})
-
-// 坐标转换函数(基于新组件逻辑)
-const geoToPixel = (geoX: number, geoY: number): { x: number; y: number } => {
-  const center = props.canvasSize / 2
-  return {
-    x: center + geoX,
-    y: center - geoY, // Canvas Y轴向下,所以用减法
-  }
-}
-
-const pixelToGeo = (pixelX: number, pixelY: number): { x: number; y: number } => {
-  const center = props.canvasSize / 2
-  return {
-    x: pixelX - center,
-    y: center - pixelY,
-  }
-}
+// ✅ 工具函数
+const debug = false
+const log = (...args: unknown[]) => debug && console.log('[EditableSubregion]', ...args)
 
-const editable = computed(() => props.editable)
-
-const blocks = ref<BlockItem[]>([])
+// ✅ refs / 状态
+const containerRef = ref<HTMLElement>()
 const isCreating = ref(false)
 const currentBlock = ref<{
-  startX: number // 像素坐标起点X
-  startY: number // 像素坐标起点Y
-  currentX: number // 像素坐标当前X
-  currentY: number // 像素坐标当前Y
+  startX: number
+  startY: number
+  currentX: number
+  currentY: number
 } | null>(null)
-const selectedBlock = ref<BlockItem | null>(null)
-
-// 监听subRegions变化,更新blocks
-watch(
-  () => props.subRegions,
-  (newSubRegions) => {
-    console.log('subRegions changed:', newSubRegions)
-    if (newSubRegions && newSubRegions.length > 0) {
-      blocks.value = newSubRegions.map((item, index) => {
-        // 计算区块中心地理坐标
-        const centerX = (item.startXx + item.stopXx) / 2
-        const centerY = (item.startYy + item.stopYy) / 2
-        const geoWidth = Math.abs(item.stopXx - item.startXx)
-        const geoHeight = Math.abs(item.stopYy - item.startYy)
-
-        // 转换为像素坐标
-        const pixelPos = geoToPixel(centerX, centerY)
-        const pixelWidth = geoWidth // 直接使用地理宽度作为像素宽度
-        const pixelHeight = geoHeight // 直接使用地理高度作为像素高度
-
-        console.log(`Block ${index}:`, {
-          geo: { centerX, centerY, geoWidth, geoHeight },
-          pixel: { ...pixelPos, pixelWidth, pixelHeight },
-        })
-
-        return {
-          id: item.id || nanoid(),
-          isDragging: false,
-          isResizing: false,
-          isActive: false,
-          isTracking: Boolean(item.trackPresence),
-          isFalling: Boolean(item.excludeFalling),
-          isBed: index === 0 && props.hasBed,
-          // 地理坐标
-          geoX: centerX,
-          geoY: centerY,
-          geoWidth,
-          geoHeight,
-          // 像素坐标
-          pixelX: pixelPos.x - pixelWidth / 2,
-          pixelY: pixelPos.y - pixelHeight / 2,
-          pixelWidth,
-          pixelHeight,
-          // 接口数据
-          startXx: item.startXx,
-          stopXx: item.stopXx,
-          startYy: item.startYy,
-          stopYy: item.stopYy,
-          startZz: item.startZz || 0,
-          stopZz: item.stopZz || 0,
-          isLowSnr: item.isLowSnr || 0,
-          isDoor: item.isDoor || 0,
-          presenceEnterDuration: item.presenceEnterDuration || 3,
-          presenceExitDuration: item.presenceExitDuration || 3,
-          trackPresence: item.trackPresence || 0,
-          excludeFalling: item.excludeFalling || 0,
-        }
-      })
-    } else {
-      blocks.value = []
-    }
-  },
-  { immediate: true, deep: true }
-)
-
-// 区块样式计算
-const getBlockStyle = (block: BlockItem) => {
-  return {
-    left: `${block.pixelX}px`,
-    top: `${block.pixelY}px`,
-    width: `${block.pixelWidth}px`,
-    height: `${block.pixelHeight}px`,
-    border: `2px solid ${block?.isBed ? '#1abc1a' : block.isActive ? 'yellow' : '#1890ff'}`,
-    position: 'absolute',
-    cursor: !editable.value ? 'no-drop' : 'move',
-    backgroundColor: block.isBed ? 'rgba(26, 188, 26, 0.1)' : 'rgba(24, 144, 255, 0.1)',
-    zIndex: 1,
-  }
-}
 
-// 更新子区域数据
-const updateSubRegionsData = () => {
-  console.log('Updating subregions data, blocks:', blocks.value)
-
-  if (blocks.value.length > 0) {
-    const subRegionsData = blocks.value.map((block) => {
-      // 从像素坐标转换回地理坐标
-      const centerGeo = pixelToGeo(
-        block.pixelX + block.pixelWidth / 2,
-        block.pixelY + block.pixelHeight / 2
-      )
-
-      const geoWidth = block.pixelWidth
-      const geoHeight = block.pixelHeight
-
-      const regionData = {
-        id: block.id,
-        startXx: centerGeo.x - geoWidth / 2,
-        stopXx: centerGeo.x + geoWidth / 2,
-        startYy: centerGeo.y - geoHeight / 2,
-        stopYy: centerGeo.y + geoHeight / 2,
-        startZz: Number(block.startZz) || 0,
-        stopZz: Number(block.stopZz) || 0,
-        isLowSnr: block.isLowSnr,
-        isDoor: block.isDoor,
-        presenceEnterDuration: block.presenceEnterDuration,
-        presenceExitDuration: block.presenceExitDuration,
-        trackPresence: Number(block.isTracking),
-        excludeFalling: Number(block.isFalling),
-      }
+const editable = computed(() => props.editable)
+const center = computed(() => props.canvasSize / 2)
 
-      console.log('Region data:', regionData)
-      return regionData
-    })
+// ✅ 坐标转换
+const geoToPixel = (x: number, y: number) => ({
+  x: center.value + x,
+  y: center.value - y,
+})
+const pixelToGeo = (x: number, y: number) => ({
+  x: x - center.value,
+  y: center.value - y,
+})
+
+// ✅ Block 样式
+const getBlockStyle = (block: LocalSubRegionItem): CSSProperties => ({
+  left: `${block.pixelX}px`,
+  top: `${block.pixelY}px`,
+  width: `${block.pixelWidth}px`,
+  height: `${block.pixelHeight}px`,
+  border: `2px solid ${block.isBed ? '#1abc1a' : block.isActive ? 'yellow' : '#1890ff'}`,
+  position: 'absolute',
+  cursor: editable.value ? 'move' : 'no-drop',
+  backgroundColor: block.isBed ? 'rgba(26,188,26,0.1)' : 'rgba(24,144,255,0.1)',
+  zIndex: 1,
+})
 
-    emit('update', subRegionsData)
-  } else {
+// ✅ 同步数据(像素→地理)
+const updateSubRegionsData = () => {
+  if (!roomStore.localSubRegions.length) {
     emit('update', [])
+    return
   }
+
+  const updated = roomStore.localSubRegions.map((block) => {
+    const centerGeo = pixelToGeo(
+      block.pixelX + block.pixelWidth / 2,
+      block.pixelY + block.pixelHeight / 2
+    )
+    const w = block.pixelWidth
+    const h = block.pixelHeight
+
+    return {
+      ...block,
+      startXx: centerGeo.x - w / 2,
+      stopXx: centerGeo.x + w / 2,
+      startYy: centerGeo.y - h / 2,
+      stopYy: centerGeo.y + h / 2,
+      startZz: Number(block.startZz) || 0,
+      stopZz: Number(block.stopZz) || 0,
+    }
+  })
+
+  roomStore.localSubRegions = updated
+  emit('update', updated)
 }
 
-// 新建区块处理
+// ✅ 新建区块
 const createNewBlock = () => {
-  console.log('createNewBlock called, current blocks:', blocks.value.length)
-  if (blocks.value && blocks.value.length >= 6) {
+  if (roomStore.localSubRegions.length >= 6) {
     message.warn('最多只能创建6个区块')
     return
   }
   isCreating.value = true
   message.info('请在画布上拖拽创建子区域')
 }
-
 defineExpose({ createNewBlock })
 
-// 获取容器边界
-const getContainerRect = () => {
-  const container = document.querySelector('.blockArea') as HTMLElement
-  const rect = container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 }
-  console.log('Container rect:', rect)
-  return rect
-}
-
-// 鼠标移动处理
-const handleMouseMove = (e: MouseEvent) => {
-  if (!currentBlock.value) return
-
-  const rect = getContainerRect()
-  currentBlock.value.currentX = Math.max(0, Math.min(e.clientX - rect.left, rect.width))
-  currentBlock.value.currentY = Math.max(0, Math.min(e.clientY - rect.top, rect.height))
-}
-
-// 鼠标释放处理 - 创建新区块
-const handleMouseUp = () => {
-  if (!currentBlock.value) return
+// ✅ 鼠标操作通用工具
+const getRect = () =>
+  containerRef.value?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 }
 
-  const { startX, startY, currentX, currentY } = currentBlock.value
-  const width = Math.abs(currentX - startX)
-  const height = Math.abs(currentY - startY)
+// ✅ 创建逻辑
+const handleMouseDown = (e: MouseEvent) => {
+  if (!editable.value || !isCreating.value) return
 
-  console.log('Mouse up, creating block:', { startX, startY, currentX, currentY, width, height })
+  const rect = getRect()
+  const startX = e.clientX - rect.left
+  const startY = e.clientY - rect.top
+  currentBlock.value = { startX, startY, currentX: startX, currentY: startY }
 
-  if (width > 10 && height > 10) {
-    const minX = Math.min(startX, currentX)
-    const minY = Math.min(startY, currentY)
+  const move = (e: MouseEvent) => {
+    if (!currentBlock.value) return
+    currentBlock.value.currentX = Math.max(0, Math.min(e.clientX - rect.left, rect.width))
+    currentBlock.value.currentY = Math.max(0, Math.min(e.clientY - rect.top, rect.height))
+  }
 
-    // 计算地理坐标
-    const centerGeo = pixelToGeo(minX + width / 2, minY + height / 2)
-    const geoWidth = width
-    const geoHeight = height
+  const up = () => {
+    if (!currentBlock.value) return
+    const { startX, startY, currentX, currentY } = currentBlock.value
+    const width = Math.abs(currentX - startX)
+    const height = Math.abs(currentY - startY)
+
+    if (width > 10 && height > 10) {
+      const minX = Math.min(startX, currentX)
+      const minY = Math.min(startY, currentY)
+      const centerGeo = pixelToGeo(minX + width / 2, minY + height / 2)
+
+      const newBlock: LocalSubRegionItem = {
+        nanoid: nanoid(),
+        isDraging: false,
+        isResizing: false,
+        isActive: false,
+        isTracking: false,
+        isFalling: false,
+        isBed: false,
+        pixelX: minX,
+        pixelY: minY,
+        pixelWidth: width,
+        pixelHeight: height,
+        startXx: centerGeo.x - width / 2,
+        stopXx: centerGeo.x + width / 2,
+        startYy: centerGeo.y - height / 2,
+        stopYy: centerGeo.y + height / 2,
+        startZz: 0,
+        stopZz: 0,
+        isLowSnr: 0,
+        isDoor: 0,
+        presenceEnterDuration: 3,
+        presenceExitDuration: 3,
+        trackPresence: 0,
+        excludeFalling: 0,
+      }
 
-    const newBlock: BlockItem = {
-      id: nanoid(),
-      isDragging: false,
-      isResizing: false,
-      isActive: false,
-      isTracking: false,
-      isFalling: false,
-      // 地理坐标
-      geoX: centerGeo.x,
-      geoY: centerGeo.y,
-      geoWidth,
-      geoHeight,
-      // 像素坐标
-      pixelX: minX,
-      pixelY: minY,
-      pixelWidth: width,
-      pixelHeight: height,
-      // 接口数据
-      startXx: centerGeo.x - geoWidth / 2,
-      stopXx: centerGeo.x + geoWidth / 2,
-      startYy: centerGeo.y - geoHeight / 2,
-      stopYy: centerGeo.y + geoHeight / 2,
-      startZz: 0,
-      stopZz: 0,
-      isLowSnr: 0,
-      isDoor: 0,
-      presenceEnterDuration: 3,
-      presenceExitDuration: 3,
-      trackPresence: 0,
-      excludeFalling: 0,
+      roomStore.localSubRegions.push(newBlock)
+      emit('create')
+    } else {
+      message.warn('区域太小,请绘制更大的区域')
     }
 
-    console.log('New block created:', newBlock)
-    blocks.value.push(newBlock)
-
-    emit('create')
-    updateSubRegionsData()
-  } else {
-    message.warn('区域太小,请绘制更大的区域')
+    currentBlock.value = null
+    isCreating.value = false
+    document.removeEventListener('mousemove', move)
+    document.removeEventListener('mouseup', up)
   }
 
-  currentBlock.value = null
-  isCreating.value = false
-  document.removeEventListener('mousemove', handleMouseMove)
-  document.removeEventListener('mouseup', handleMouseUp)
+  document.addEventListener('mousemove', move)
+  document.addEventListener('mouseup', up)
 }
 
-// 区块拖动
-const startDrag = (block: BlockItem, e: MouseEvent) => {
+// ✅ 拖动
+const startDrag = (block: LocalSubRegionItem, e: MouseEvent) => {
   if (!editable.value) return
   e.stopPropagation()
 
-  console.log('Start dragging block:', block.id)
-
-  // 取消选中其他区块
-  blocks.value.forEach((b) => {
-    b.isActive = false
-  })
-
-  block.isDragging = true
+  roomStore.localSubRegions.forEach((b) => (b.isActive = false))
+  block.isDraging = true
   block.isActive = true
 
-  const container = document.querySelector('.blockArea') as HTMLElement
-  const rect = container.getBoundingClientRect()
+  const rect = getRect()
   const offsetX = e.clientX - rect.left - block.pixelX
   const offsetY = e.clientY - rect.top - block.pixelY
+  const initX = block.pixelX
+  const initY = block.pixelY
 
-  const initialPixelX = block.pixelX
-  const initialPixelY = block.pixelY
-
-  const moveHandler = (e: MouseEvent) => {
+  const move = (e: MouseEvent) => {
     const newX = e.clientX - rect.left - offsetX
     const newY = e.clientY - rect.top - offsetY
-    const containerWidth = container.offsetWidth
-    const containerHeight = container.offsetHeight
-
-    // 限制边界
-    block.pixelX = Math.max(0, Math.min(newX, containerWidth - block.pixelWidth))
-    block.pixelY = Math.max(0, Math.min(newY, containerHeight - block.pixelHeight))
+    block.pixelX = Math.max(0, Math.min(newX, rect.width - block.pixelWidth))
+    block.pixelY = Math.max(0, Math.min(newY, rect.height - block.pixelHeight))
   }
 
-  const upHandler = () => {
-    block.isDragging = false
+  const up = () => {
+    block.isDraging = false
     block.isActive = false
-
-    if (block.pixelX !== initialPixelX || block.pixelY !== initialPixelY) {
-      console.log('Block dragged, updating data')
-      updateSubRegionsData()
-    }
-
-    document.removeEventListener('mousemove', moveHandler)
-    document.removeEventListener('mouseup', upHandler)
+    if (block.pixelX !== initX || block.pixelY !== initY) updateSubRegionsData()
+    document.removeEventListener('mousemove', move)
+    document.removeEventListener('mouseup', up)
   }
 
-  document.addEventListener('mousemove', moveHandler)
-  document.addEventListener('mouseup', upHandler)
+  document.addEventListener('mousemove', move)
+  document.addEventListener('mouseup', up)
 }
 
-// 区块调整大小
-const startResize = (block: BlockItem, e: MouseEvent) => {
+// ✅ 缩放
+const startResize = (block: LocalSubRegionItem, e: MouseEvent) => {
   if (!editable.value) return
   e.stopPropagation()
   e.preventDefault()
 
-  console.log('Start resizing block:', block.id)
-
   block.isResizing = true
-  selectedBlock.value = block
-
   const startX = e.clientX
   const startY = e.clientY
-  const initialWidth = block.pixelWidth
-  const initialHeight = block.pixelHeight
-  const initialX = block.pixelX
-  const initialY = block.pixelY
+  const initW = block.pixelWidth
+  const initH = block.pixelHeight
+  const rect = getRect()
 
-  const moveHandler = (e: MouseEvent) => {
-    const rect = getContainerRect()
+  const move = (e: MouseEvent) => {
     const deltaX = e.clientX - startX
     const deltaY = e.clientY - startY
-
-    // 限制最小尺寸和容器边界
-    const newWidth = Math.max(20, Math.min(initialWidth + deltaX, rect.width - block.pixelX))
-    const newHeight = Math.max(20, Math.min(initialHeight + deltaY, rect.height - block.pixelY))
-
-    block.pixelWidth = newWidth
-    block.pixelHeight = newHeight
+    block.pixelWidth = Math.max(20, Math.min(initW + deltaX, rect.width - block.pixelX))
+    block.pixelHeight = Math.max(20, Math.min(initH + deltaY, rect.height - block.pixelY))
   }
 
-  const upHandler = () => {
+  const up = () => {
     block.isResizing = false
-    selectedBlock.value = null
-    console.log('Block resized, updating data')
     updateSubRegionsData()
-    document.removeEventListener('mousemove', moveHandler)
-    document.removeEventListener('mouseup', upHandler)
-  }
-
-  document.addEventListener('mousemove', moveHandler)
-  document.addEventListener('mouseup', upHandler)
-}
-
-// 鼠标按下事件 - 开始创建区块
-const handleMouseDown = (e: MouseEvent) => {
-  if (!editable.value) {
-    console.log('Not editable, ignoring mousedown')
-    return
-  }
-  if (!isCreating.value) {
-    console.log('Not in creating mode, ignoring mousedown')
-    return
-  }
-
-  console.log('Mouse down for creating block')
-
-  const rect = getContainerRect()
-  const startX = e.clientX - rect.left
-  const startY = e.clientY - rect.top
-
-  currentBlock.value = {
-    startX,
-    startY,
-    currentX: startX,
-    currentY: startY,
+    document.removeEventListener('mousemove', move)
+    document.removeEventListener('mouseup', up)
   }
 
-  document.addEventListener('mousemove', handleMouseMove)
-  document.addEventListener('mouseup', handleMouseUp)
+  document.addEventListener('mousemove', move)
+  document.addEventListener('mouseup', up)
 }
 
-const selectBlock = (block: BlockItem) => {
-  if (!editable.value) return
-  selectedBlock.value = block
-  blocks.value.forEach((item) => {
-    item.isActive = item === block
+// ✅ 回显数据(地理→像素)
+const echoSubRegions = () => {
+  roomStore.localSubRegions = roomStore.localSubRegions.map((item, index) => {
+    const centerX = (Number(item.startXx) + Number(item.stopXx)) / 2
+    const centerY = (Number(item.startYy) + Number(item.stopYy)) / 2
+    const w = Math.abs(Number(item.stopXx) - Number(item.startXx))
+    const h = Math.abs(Number(item.stopYy) - Number(item.startYy))
+    const pos = geoToPixel(centerX, centerY)
+
+    return {
+      ...item,
+      nanoid: item.nanoid || nanoid(),
+      isDraging: false,
+      isResizing: false,
+      isActive: false,
+      isTracking: Boolean(item.trackPresence),
+      isFalling: Boolean(item.excludeFalling),
+      isBed: index === 0 && props.hasBed,
+      pixelX: pos.x - w / 2,
+      pixelY: pos.y - h / 2,
+      pixelWidth: w,
+      pixelHeight: h,
+    }
   })
-  console.log('Block selected:', block.id)
 }
+echoSubRegions()
 
-// 添加调试信息
-onMounted(() => {
-  console.log('EditableSubregion component mounted')
+// ✅ 监听手动调整,重新回显像素位置
+const geoCoordinatesSignature = computed(() => {
+  return roomStore.localSubRegions
+    .map((region) => `${region.startXx}_${region.stopXx}_${region.startYy}_${region.stopYy}`)
+    .join('|')
 })
+
+watch(geoCoordinatesSignature, () => {
+  nextTick(() => {
+    echoSubRegions()
+  })
+})
+
+onMounted(() => log('component mounted'))
 </script>
 
 <style scoped lang="less">
@@ -507,14 +338,6 @@ onMounted(() => {
   gap: 20px;
 }
 
-// .mapBox {
-//   position: relative;
-//   flex-shrink: 0;
-//   border: 1px solid #ddd;
-//   background-color: rgba(242, 242, 240, 0.5);
-//   pointer-events: auto;
-// }
-
 .temp-block {
   position: absolute;
   background: rgba(24, 144, 255, 0.2);

+ 67 - 67
src/components/RadarEditor/index.vue

@@ -10,16 +10,17 @@
           :coordinates="coordinates"
           :canvas-size="400"
           :disabled="disabled"
+          @update="handleFurnitureUpdate"
         />
       </template>
 
       <template #subregion>
         <EditableSubregion
+          v-if="modeRadio === 2"
           ref="editableSubregionRef"
-          :ranges="coordinates"
           :angle="angle"
           :canvas-size="400"
-          :subRegions="localSubRegions"
+          :subRegions="roomStore.localSubRegions"
           :editable="!disabled"
           :has-bed="localFurniture.some((item) => item.type === 'bed')"
           @create="handleSubregionCreate"
@@ -81,15 +82,15 @@
           <div class="panel-hd">区域操作</div>
           <div class="panel-ct">
             <div class="subregionTool">
-              <div v-if="localSubRegions.length === 0">
+              <div v-if="roomStore.localSubRegions.length === 0">
                 暂无区域,<a-button type="link" size="small" @click="createSubregion"
                   >新建区域</a-button
                 >
               </div>
               <div v-else>
-                <span>已创建 {{ localSubRegions.length }} 个区域</span>
+                <span>已创建 {{ roomStore.localSubRegions.length }} 个区域</span>
                 <a-button
-                  v-if="localSubRegions.length < 6"
+                  v-if="roomStore.localSubRegions.length < 6"
                   type="link"
                   size="small"
                   @click="createSubregion"
@@ -167,7 +168,7 @@
                         </div>
                       </div>
 
-                      <div class="mapConfig-item">
+                      <!-- <div class="mapConfig-item">
                         <label class="mapConfig-item-label">位置微调:</label>
                         <div class="mapConfig-item-content"></div>
                         <a-space>
@@ -182,9 +183,9 @@
                             style="width: 60px"
                           />
                         </a-space>
-                      </div>
+                      </div> -->
 
-                      <div class="mapConfig-item">
+                      <!-- <div class="mapConfig-item">
                         <label class="mapConfig-item-label">left/top:</label>
                         <div class="mapConfig-item-content">
                           <a-space>
@@ -202,7 +203,7 @@
                             />
                           </a-space>
                         </div>
-                      </div>
+                      </div> -->
 
                       <div class="mapConfig-item">
                         <label class="mapConfig-item-label">x/y:</label>
@@ -264,7 +265,7 @@
             </a-space>
           </div>
           <div class="panel-ct">
-            <template v-if="localSubRegions.length">
+            <template v-if="roomStore.localSubRegions.length">
               <div
                 v-for="(region, index) in roomStore.localSubRegions"
                 :key="index"
@@ -354,7 +355,7 @@
                         <div class="mapConfig-item-content">
                           <a-popconfirm
                             title="确定删除区域吗?"
-                            @confirm="deleteBlockArea(region.id || '')"
+                            @confirm="deleteBlockArea(region.nanoid || '')"
                           >
                             <DeleteOutlined />
                           </a-popconfirm>
@@ -377,26 +378,25 @@
 
 <script setup lang="ts">
 import { ref, computed, onUnmounted, reactive } from 'vue'
-// import type { FurnitureItem } from '@/types/radar'
 import DetectionAreaView from '../DetectionAreaView/index.vue'
 import EditableFurniture from '../EditableFurniture/index.vue'
 import EditableSubregion from '../EditableSubregion/index.vue'
 import type { FurnitureIconType } from '@/types/furniture'
-import { message } from 'ant-design-vue'
+// import { message } from 'ant-design-vue'
 import { nanoid } from 'nanoid'
 import { furnitureIconNameMap, furnitureIconSizeMap } from '@/const/furniture'
 import type {
   FurnitureItem,
-  // LocalFurnitureItem,
-  // LocalSubRegionItem,
-  SubRegions,
+  LocalFurnitureItem,
+  LocalSubRegionItem,
+  // SubRegions,
 } from '@/api/room/types'
 import {
   DeleteOutlined,
-  ArrowLeftOutlined,
-  ArrowUpOutlined,
-  ArrowDownOutlined,
-  ArrowRightOutlined,
+  // ArrowLeftOutlined,
+  // ArrowUpOutlined,
+  // ArrowDownOutlined,
+  // ArrowRightOutlined,
 } from '@ant-design/icons-vue'
 import { useRoomStore } from '@/stores/room'
 
@@ -405,56 +405,53 @@ defineOptions({ name: 'RadarEditor' })
 interface Props {
   coordinates: [number, number, number, number]
   angle: number
-  // furnitureItems?: LocalFurnitureItem[]
-  // subRegions?: LocalSubRegionItem[]
   disabled?: boolean
 }
 const props = defineProps<Props>()
-const emit = defineEmits<{
-  (e: 'update:furnitureItems', items: FurnitureItem[]): void
-  (e: 'update:subRegions', regions: SubRegions[]): void
-}>()
+// const emit = defineEmits<{
+//   (e: 'update:furnitureItems', items: FurnitureItem[]): void
+//   (e: 'update:subRegions', regions: SubRegions[]): void
+// }>()
 
 const roomStore = useRoomStore()
 const localFurniture = ref<FurnitureItem[]>(roomStore.localFurnitureItems ?? [])
-const localSubRegions = ref<SubRegions[]>(roomStore.localSubRegions ?? [])
+// const localSubRegions = ref<SubRegions[]>(roomStore.localSubRegions ?? [])
 
 console.log('props', props)
 
 const inputStyle = computed(() => ({ width: '70px' }))
-const pixelPosition = reactive({ left: 0, top: 0 })
-const nudgeStep = ref(5)
+// const pixelPosition = reactive({ left: 0, top: 0 })
+// const nudgeStep = ref(5)
 // 微调功能
-const nudge = (direction: 'left' | 'right' | 'up' | 'down') => {
-  const step = nudgeStep.value
-  switch (direction) {
-    case 'left':
-      pixelPosition.left -= step
-      break
-    case 'right':
-      pixelPosition.left += step
-      break
-    case 'up':
-      pixelPosition.top -= step
-      break
-    case 'down':
-      pixelPosition.top += step
-      break
-  }
-  // updateGeoPosition()
-  // emit('update', { ...localItem })
-}
+// const nudge = (direction: 'left' | 'right' | 'up' | 'down') => {
+//   const step = nudgeStep.value
+//   switch (direction) {
+//     case 'left':
+//       pixelPosition.left -= step
+//       break
+//     case 'right':
+//       pixelPosition.left += step
+//       break
+//     case 'up':
+//       pixelPosition.top -= step
+//       break
+//     case 'down':
+//       pixelPosition.top += step
+//       break
+//   }
+// }
 
 function deleteFurniture(nanoid: string) {
   roomStore.localFurnitureItems = roomStore.localFurnitureItems.filter((i) => i.nanoid !== nanoid)
 }
 
-function addFurniture(item: FurnitureItem) {
-  localFurniture.value.push(item)
-  emit('update:furnitureItems', [...localFurniture.value])
-}
+// function addFurniture(item: FurnitureItem) {
+//   // localFurniture.value.push(item)
+//   roomStore.localFurnitureItems.push(item)
+//   // emit('update:furnitureItems', [...roomStore.localFurnitureItems])
+// }
 
-defineExpose({ addFurniture })
+// defineExpose({ addFurniture })
 
 const modeRadio = ref<1 | 2 | 3>(1)
 const sideRadio = ref<1 | 2 | 3 | 4>(1)
@@ -501,27 +498,31 @@ const add = (icon: FurnitureIconType) => {
   const originWidth = furnitureIconSizeMap[icon].width || 30
   const originHeight = furnitureIconSizeMap[icon].height || 30
 
-  const newItem: FurnitureItem = {
+  const newItem: LocalFurnitureItem = {
     name: furnitureIconNameMap[icon],
     type: icon,
     width: originWidth,
     length: originHeight,
-    top: 0,
-    left: 0,
+    // top: 0,
+    // left: 0,
     rotate: 0,
     x: 0,
     y: 0,
     nanoid: nanoid(),
   }
 
-  addFurniture(newItem)
-  message.success('已添加家具')
+  roomStore.localFurnitureItems.push(newItem)
+  // message.success('已添加家具')
+}
+
+const handleFurnitureUpdate = (item: LocalFurnitureItem) => {
+  roomStore.localFurnitureItems = roomStore.localFurnitureItems.map((i) =>
+    i.nanoid === item.nanoid ? item : i
+  )
 }
 
 // 创建子区域
 const createSubregion = () => {
-  // modeRadio.value = 2
-  // 通过ref调用EditableSubregion组件的createNewBlock方法
   if (editableSubregionRef.value) {
     editableSubregionRef.value?.createNewBlock()
   }
@@ -533,19 +534,18 @@ const handleSubregionCreate = () => {
 }
 
 // 处理子区域更新事件
-const handleSubregionUpdate = (item: SubRegions[]) => {
+const handleSubregionUpdate = (item: LocalSubRegionItem[]) => {
   // 可以在这里添加更新后的逻辑
-  const hasBed = localFurniture.value.some((furniture) => furniture.type === 'bed')
+  const hasBed = roomStore.localFurnitureItems.some((furniture) => furniture.type === 'bed')
   console.log('子区域更新', item, hasBed)
   if (hasBed) {
-    localSubRegions.value = item.map((region, index) => ({
+    roomStore.localSubRegions = item.map((region, index) => ({
       ...region,
       isBed: index === 0,
     }))
   } else {
-    localSubRegions.value = item
+    roomStore.localSubRegions = item
   }
-  // emit('update:subRegions', item)
 }
 
 const showPanel = ref(true)
@@ -560,7 +560,7 @@ const furnitureActiveKey = ref<number[]>([])
 
 const deleteBlockArea = (id: string) => {
   if (id) {
-    localSubRegions.value = localSubRegions.value.filter((item) => item.id !== id)
+    roomStore.localSubRegions = roomStore.localSubRegions.filter((item) => item.nanoid !== id)
   }
 }
 
@@ -574,7 +574,7 @@ const modeRadioChange = () => {
 // }
 
 const clearSubregions = () => {
-  localSubRegions.value = []
+  roomStore.localSubRegions = []
 }
 
 const clearFurniture = () => {

+ 17 - 2
src/stores/room.ts

@@ -7,6 +7,7 @@ import type {
   SubRegionItem,
   LocalSubRegionItem,
 } from '@/api/room/types'
+import { convert_region_c2r, type RadarPosition } from '@/utils/coordTransform'
 
 export const useRoomStore = defineStore(
   'room',
@@ -24,7 +25,7 @@ export const useRoomStore = defineStore(
      * 缓存接口中的家具列表
      * @param items - 家具列表
      */
-    function cacheFurniture(items: FurnitureItem[]) {
+    function cacheFurniture(items: FurnitureItem & LocalFurnitureItem[]) {
       // 过滤掉雷达家具
       const furniture = items.filter((item) => item.type !== 'radar')
       furnitureItems.value = furniture
@@ -38,7 +39,7 @@ export const useRoomStore = defineStore(
      * 缓存接口中的子区域列表
      * @param items - 子区域列表
      */
-    function cacheSubRegion(items: SubRegionItem[]) {
+    function cacheSubRegion(items: SubRegionItem & LocalSubRegionItem[]) {
       subRegionItems.value = items
       localSubRegions.value = items.map((item) => ({
         ...item,
@@ -59,6 +60,19 @@ export const useRoomStore = defineStore(
 
     // 子区域列表坐标转换    雷达坐标系 --> 画布坐标系
     // 子区域列表坐标转换    画布坐标系 --> 雷达坐标系
+    function convertSubRegionC2R(item: LocalSubRegionItem, pRadar: RadarPosition) {
+      const p_radar = {
+        x_radar: pRadar.x_radar,
+        y_radar: pRadar.y_radar,
+      }
+      const dst_rect = {
+        left: item.pixelX,
+        top: item.pixelY,
+        width: item.pixelWidth,
+        height: item.pixelHeight,
+      }
+      return convert_region_c2r(dst_rect, p_radar)
+    }
 
     return {
       furnitureItems,
@@ -67,6 +81,7 @@ export const useRoomStore = defineStore(
       localSubRegions,
       cacheFurniture,
       cacheSubRegion,
+      convertSubRegionC2R,
     }
   },
   {

+ 33 - 16
src/views/device/detail/components/deviceAreaConfig/index.vue

@@ -114,6 +114,8 @@ const fetchRoomLayout = async () => {
   } catch (error) {
     console.error('❌获取房间布局信息失败', error)
     spinning.value = false
+    roomStore.cacheFurniture([])
+    roomStore.cacheSubRegion([])
   }
 }
 fetchRoomLayout()
@@ -130,26 +132,41 @@ const saveAllConfig = () => {
     subRegions: roomStore.localSubRegions,
   })
 
+  const furnitureItems = roomStore.localFurnitureItems.map((item) => {
+    return {
+      name: item.name,
+      type: item.type,
+      width: item.width,
+      length: item.length,
+      rotate: item.rotate as 0 | 90 | 180 | 270,
+      x: item?.x || 0,
+      y: item?.y || 0,
+    }
+  })
+
+  const subRegions = roomStore.localSubRegions.map((item) => {
+    return {
+      startXx: item.startXx,
+      stopXx: item.stopXx,
+      startYy: item.startYy,
+      stopYy: item.stopYy,
+      startZz: item.startZz,
+      stopZz: item.stopZz,
+      isLowSnr: Number(item.isBed),
+      isDoor: item.isDoor,
+      presenceEnterDuration: item.presenceEnterDuration,
+      presenceExitDuration: item.presenceExitDuration,
+      trackPresence: Number(item.isTracking),
+      excludeFalling: Number(item.isFalling),
+    }
+  })
+
   try {
     const res = roomApi.saveRoomInfo({
       roomId: deviceRoomId.value,
       devId: props.devId,
-      // furnitures: mapCanvasList.value
-      //   .filter((item) => item.type !== 'radar')
-      //   .map((item) => {
-      //     return {
-      //       name: item.name,
-      //       type: item.type as FurnitureType,
-      //       width: item.width,
-      //       length: item.height || (item.length as number),
-      //       top: item.top,
-      //       left: item.left,
-      //       rotate: item.rotate as 0 | 90 | 180 | 270,
-      //       x: item?.x || 0,
-      //       y: item?.y || 0,
-      //     }
-      //   }),
-      // subRegions: blockData,
+      furnitures: furnitureItems,
+      subRegions,
     })
     console.log('保存所有配置 ✅', res)
     message.success('保存成功')

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

@@ -318,6 +318,8 @@ const fetchRoomLayout = async () => {
     deviceRoomId.value = roomId ?? ''
   } catch (error) {
     console.error('❌获取房间布局信息失败', error)
+    roomStore.cacheFurniture([])
+    roomStore.cacheSubRegion([])
   }
 }
 fetchRoomLayout()