|
@@ -1,6 +1,7 @@
|
|
<template>
|
|
<template>
|
|
<div class="editable-subregion">
|
|
<div class="editable-subregion">
|
|
<div
|
|
<div
|
|
|
|
+ ref="containerRef"
|
|
class="mapBox blockArea"
|
|
class="mapBox blockArea"
|
|
:style="{
|
|
:style="{
|
|
width: `${canvasSize}px`,
|
|
width: `${canvasSize}px`,
|
|
@@ -24,21 +25,18 @@
|
|
|
|
|
|
<!-- 已创建区块 -->
|
|
<!-- 已创建区块 -->
|
|
<div
|
|
<div
|
|
- v-for="(block, blockIndex) in blocks"
|
|
|
|
- :key="block.id"
|
|
|
|
|
|
+ v-for="(block, index) in roomStore.localSubRegions"
|
|
|
|
+ :key="block.nanoid"
|
|
class="block-item"
|
|
class="block-item"
|
|
:style="getBlockStyle(block)"
|
|
:style="getBlockStyle(block)"
|
|
@mousedown.self="startDrag(block, $event)"
|
|
@mousedown.self="startDrag(block, $event)"
|
|
- @click="selectBlock(block)"
|
|
|
|
>
|
|
>
|
|
<div
|
|
<div
|
|
class="resize-handle"
|
|
class="resize-handle"
|
|
- :style="{
|
|
|
|
- backgroundColor: block?.isBed ? '#1abc1a' : '#1890ff',
|
|
|
|
- }"
|
|
|
|
|
|
+ :style="{ backgroundColor: block.isBed ? '#1abc1a' : '#1890ff' }"
|
|
@mousedown.stop="startResize(block, $event)"
|
|
@mousedown.stop="startResize(block, $event)"
|
|
>
|
|
>
|
|
- {{ blockIndex + 1 }}
|
|
|
|
|
|
+ {{ index + 1 }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -46,459 +44,292 @@
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
<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 { message } from 'ant-design-vue'
|
|
import { nanoid } from 'nanoid'
|
|
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' })
|
|
defineOptions({ name: 'EditableSubregion' })
|
|
|
|
+const roomStore = useRoomStore()
|
|
|
|
|
|
|
|
+// ✅ props
|
|
interface Props {
|
|
interface Props {
|
|
- ranges: [number, number, number, number] // 区域范围 [xStart, xEnd, yStart, yEnd]
|
|
|
|
- angle: number // 设备方向
|
|
|
|
- canvasSize?: number // 画布尺寸
|
|
|
|
- subRegions?: SubRegions[]
|
|
|
|
|
|
+ angle: number
|
|
|
|
+ canvasSize?: number
|
|
editable?: boolean
|
|
editable?: boolean
|
|
hasBed?: 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>(), {
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
canvasSize: 500,
|
|
canvasSize: 500,
|
|
editable: false,
|
|
editable: false,
|
|
hasBed: false,
|
|
hasBed: false,
|
|
})
|
|
})
|
|
-
|
|
|
|
const emit = defineEmits<{
|
|
const emit = defineEmits<{
|
|
- (e: 'update:subRegions', regions: SubRegions[]): void
|
|
|
|
|
|
+ // (e: 'update:subRegions', regions: LocalSubRegionItem[]): void
|
|
(e: 'create'): 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 isCreating = ref(false)
|
|
const currentBlock = ref<{
|
|
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)
|
|
} | 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', [])
|
|
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 = () => {
|
|
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个区块')
|
|
message.warn('最多只能创建6个区块')
|
|
return
|
|
return
|
|
}
|
|
}
|
|
isCreating.value = true
|
|
isCreating.value = true
|
|
message.info('请在画布上拖拽创建子区域')
|
|
message.info('请在画布上拖拽创建子区域')
|
|
}
|
|
}
|
|
-
|
|
|
|
defineExpose({ createNewBlock })
|
|
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
|
|
if (!editable.value) return
|
|
e.stopPropagation()
|
|
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
|
|
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 offsetX = e.clientX - rect.left - block.pixelX
|
|
const offsetY = e.clientY - rect.top - block.pixelY
|
|
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 newX = e.clientX - rect.left - offsetX
|
|
const newY = e.clientY - rect.top - offsetY
|
|
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
|
|
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
|
|
if (!editable.value) return
|
|
e.stopPropagation()
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
e.preventDefault()
|
|
|
|
|
|
- console.log('Start resizing block:', block.id)
|
|
|
|
-
|
|
|
|
block.isResizing = true
|
|
block.isResizing = true
|
|
- selectedBlock.value = block
|
|
|
|
-
|
|
|
|
const startX = e.clientX
|
|
const startX = e.clientX
|
|
const startY = e.clientY
|
|
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 deltaX = e.clientX - startX
|
|
const deltaY = e.clientY - startY
|
|
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
|
|
block.isResizing = false
|
|
- selectedBlock.value = null
|
|
|
|
- console.log('Block resized, updating data')
|
|
|
|
updateSubRegionsData()
|
|
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>
|
|
</script>
|
|
|
|
|
|
<style scoped lang="less">
|
|
<style scoped lang="less">
|
|
@@ -507,14 +338,6 @@ onMounted(() => {
|
|
gap: 20px;
|
|
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 {
|
|
.temp-block {
|
|
position: absolute;
|
|
position: absolute;
|
|
background: rgba(24, 144, 255, 0.2);
|
|
background: rgba(24, 144, 255, 0.2);
|