Răsfoiți Sursa

feat: 新增屏蔽子区域组件;

liujia 3 săptămâni în urmă
părinte
comite
b8f87a7

+ 1 - 2
components.d.ts

@@ -47,7 +47,6 @@ declare module 'vue' {
     AResult: typeof import('ant-design-vue/es')['Result']
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
-    ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
     ASpace: typeof import('ant-design-vue/es')['Space']
     ASpin: typeof import('ant-design-vue/es')['Spin']
     ASteps: typeof import('ant-design-vue/es')['Steps']
@@ -57,7 +56,6 @@ declare module 'vue' {
     ATag: typeof import('ant-design-vue/es')['Tag']
     ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker']
     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']
@@ -68,6 +66,7 @@ declare module 'vue' {
     Copyright: typeof import('./src/components/Copyright/index.vue')['default']
     ECharts: typeof import('./src/components/baseCard/components/e-charts/index.vue')['default']
     EditableFurniture: typeof import('./src/components/EditableFurniture/index.vue')['default']
+    EditableSubregion: typeof import('./src/components/EditableSubregion/index.vue')['default']
     FurnitureIcon: typeof import('./src/components/furnitureIcon/index.vue')['default']
     FurnitureItem: typeof import('./src/components/furnitureItem/index.vue')['default']
     FurnitureList: typeof import('./src/components/furnitureList/index.vue')['default']

+ 629 - 0
src/components/EditableSubregion/index.vue

@@ -0,0 +1,629 @@
+<template>
+  <div class="editable-subregion">
+    <div
+      class="mapBox blockArea"
+      :style="{
+        width: `${areaWidth}px`,
+        height: `${areaHeight}px`,
+        cursor: !editable ? 'no-drop' : isCreating ? 'crosshair' : 'default',
+      }"
+      @mousedown="handleMouseDown"
+    >
+      <!-- 绘制临时选区 -->
+      <div
+        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`,
+        }"
+      ></div>
+
+      <!-- 已创建区块 -->
+      <div
+        v-for="(block, blockIndex) in blocks"
+        :key="block.id"
+        class="block-item"
+        :style="{
+          left: `${block.x}px`,
+          top: `${block.y}px`,
+          width: `${block.width}px`,
+          height: `${block.height}px`,
+          border: `2px solid ${block?.isBed ? '#1abc1a' : block.isActice ? 'yellow' : '#1890ff'}`,
+          position: 'absolute',
+          cursor: !editable ? 'no-drop' : 'move',
+          backgroundColor: block.isBed ? 'rgba(26, 188, 26, 0.1)' : 'rgba(24, 144, 255, 0.1)',
+        }"
+        @mousedown="startDrag(block, $event)"
+        @click="selectBlock(block)"
+      >
+        <div
+          class="resize-handle"
+          :style="{
+            backgroundColor: block.isBed ? '#1abc1a' : '#1890ff',
+          }"
+          @mousedown.stop="startResize(block, $event)"
+        >
+          {{ blockIndex + 1 }}
+        </div>
+      </div>
+    </div>
+
+    <div v-if="selectedBlock" class="mapConfig">
+      <div class="mapConfig-header">
+        <span class="title">子区域属性</span>
+        <span class="close" @click="closeSubregionAttr"><CloseOutlined /></span>
+      </div>
+      <div class="mapConfig-item">
+        <div class="mapConfig-item-label">X范围:</div>
+        <div class="mapConfig-item-content">
+          <a-space>
+            <a-input
+              v-model:value.trim="selectedBlock.startXx"
+              :style="{ width: '50px' }"
+              size="small"
+              @pressEnter="blockInputPressEnter($event, selectedBlock, 'startXx')"
+              @blur="blockInputBlur($event, selectedBlock, 'startXx')"
+            />
+
+            <a-input
+              v-model:value.trim="selectedBlock.stopXx"
+              :style="{ width: '50px' }"
+              size="small"
+              @pressEnter="blockInputPressEnter($event, selectedBlock, 'stopXx')"
+              @blur="blockInputBlur($event, selectedBlock, 'stopXx')"
+            />
+          </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="selectedBlock.startYy"
+              :style="{ width: '50px' }"
+              size="small"
+              @pressEnter="blockInputPressEnter($event, selectedBlock, 'startYy')"
+              @blur="blockInputBlur($event, selectedBlock, 'startYy')"
+            />
+
+            <a-input
+              v-model:value.trim="selectedBlock.stopYy"
+              :style="{ width: '50px' }"
+              size="small"
+              @pressEnter="blockInputPressEnter($event, selectedBlock, 'stopYy')"
+              @blur="blockInputBlur($event, selectedBlock, 'stopYy')"
+            />
+          </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="selectedBlock.startZz"
+              :style="{ width: '50px' }"
+              size="small"
+            />
+
+            <a-input
+              v-model:value.trim="selectedBlock.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="selectedBlock.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="selectedBlock.isFalling" size="small" />
+        </div>
+      </div>
+
+      <div v-if="selectedBlock.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(selectedBlock.id || '')" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+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 { getOriginPosition } from '@/utils'
+import type { SubRegions } from '@/api/room/types'
+
+defineOptions({ name: 'EditableSubregion' })
+
+interface Props {
+  ranges: [number, number, number, number] // 区域范围
+  width: number // 区域高度
+  length: number // 区域宽度
+  subRegions?: SubRegions[]
+  editable?: 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 // 区块高度
+  isDragging: boolean // 是否正在拖动
+  isResizing: boolean // 是否正在调整大小
+  isActice: boolean // 是否选中
+  isTracking: boolean // 是否开启区域跟踪  0-否,1-是 对应 trackPresence 字段
+  isFalling: boolean // 是否屏蔽区域跌倒检测  0-否,1-是 对应 excludeFalling 字段
+  isBed?: boolean // 是否是床 本地判断使用
+  // 接口用
+  startXx: number // 屏蔽子区域X开始
+  stopXx: number // 屏蔽子区域X结束
+  startYy: number // 屏蔽子区域Y开始
+  stopYy: number // 屏蔽子区域Y结束
+  startZz: number // 屏蔽子区域Z开始
+  stopZz: number // 屏蔽子区域Z结束
+  isLowSnr: number // 是否为床  0-不是,1-是
+  isDoor: number // 是否是门 0-否,1-是 默认0
+  presenceEnterDuration: number // 人员进入时间 默认3
+  presenceExitDuration: number // 人员离开时间 默认3
+  trackPresence: number // 是否开启区域跟踪存在 0-否,1-是
+  excludeFalling: number // 是否屏蔽区域跌倒检测 0-否,1-是
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  ranges: () => [Infinity, Infinity, Infinity, Infinity],
+  width: 0,
+  length: 0,
+  editable: false,
+})
+
+const emit = defineEmits<{
+  (e: 'update:subRegions', regions: SubRegions[]): void
+  (e: 'create'): void
+  (e: 'update'): void
+}>()
+
+// 检测区域宽度 length
+const areaWidth = computed(() => {
+  return Math.abs(props.length)
+})
+// 检测区域高度 width
+const areaHeight = computed(() => {
+  return Math.abs(props.width)
+})
+
+const editable = computed(() => props.editable)
+
+const blocks = ref<BlockItem[]>([])
+const isCreating = ref(false)
+const currentBlock = ref<{
+  startX: number
+  startY: number
+  currentX: number
+  currentY: number
+} | null>(null)
+const selectedBlock = ref<BlockItem | null>(null)
+
+const { originX, originY } = getOriginPosition(props.ranges, [0, 0])
+
+// 监听subRegions变化,更新blocks
+watch(
+  () => props.subRegions,
+  (newSubRegions) => {
+    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,
+        width: Math.abs(item.stopXx - item.startXx),
+        height: Math.abs(item.stopYy - item.startYy),
+        isDragging: false,
+        isResizing: false,
+        isActice: false,
+        isTracking: Boolean(item.trackPresence),
+        isFalling: Boolean(item.excludeFalling),
+        isBed: index === 0, // 简单判断,实际应该基于家具信息
+        // 来自接口回显的数据
+        startXx: item.startXx,
+        stopXx: item.stopXx,
+        startYy: item.startYy,
+        stopYy: item.stopYy,
+        startZz: item.startZz,
+        stopZz: item.stopZz,
+        isLowSnr: item.isLowSnr,
+        isDoor: item.isDoor,
+        presenceEnterDuration: item.presenceEnterDuration,
+        presenceExitDuration: item.presenceExitDuration,
+        trackPresence: item.trackPresence,
+        excludeFalling: item.excludeFalling,
+      }))
+    } else {
+      blocks.value = []
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+// 手动触发子区域数据更新的函数
+const updateSubRegionsData = () => {
+  if (blocks.value) {
+    const subRegionsData = blocks.value.map((item) => ({
+      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),
+    }))
+    emit('update:subRegions', subRegionsData)
+  }
+}
+
+// 新建区块处理
+const createNewBlock = () => {
+  if (blocks.value && blocks.value.length > 5) {
+    message.warn('最多只能创建6个区块')
+    return
+  }
+  isCreating.value = true
+}
+
+defineExpose({ createNewBlock })
+
+// 获取容器边界
+const getContainerRect = () => {
+  const container = document.querySelector('.blockArea') as HTMLElement
+  return container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 }
+}
+
+// 鼠标移动处理
+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 { 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.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,
+      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
+      startZz: 0,
+      stopZz: 0,
+      isLowSnr: 0,
+      isDoor: 0,
+      presenceEnterDuration: 3,
+      presenceExitDuration: 3,
+      trackPresence: 0,
+      excludeFalling: 0,
+    })
+    emit('create')
+    // 新创建区块后也调用手动更新函数
+    updateSubRegionsData()
+  }
+
+  currentBlock.value = null
+  isCreating.value = false
+  document.removeEventListener('mousemove', handleMouseMove)
+  document.removeEventListener('mouseup', handleMouseUp)
+}
+
+// 修复区块拖动中的坐标计算
+const startDrag = (block: BlockItem, e: MouseEvent) => {
+  if (!editable.value) return
+  e.stopPropagation()
+
+  // 取消选中其他区块
+  blocks.value.forEach((b) => {
+    b.isActice = false
+  })
+
+  block.isDragging = true
+  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
+
+  const moveHandler = (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.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
+  }
+
+  const upHandler = () => {
+    block.isDragging = false
+    block.isActice = false
+
+    // 只在拖动结束时更新这些属性
+    block.startXx = block.ox
+    block.stopXx = block.ox + block.width
+    block.startYy = block.oy
+    block.stopYy = block.oy - block.height
+
+    document.removeEventListener('mousemove', moveHandler)
+    document.removeEventListener('mouseup', upHandler)
+    // 使用手动更新函数而不是emit('update')
+    updateSubRegionsData()
+  }
+
+  document.addEventListener('mousemove', moveHandler)
+  document.addEventListener('mouseup', upHandler)
+}
+
+// 修复区块调整大小中的坐标计算
+const startResize = (block: BlockItem, e: MouseEvent) => {
+  if (!editable.value) return
+  e.stopPropagation()
+  e.preventDefault()
+
+  block.isResizing = true
+  selectedBlock.value = block
+  const startX = e.clientX
+  const startY = e.clientY
+  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
+    // 限制最小尺寸和容器边界
+    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))
+  }
+
+  const upHandler = () => {
+    block.isResizing = false
+    selectedBlock.value = null
+
+    // 只在调整大小结束时更新这些属性
+    block.stopXx = block.ox + block.width
+    block.stopYy = block.oy - block.height
+
+    document.removeEventListener('mousemove', moveHandler)
+    document.removeEventListener('mouseup', upHandler)
+    // 使用手动更新函数而不是emit('update')
+    updateSubRegionsData()
+  }
+
+  document.addEventListener('mousemove', moveHandler)
+  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 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
+
+  currentBlock.value = {
+    startX,
+    startY,
+    currentX: startX,
+    currentY: startY,
+  }
+
+  document.addEventListener('mousemove', handleMouseMove)
+  document.addEventListener('mouseup', handleMouseUp)
+}
+
+const selectBlock = (block: BlockItem) => {
+  if (!editable.value) return
+  selectedBlock.value = block
+  blocks.value.forEach((item) => {
+    item.isActice = item === block
+  })
+}
+
+// 关闭子区域属性
+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')
+  }
+}
+</script>
+
+<style scoped lang="less">
+.editable-subregion {
+  display: flex;
+  gap: 20px;
+}
+
+.mapBox {
+  position: relative;
+  flex-shrink: 0;
+}
+
+.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: 80px;
+    }
+    &-content {
+      color: #555;
+      min-width: 100px;
+    }
+  }
+}
+
+.temp-block {
+  position: absolute;
+  background: rgba(24, 144, 255, 0.2);
+  border: 2px dashed #1890ff;
+}
+
+.block-item {
+  background: rgba(24, 144, 255, 0.1);
+
+  .resize-handle {
+    position: absolute;
+    right: -4px;
+    bottom: -4px;
+    width: 15px;
+    height: 15px;
+    background: #1890ff;
+    cursor: nwse-resize;
+    font-size: 12px;
+    color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 81 - 12
src/components/RadarEditor/index.vue

@@ -13,7 +13,19 @@
         />
       </template>
 
-      <template #subregion> </template>
+      <template #subregion>
+        <EditableSubregion
+          v-if="modeRadio === 2"
+          ref="editableSubregionRef"
+          :ranges="coordinates"
+          :width="areaHeight"
+          :length="areaWidth"
+          v-model:subRegions="localSubRegions"
+          :editable="!disabled && showPanel"
+          @create="handleSubregionCreate"
+          @update="handleSubregionUpdate"
+        />
+      </template>
     </RadarView>
 
     <div v-if="!disabled && showPanel" class="options">
@@ -21,8 +33,8 @@
       <div class="header">
         <a-radio-group v-model:value="modeRadio" button-style="solid" size="small">
           <a-radio-button :value="1">家具</a-radio-button>
-          <!-- <a-radio-button :value="2">子区域</a-radio-button> -->
-          <!-- <a-radio-button :value="3">信息面板</a-radio-button> -->
+          <!-- <a-radio-button :value="2">子区域</a-radio-button>
+          <a-radio-button :value="3">信息面板</a-radio-button> -->
         </a-radio-group>
       </div>
 
@@ -43,14 +55,16 @@
       <div v-if="modeRadio === 2" class="subregion">
         <div class="subregion-header">
           <div class="title">区域列表</div>
-          <a-button size="small">新建区域</a-button>
+          <a-button size="small" @click="createSubregion">新建区域</a-button>
         </div>
 
         <div class="subregion-list">
-          <div class="list-item">区域1 包含床</div>
-          <div class="list-item">区域2</div>
-          <div class="list-item">区域3</div>
-          <div class="list-item">区域4</div>
+          <div v-for="(region, index) in localSubRegions" :key="index" class="list-item">
+            区域{{ index + 1 }} {{ region.isLowSnr ? '包含床' : '' }}
+          </div>
+          <div v-if="localSubRegions.length === 0" class="list-item empty">
+            暂无区域,请点击"新建区域"开始创建
+          </div>
         </div>
       </div>
 
@@ -62,15 +76,17 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { ref, watch, computed } from 'vue'
 import type { FurnitureItem } from '@/types/radar'
 import RadarView from '../RadarView/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 { nanoid } from 'nanoid'
 import type { FurnitureType } from '@/api/room/types'
 import { furnitureIconNameMap, furnitureIconSizeMap } from '@/const/furniture'
+import type { SubRegions } from '@/api/room/types'
 
 defineOptions({ name: 'RadarEditor' })
 
@@ -78,14 +94,28 @@ interface Props {
   coordinates: [number, number, number, number]
   angle: number
   furnitureItems?: FurnitureItem[]
+  subRegions?: SubRegions[]
   disabled?: boolean
 }
 const props = defineProps<Props>()
 const emit = defineEmits<{
   (e: 'update:furnitureItems', items: FurnitureItem[]): void
+  (e: 'update:subRegions', regions: SubRegions[]): void
 }>()
 
 const localFurniture = ref<FurnitureItem[]>(props.furnitureItems ?? [])
+const localSubRegions = ref<SubRegions[]>(props.subRegions ?? [])
+
+// 计算区域宽度和高度
+const areaWidth = computed(() => {
+  const [minX, maxX, minY, maxY] = props.coordinates
+  return Math.abs(maxX - minX)
+})
+
+const areaHeight = computed(() => {
+  const [minX, maxX, minY, maxY] = props.coordinates
+  return Math.abs(maxY - minY)
+})
 
 watch(
   () => props.furnitureItems,
@@ -95,6 +125,24 @@ watch(
   { deep: true }
 )
 
+watch(
+  () => props.subRegions,
+  (newVal) => {
+    if (newVal) localSubRegions.value = [...newVal]
+  },
+  { deep: true }
+)
+
+// 监听本地子区域变化,通知父组件
+watch(
+  localSubRegions,
+  (newRegions) => {
+    // 添加防抖处理,避免频繁更新
+    emit('update:subRegions', newRegions)
+  },
+  { deep: true }
+)
+
 function updateFurniture(item: FurnitureItem) {
   localFurniture.value = localFurniture.value.map((i) => (i.nanoid === item.nanoid ? item : i))
   emit('update:furnitureItems', localFurniture.value)
@@ -114,6 +162,7 @@ defineExpose({ addFurniture })
 
 const modeRadio = ref<1 | 2 | 3>(1)
 const sideRadio = ref<1 | 2 | 3 | 4>(1)
+const editableSubregionRef = ref<InstanceType<typeof EditableSubregion>>()
 
 // 客厅图标
 const livingroomIcons = [
@@ -147,8 +196,6 @@ const bathroomIocns = ['bath_basin', 'bath_shower', 'bath_toilet', 'bath_floor']
 
 // 添加家具
 const add = (icon: FurnitureIconType) => {
-  // emit('add', icon)
-  console.log('add', icon)
   const originWidth = furnitureIconSizeMap[icon].width || 30
   const originHeight = furnitureIconSizeMap[icon].height || 30
 
@@ -169,6 +216,25 @@ const add = (icon: FurnitureIconType) => {
   message.success('已添加家具')
 }
 
+// 创建子区域
+const createSubregion = () => {
+  modeRadio.value = 2
+  // 通过ref调用EditableSubregion组件的createNewBlock方法
+  if (editableSubregionRef.value) {
+    editableSubregionRef.value?.createNewBlock()
+  }
+}
+
+// 处理子区域创建事件
+const handleSubregionCreate = () => {
+  message.success('已创建子区域')
+}
+
+// 处理子区域更新事件
+const handleSubregionUpdate = () => {
+  // 可以在这里添加更新后的逻辑
+}
+
 const showPanel = ref(true)
 </script>
 
@@ -244,7 +310,6 @@ const showPanel = ref(true)
         display: flex;
         flex-direction: column;
         gap: 8px;
-        gap: 8px;
 
         .list-item {
           display: flex;
@@ -255,6 +320,10 @@ const showPanel = ref(true)
           background-color: #f5f5f5;
           cursor: pointer;
         }
+        .list-item.empty {
+          color: #999;
+          text-align: center;
+        }
       }
     }
   }