Quellcode durchsuchen

feat(雷达编辑器): 增强子区域编辑功能并修复坐标计算问题

添加子区域属性面板支持实时编辑区域参数
修复子区域坐标计算错误,确保水平/垂直方向正确对应
增加子区域缓存功能,使用localStorage暂存编辑状态
优化子区域UI交互,添加删除区域功能
更新类型定义以支持新增功能
liujia vor 3 Wochen
Ursprung
Commit
c20f665d93

+ 2 - 0
components.d.ts

@@ -15,6 +15,7 @@ declare module 'vue' {
     ACascader: typeof import('ant-design-vue/es')['Cascader']
     ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
     ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
+    ACol: typeof import('ant-design-vue/es')['Col']
     ACollapse: typeof import('ant-design-vue/es')['Collapse']
     ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
     AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
@@ -45,6 +46,7 @@ declare module 'vue' {
     ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
     ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
     AResult: typeof import('ant-design-vue/es')['Result']
+    ARow: typeof import('ant-design-vue/es')['Row']
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
     ASkeleton: typeof import('ant-design-vue/es')['Skeleton']

+ 4 - 0
src/api/room/types.ts

@@ -49,6 +49,10 @@ export interface SubRegions {
   presenceExitDuration: number
   trackPresence: number // 是否开启区域跟踪存在 0-否,1-是
   excludeFalling: number // 是否屏蔽区域跌倒检测 0-否,1-是
+  isBed?: boolean
+  id?: string
+  isTracking?: boolean
+  isFalling?: boolean
 }
 
 export interface RoomData {

+ 150 - 137
src/components/EditableSubregion/index.vue

@@ -14,10 +14,10 @@
         v-if="currentBlock"
         class="temp-block"
         :style="{
-          left: `${Math.min(currentBlock.startX, currentBlock.currentX)}px`,
-          top: `${Math.min(currentBlock.startY, currentBlock.currentY)}px`,
-          width: `${Math.abs(currentBlock.currentX - currentBlock.startX)}px`,
-          height: `${Math.abs(currentBlock.currentY - currentBlock.startY)}px`,
+          left: `${Math.min(currentBlock.startY, currentBlock.currentY)}px`,
+          top: `${Math.min(currentBlock.startX, currentBlock.currentX)}px`,
+          width: `${Math.abs(currentBlock.currentY - currentBlock.startY)}px`,
+          height: `${Math.abs(currentBlock.currentX - currentBlock.startX)}px`,
         }"
       ></div>
 
@@ -27,8 +27,8 @@
         :key="block.id"
         class="block-item"
         :style="{
-          left: `${block.x}px`,
-          top: `${block.y}px`,
+          left: `${block.y}px`,
+          top: `${block.x}px`,
           width: `${block.width}px`,
           height: `${block.height}px`,
           border: `2px solid ${block?.isBed ? '#1abc1a' : block.isActice ? 'yellow' : '#1890ff'}`,
@@ -51,7 +51,7 @@
       </div>
     </div>
 
-    <div v-if="selectedBlock" class="mapConfig">
+    <!-- <div v-if="selectedBlock" class="mapConfig">
       <div class="mapConfig-header">
         <span class="title">子区域属性</span>
         <span class="close" @click="closeSubregionAttr"><CloseOutlined /></span>
@@ -146,7 +146,9 @@
           <DeleteOutlined @click="deleteBlockArea(selectedBlock.id || '')" />
         </div>
       </div>
-    </div>
+
+      <pre>{{ selectedBlock }}</pre>
+    </div> -->
   </div>
 </template>
 
@@ -154,7 +156,7 @@
 import { ref, computed, watch } from 'vue'
 import { message } from 'ant-design-vue'
 import { nanoid } from 'nanoid'
-import { DeleteOutlined, CloseOutlined } from '@ant-design/icons-vue'
+// import { DeleteOutlined, CloseOutlined } from '@ant-design/icons-vue'
 import { getOriginPosition } from '@/utils'
 import type { SubRegions } from '@/api/room/types'
 
@@ -166,28 +168,29 @@ interface Props {
   length: number // 区域宽度
   subRegions?: SubRegions[]
   editable?: boolean
+  hasBed?: boolean
 }
 
 interface BlockItem {
   // 本地用
   id: string // 唯一标识
-  x: number // 区块基于父元素的X偏移量,区块的左上角x坐标
-  y: number // 区块基于父元素的Y偏移量,区块的左上角y坐标
-  ox: number // 区块基于原点的X偏移量,区块的左上角x坐标
-  oy: number // 区块基于原点的Y偏移量,区块的左上角y坐标
-  width: number // 区块宽度
-  height: number // 区块高度
+  x: number // 区块基于父元素的X偏移量(垂直方向,朝上为正)
+  y: number // 区块基于父元素的Y偏移量(水平方向,朝右为正)
+  ox: number // 区块基于原点的X偏移量
+  oy: number // 区块基于原点的Y偏移量
+  width: number // 区块宽度(水平方向)
+  height: number // 区块高度(垂直方向)
   isDragging: boolean // 是否正在拖动
   isResizing: boolean // 是否正在调整大小
   isActice: boolean // 是否选中
-  isTracking: boolean // 是否开启区域跟踪  0-否,1-是 对应 trackPresence 字段
-  isFalling: boolean // 是否屏蔽区域跌倒检测  0-否,1-是 对应 excludeFalling 字段
-  isBed?: boolean // 是否是床 本地判断使用
+  isTracking: boolean // 是否开启区域跟踪
+  isFalling: boolean // 是否屏蔽区域跌倒检测
+  isBed?: boolean // 是否是床
   // 接口用
-  startXx: number // 屏蔽子区域X开始
-  stopXx: number // 屏蔽子区域X结束
-  startYy: number // 屏蔽子区域Y开始
-  stopYy: number // 屏蔽子区域Y结束
+  startXx: number // 屏蔽子区域X开始(水平)
+  stopXx: number // 屏蔽子区域X结束(水平)
+  startYy: number // 屏蔽子区域Y开始(垂直)
+  stopYy: number // 屏蔽子区域Y结束(垂直)
   startZz: number // 屏蔽子区域Z开始
   stopZz: number // 屏蔽子区域Z结束
   isLowSnr: number // 是否为床  0-不是,1-是
@@ -208,7 +211,7 @@ const props = withDefaults(defineProps<Props>(), {
 const emit = defineEmits<{
   (e: 'update:subRegions', regions: SubRegions[]): void
   (e: 'create'): void
-  (e: 'update'): void
+  (e: 'update', regions: SubRegions[]): void
 }>()
 
 // 检测区域宽度 length
@@ -225,10 +228,10 @@ const editable = computed(() => props.editable)
 const blocks = ref<BlockItem[]>([])
 const isCreating = ref(false)
 const currentBlock = ref<{
-  startX: number
-  startY: number
-  currentX: number
-  currentY: number
+  startX: number // 垂直方向起点
+  startY: number // 水平方向起点
+  currentX: number // 垂直方向当前点
+  currentY: number // 水平方向当前点
 } | null>(null)
 const selectedBlock = ref<BlockItem | null>(null)
 
@@ -241,10 +244,10 @@ watch(
     if (newSubRegions && newSubRegions.length > 0) {
       blocks.value = newSubRegions.map((item, index) => ({
         id: nanoid(),
-        x: item.startXx + originX,
-        y: originY - item.startYy,
-        ox: item.startXx + originX - originX,
-        oy: originY - item.startYy - originY,
+        x: originY - Number(item.startYy), // x对应垂直方向
+        y: Number(item.startXx) + originX, // y对应水平方向
+        ox: originY - item.startYy - originY,
+        oy: item.startXx + originX - originX,
         width: Math.abs(item.stopXx - item.startXx),
         height: Math.abs(item.stopYy - item.startYy),
         isDragging: false,
@@ -252,7 +255,7 @@ watch(
         isActice: false,
         isTracking: Boolean(item.trackPresence),
         isFalling: Boolean(item.excludeFalling),
-        isBed: index === 0, // 简单判断,实际应该基于家具信息
+        isBed: index === 0 && props.hasBed,
         // 来自接口回显的数据
         startXx: item.startXx,
         stopXx: item.stopXx,
@@ -291,7 +294,8 @@ const updateSubRegionsData = () => {
       trackPresence: Number(item.isTracking),
       excludeFalling: Number(item.isFalling),
     }))
-    emit('update:subRegions', subRegionsData)
+    // emit('update:subRegions', subRegionsData)
+    emit('update', subRegionsData)
   }
 }
 
@@ -317,41 +321,44 @@ 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))
+  // currentX 对应垂直方向(y轴)
+  currentBlock.value.currentX = Math.max(0, Math.min(e.clientY - rect.top, rect.height))
+  // currentY 对应水平方向(x轴)
+  currentBlock.value.currentY = Math.max(0, Math.min(e.clientX - rect.left, rect.width))
 }
 
-// 修复鼠标释放处理中的坐标计算
+// 鼠标释放处理
 const handleMouseUp = () => {
   if (!currentBlock.value) return
 
   const { startX, startY, currentX, currentY } = currentBlock.value
-  const width = Math.abs(currentX - startX)
-  const height = Math.abs(currentY - startY)
+  // 宽度由水平方向(y)差值决定,高度由垂直方向(x)差值决定
+  const width = Math.abs(currentY - startY)
+  const height = Math.abs(currentX - startX)
 
   if (width > 10 && height > 10) {
-    const minX = Math.round(Math.min(startX, currentX))
-    const minY = Math.round(Math.min(startY, currentY))
+    const minX = Math.round(Math.min(startX, currentX)) // 垂直方向最小坐标
+    const minY = Math.round(Math.min(startY, currentY)) // 水平方向最小坐标
 
     blocks.value.push({
       // 本地用
       id: nanoid(),
-      x: minX,
-      y: minY,
-      ox: minX - originX,
-      oy: originY - minY, // 注意这里是originY - minY
-      width,
-      height,
+      x: minX, // 垂直方向位置
+      y: minY, // 水平方向位置
+      ox: minX - originY,
+      oy: minY - originX,
+      width, // 水平宽度
+      height, // 垂直高度
       isDragging: false,
       isResizing: false,
       isActice: false,
       isTracking: false,
       isFalling: false,
       // 接口用
-      startXx: minX - originX,
-      stopXx: minX - originX + width,
-      startYy: originY - minY, // 注意这里是originY - minY
-      stopYy: originY - minY - height, // 注意这里是originY - minY - height
+      startXx: minY - originX,
+      stopXx: minY - originX + width,
+      startYy: originY - minX,
+      stopYy: originY - minX - height,
       startZz: 0,
       stopZz: 0,
       isLowSnr: 0,
@@ -362,7 +369,6 @@ const handleMouseUp = () => {
       excludeFalling: 0,
     })
     emit('create')
-    // 新创建区块后也调用手动更新函数
     updateSubRegionsData()
   }
 
@@ -372,7 +378,7 @@ const handleMouseUp = () => {
   document.removeEventListener('mouseup', handleMouseUp)
 }
 
-// 修复区块拖动中的坐标计算
+// 区块拖动
 const startDrag = (block: BlockItem, e: MouseEvent) => {
   if (!editable.value) return
   e.stopPropagation()
@@ -386,22 +392,26 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
   block.isActice = true
   const container = document.querySelector('.blockArea') as HTMLElement
   const rect = container.getBoundingClientRect()
-  const offsetX = e.clientX - rect.left - block.x
-  const offsetY = e.clientY - rect.top - block.y
+  // 垂直方向偏移(x轴)基于clientY,水平方向偏移(y轴)基于clientX
+  const offsetX = e.clientY - rect.top - block.x
+  const offsetY = e.clientX - rect.left - block.y
 
   const initialOx = block.ox
   const initialOy = block.oy
 
   const moveHandler = (e: MouseEvent) => {
-    const newX = e.clientX - rect.left - offsetX
-    const newY = e.clientY - rect.top - offsetY
+    // 新垂直位置(x轴)= 鼠标垂直位置 - 容器顶部 - 偏移量
+    const newX = e.clientY - rect.top - offsetX
+    // 新水平位置(y轴)= 鼠标水平位置 - 容器左侧 - 偏移量
+    const newY = e.clientX - rect.left - offsetY
     const containerWidth = container.offsetWidth
     const containerHeight = container.offsetHeight
 
-    block.x = Math.max(0, Math.min(newX, containerWidth - block.width))
-    block.y = Math.max(0, Math.min(newY, containerHeight - block.height))
-    block.ox = block.x - originX
-    block.oy = originY - block.y // 注意这里是originY - block.y
+    // 限制边界:x(垂直)不超过容器高度,y(水平)不超过容器宽度
+    block.x = Math.max(0, Math.min(newX, containerHeight - block.height))
+    block.y = Math.max(0, Math.min(newY, containerWidth - block.width))
+    block.ox = block.x - originY
+    block.oy = block.y - originX
   }
 
   const upHandler = () => {
@@ -409,10 +419,10 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
     block.isActice = false
 
     if (block.ox !== initialOx || block.oy !== initialOy) {
-      block.startXx = block.ox
-      block.stopXx = block.ox + block.width
-      block.startYy = block.oy
-      block.stopYy = block.oy - block.height
+      block.startXx = block.oy
+      block.stopXx = block.oy + block.width
+      block.startYy = originY - block.x
+      block.stopYy = originY - block.x - block.height
 
       updateSubRegionsData()
     }
@@ -425,7 +435,7 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
   document.addEventListener('mouseup', upHandler)
 }
 
-// 修复区块调整大小中的坐标计算
+// 区块调整大小
 const startResize = (block: BlockItem, e: MouseEvent) => {
   if (!editable.value) return
   e.stopPropagation()
@@ -433,18 +443,18 @@ const startResize = (block: BlockItem, e: MouseEvent) => {
 
   block.isResizing = true
   selectedBlock.value = block
-  const startX = e.clientX
-  const startY = e.clientY
+  const startX = e.clientY // 垂直方向起点使用clientY
+  const startY = e.clientX // 水平方向起点使用clientX
   const initialWidth = block.width
   const initialHeight = block.height
 
   const moveHandler = (e: MouseEvent) => {
     const rect = getContainerRect()
-    const deltaX = e.clientX - startX
-    const deltaY = e.clientY - startY
+    const deltaX = e.clientY - startX // 垂直方向变化
+    const deltaY = e.clientX - startY // 水平方向变化
     // 限制最小尺寸和容器边界
-    block.width = Math.max(50, Math.min(initialWidth + deltaX, rect.width - block.x))
-    block.height = Math.max(50, Math.min(initialHeight + deltaY, rect.height - block.y))
+    block.width = Math.max(50, Math.min(initialWidth + deltaY, rect.width - block.y))
+    block.height = Math.max(50, Math.min(initialHeight + deltaX, rect.height - block.x))
   }
 
   const upHandler = () => {
@@ -452,11 +462,10 @@ const startResize = (block: BlockItem, e: MouseEvent) => {
     selectedBlock.value = null
 
     block.stopXx = block.startXx + block.width
-    block.stopYy = block.startYy - block.height // 使用减号,与blockInputPressEnter中的逻辑保持一致
+    block.stopYy = block.startYy - block.height
 
     document.removeEventListener('mousemove', moveHandler)
     document.removeEventListener('mouseup', upHandler)
-    // 使用手动更新函数而不是emit('update')
     updateSubRegionsData()
   }
 
@@ -464,39 +473,41 @@ const startResize = (block: BlockItem, e: MouseEvent) => {
   document.addEventListener('mouseup', upHandler)
 }
 
-// 修复输入框回车事件中的坐标计算
-const blockInputPressEnter = (e: Event, el: BlockItem, attr: string) => {
-  if (!el) return
-  if (attr === 'startXx') {
-    el.startXx = Number(el[attr as keyof BlockItem])
-    el.x = el.startXx + originX
-  }
-  if (attr === 'stopXx') {
-    el.stopXx = Number(el[attr as keyof BlockItem])
-    el.width = el.stopXx - el.startXx
-  }
-
-  if (attr === 'startYy') {
-    el.startYy = Number(el[attr as keyof BlockItem])
-    el.y = originY - el.startYy // 修复这里,之前错误地设置到了x坐标
-  }
-  if (attr === 'stopYy') {
-    el.stopYy = Number(el[attr as keyof BlockItem])
-    el.height = el.startYy - el.stopYy // 修复这里,与整体坐标系统保持一致
-  }
-
-  // 输入框修改后也调用手动更新函数
-  updateSubRegionsData()
-}
-
-// 修复鼠标按下事件中的坐标计算
+// 输入框回车事件
+// const blockInputPressEnter = (e: Event, el: BlockItem, attr: string) => {
+//   if (!el) return
+//   // X范围(水平方向)对应y轴逻辑
+//   if (attr === 'startXx') {
+//     el.startXx = Number(el[attr as keyof BlockItem])
+//     el.y = el.startXx + originX // y控制水平位置
+//   }
+//   if (attr === 'stopXx') {
+//     el.stopXx = Number(el[attr as keyof BlockItem])
+//     el.width = el.stopXx - el.startXx // 宽度由X范围决定
+//   }
+
+//   // Y范围(垂直方向,朝上为正)对应x轴逻辑
+//   if (attr === 'startYy') {
+//     el.startYy = Number(el[attr as keyof BlockItem])
+//     el.x = originY - el.startYy // x控制垂直位置
+//   }
+//   if (attr === 'stopYy') {
+//     el.stopYy = Number(el[attr as keyof BlockItem])
+//     el.height = el.startYy - el.stopYy // 高度由Y范围决定(朝上为正,差值为正)
+//   }
+
+//   updateSubRegionsData()
+// }
+
+// 鼠标按下事件
 const handleMouseDown = (e: MouseEvent) => {
   if (!editable.value) return
   if (!isCreating.value) return
 
   const rect = getContainerRect()
-  const startX = e.clientX - rect.left
-  const startY = e.clientY - rect.top
+  // 交换x/y轴:startX 对应垂直方向(clientY),startY 对应水平方向(clientX)
+  const startX = e.clientY - rect.top // 垂直方向起点(x轴)
+  const startY = e.clientX - rect.left // 水平方向起点(y轴)
 
   currentBlock.value = {
     startX,
@@ -518,42 +529,44 @@ const selectBlock = (block: BlockItem) => {
 }
 
 // 关闭子区域属性
-const closeSubregionAttr = () => {
-  blocks.value.forEach((item) => {
-    item.isActice = false
-  })
-  selectedBlock.value = null
-}
-
-const blockInputBlur = (e: Event, el: BlockItem, attr: string) => {
-  if (!el) return
-  if (attr === 'startXx') {
-    el.startXx = Number(el[attr as keyof BlockItem])
-    el.x = el.startXx + originX
-  }
-  if (attr === 'stopXx') {
-    el.stopXx = Number(el[attr as keyof BlockItem])
-    el.width = el.stopXx - el.startXx
-  }
-
-  if (attr === 'startYy') {
-    el.startYy = Number(el[attr as keyof BlockItem])
-    el.x = el.startYy + originY
-  }
-  if (attr === 'stopYy') {
-    el.stopYy = Number(el[attr as keyof BlockItem])
-    el.height = el.stopYy - el.startYy
-  }
-  emit('update')
-}
-
-const deleteBlockArea = (id: string) => {
-  if (id) {
-    blocks.value = blocks.value.filter((item) => item.id !== id)
-    selectedBlock.value = null
-    emit('update')
-  }
-}
+// const closeSubregionAttr = () => {
+//   blocks.value.forEach((item) => {
+//     item.isActice = false
+//   })
+//   selectedBlock.value = null
+// }
+
+// const blockInputBlur = (e: Event, el: BlockItem, attr: string) => {
+//   if (!el) return
+//   // X范围(水平方向)
+//   if (attr === 'startXx') {
+//     el.startXx = Number(el[attr as keyof BlockItem])
+//     el.y = el.startXx + originX
+//   }
+//   if (attr === 'stopXx') {
+//     el.stopXx = Number(el[attr as keyof BlockItem])
+//     el.width = el.stopXx - el.startXx
+//   }
+
+//   // Y范围(垂直方向)
+//   if (attr === 'startYy') {
+//     el.startYy = Number(el[attr as keyof BlockItem])
+//     el.x = originY - el.startYy
+//   }
+//   if (attr === 'stopYy') {
+//     el.stopYy = Number(el[attr as keyof BlockItem])
+//     el.height = el.startYy - el.stopYy
+//   }
+//   updateSubRegionsData()
+// }
+
+// const deleteBlockArea = (id: string) => {
+//   if (id) {
+//     blocks.value = blocks.value.filter((item) => item.id !== id)
+//     selectedBlock.value = null
+//     updateSubRegionsData()
+//   }
+// }
 </script>
 
 <style scoped lang="less">

+ 181 - 8
src/components/RadarEditor/index.vue

@@ -18,10 +18,12 @@
           v-if="modeRadio === 2"
           ref="editableSubregionRef"
           :ranges="coordinates"
+          :angle="angle"
           :width="areaHeight"
           :length="areaWidth"
-          v-model:subRegions="localSubRegions"
+          :subRegions="localSubRegions"
           :editable="!disabled"
+          :has-bed="localFurniture.some((item) => item.type === 'bed')"
           @create="handleSubregionCreate"
           @update="handleSubregionUpdate"
         />
@@ -31,9 +33,14 @@
     <div v-if="!disabled && showPanel" class="options">
       <div class="close" @click="showPanel = false">×</div>
       <div class="header">
-        <a-radio-group v-model:value="modeRadio" button-style="solid" size="small">
+        <a-radio-group
+          v-model:value="modeRadio"
+          button-style="solid"
+          size="small"
+          @change="modeRadioChange"
+        >
           <a-radio-button :value="1">家具</a-radio-button>
-          <a-radio-button :value="2">子区域</a-radio-button>
+          <a-radio-button :value="2">子区域beta</a-radio-button>
           <!-- <a-radio-button :value="3">信息面板</a-radio-button> -->
         </a-radio-group>
       </div>
@@ -60,7 +67,94 @@
 
         <div class="subregion-list">
           <div v-for="(region, index) in localSubRegions" :key="index" class="list-item">
-            区域{{ index + 1 }} {{ region.isLowSnr ? '包含床' : '' }}
+            <a-collapse v-model:activeKey="activeKey" ghost>
+              <a-collapse-panel :key="index + 2" :header="`子区域 ${index + 1} 属性`">
+                <div class="mapConfig">
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">X范围:</div>
+                    <div class="mapConfig-item-content">
+                      <a-space>
+                        <a-input
+                          v-model:value.trim="region.startXx"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+
+                        <a-input
+                          v-model:value.trim="region.stopXx"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+                      </a-space>
+                    </div>
+                  </div>
+
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">Y范围:</div>
+                    <div class="mapConfig-item-content">
+                      <a-space>
+                        <a-input
+                          v-model:value.trim="region.startYy"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+
+                        <a-input
+                          v-model:value.trim="region.stopYy"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+                      </a-space>
+                    </div>
+                  </div>
+
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">Z范围:</div>
+                    <div class="mapConfig-item-content">
+                      <a-space>
+                        <a-input
+                          v-model:value.trim="region.startZz"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+
+                        <a-input
+                          v-model:value.trim="region.stopZz"
+                          :style="{ width: '50px' }"
+                          size="small"
+                        />
+                      </a-space>
+                    </div>
+                  </div>
+
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">区域跟踪:</div>
+                    <div class="mapConfig-item-content">
+                      <a-switch v-model:checked="region.isTracking" size="small" />
+                    </div>
+                  </div>
+
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">区域跌倒:</div>
+                    <div class="mapConfig-item-content">
+                      <a-switch v-model:checked="region.isFalling" size="small" />
+                    </div>
+                  </div>
+
+                  <div v-if="region.isBed" class="mapConfig-item">
+                    <div class="mapConfig-item-label">呼吸检测:</div>
+                    <div class="mapConfig-item-content"> 默认开启 </div>
+                  </div>
+
+                  <div class="mapConfig-item">
+                    <div class="mapConfig-item-label">删除区域:</div>
+                    <div class="mapConfig-item-content">
+                      <DeleteOutlined @click="deleteBlockArea(region.id || '')" />
+                    </div>
+                  </div>
+                </div>
+              </a-collapse-panel>
+            </a-collapse>
           </div>
           <div v-if="localSubRegions.length === 0" class="list-item empty">
             暂无区域,请点击"新建区域"开始创建
@@ -76,7 +170,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, computed } from 'vue'
+import { ref, watch, computed, onUnmounted } from 'vue'
 import type { FurnitureItem } from '@/types/radar'
 import RadarView from '../RadarView/index.vue'
 import EditableFurniture from '../EditableFurniture/index.vue'
@@ -87,6 +181,7 @@ import { nanoid } from 'nanoid'
 import type { FurnitureType } from '@/api/room/types'
 import { furnitureIconNameMap, furnitureIconSizeMap } from '@/const/furniture'
 import type { SubRegions } from '@/api/room/types'
+import { DeleteOutlined } from '@ant-design/icons-vue'
 
 defineOptions({ name: 'RadarEditor' })
 
@@ -137,7 +232,10 @@ watch(
 watch(
   localSubRegions,
   (newRegions) => {
-    emit('update:subRegions', newRegions)
+    // emit('update:subRegions', newRegions)
+    console.log('子区域变化', newRegions)
+    // 缓存起来
+    localStorage.setItem('subRegions', JSON.stringify(newRegions))
   },
   { deep: true }
 )
@@ -230,11 +328,41 @@ const handleSubregionCreate = () => {
 }
 
 // 处理子区域更新事件
-const handleSubregionUpdate = () => {
+const handleSubregionUpdate = (item: SubRegions[]) => {
   // 可以在这里添加更新后的逻辑
+  const hasBed = localFurniture.value.some((furniture) => furniture.type === 'bed')
+  console.log('子区域更新', item, hasBed)
+  if (hasBed) {
+    localSubRegions.value = item.map((region, index) => ({
+      ...region,
+      isBed: index === 0,
+    }))
+  } else {
+    localSubRegions.value = item
+  }
+  // emit('update:subRegions', item)
 }
 
 const showPanel = ref(true)
+
+onUnmounted(() => {
+  // 组件销毁时清除缓存
+  localStorage.removeItem('subRegions')
+})
+
+const activeKey = ref<number[]>([0])
+
+const deleteBlockArea = (id: string) => {
+  if (id) {
+    localSubRegions.value = localSubRegions.value.filter((item) => item.id !== id)
+  }
+}
+
+const modeRadioChange = (e: Event) => {
+  const value = (e.target as HTMLInputElement).value as unknown as 1 | 2 | 3
+  console.log(11111111, value)
+  activeKey.value = [0]
+}
 </script>
 
 <style scoped lang="less">
@@ -314,7 +442,7 @@ const showPanel = ref(true)
           display: flex;
           align-items: center;
           justify-content: space-between;
-          padding: 8px 12px;
+          // padding: 8px 12px;
           border-radius: 8px;
           background-color: #f5f5f5;
           cursor: pointer;
@@ -327,4 +455,49 @@ const showPanel = ref(true)
     }
   }
 }
+
+.mapConfig {
+  background-color: #f5f5f5;
+  border-radius: 10px;
+  padding: 12px;
+  // min-width: 200px;
+  &-header {
+    margin-bottom: 10px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .title {
+      font-size: 14px;
+      font-weight: 600;
+      line-height: 24px;
+    }
+    .close {
+      font-size: 14px;
+      color: #666;
+      cursor: pointer;
+      position: relative;
+      top: -5px;
+    }
+  }
+
+  &-item {
+    display: flex;
+    line-height: 30px;
+    &-label {
+      color: #888;
+      min-width: 55px;
+    }
+    &-content {
+      color: #555;
+      // min-width: 100px;
+    }
+  }
+}
+:deep(.ant-collapse .ant-collapse-content > .ant-collapse-content-box) {
+  padding: 0;
+}
+:deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header) {
+  padding: 5px;
+  user-select: none;
+}
 </style>

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

@@ -47,6 +47,7 @@
         :coordinates="props.ranges"
         :angle="props.angle"
         :furnitureItems="furnitureItems"
+        :subRegions="blocks"
         :disabled="!isEditDraggable"
         @update:furnitureItems="mapCanvasList = $event"
       />
@@ -708,22 +709,41 @@ const initRadarIcon = () => {
 // 保存所有配置
 const saveAllConfig = () => {
   console.log('保存所有配置')
-  const blockData = blocks.value.map((item) => {
-    return {
-      startXx: item.startXx,
-      stopXx: item.stopXx,
-      startYy: item.startYy,
-      stopYy: item.stopYy,
-      startZz: Number(item.startZz) || 0,
-      stopZz: Number(item.stopZz) || 0,
-      isLowSnr: item.isLowSnr,
-      isDoor: item.isDoor,
-      presenceEnterDuration: item.presenceEnterDuration,
-      presenceExitDuration: item.presenceExitDuration,
-      trackPresence: Number(item.isTracking),
-      excludeFalling: Number(item.isFalling),
-    }
-  })
+  // const blockData = blocks.value.map((item) => {
+  //   return {
+  //     startXx: item.startXx,
+  //     stopXx: item.stopXx,
+  //     startYy: item.startYy,
+  //     stopYy: item.stopYy,
+  //     startZz: Number(item.startZz) || 0,
+  //     stopZz: Number(item.stopZz) || 0,
+  //     isLowSnr: item.isLowSnr,
+  //     isDoor: item.isDoor,
+  //     presenceEnterDuration: item.presenceEnterDuration,
+  //     presenceExitDuration: item.presenceExitDuration,
+  //     trackPresence: Number(item.isTracking),
+  //     excludeFalling: Number(item.isFalling),
+  //   }
+  // })
+
+  const blockData =
+    Array.isArray(JSON.parse(localStorage.getItem('subRegions') ?? '[]')) &&
+    JSON.parse(localStorage.getItem('subRegions') ?? '[]').map((item) => {
+      return {
+        startXx: item.startXx,
+        stopXx: item.stopXx,
+        startYy: item.startYy,
+        stopYy: item.stopYy,
+        startZz: Number(item.startZz) || 0,
+        stopZz: Number(item.stopZz) || 0,
+        isLowSnr: item.isLowSnr,
+        isDoor: item.isDoor,
+        presenceEnterDuration: item.presenceEnterDuration,
+        presenceExitDuration: item.presenceExitDuration,
+        trackPresence: Number(item.isTracking),
+        excludeFalling: Number(item.isFalling),
+      }
+    })
   console.log('当前所有区块配置:', blockData)
   try {
     const res = roomApi.saveRoomInfo({