|
@@ -3,9 +3,12 @@
|
|
|
<div
|
|
|
class="mapBox blockArea"
|
|
|
:style="{
|
|
|
- width: `${areaWidth}px`,
|
|
|
- height: `${areaHeight}px`,
|
|
|
+ width: `${canvasSize}px`,
|
|
|
+ height: `${canvasSize}px`,
|
|
|
cursor: !editable ? 'no-drop' : isCreating ? 'crosshair' : 'default',
|
|
|
+ position: 'relative',
|
|
|
+ border: '1px solid #ccc',
|
|
|
+ background: 'rgba(242, 242, 240, 0.5)',
|
|
|
}"
|
|
|
@mousedown="handleMouseDown"
|
|
|
>
|
|
@@ -14,10 +17,10 @@
|
|
|
v-if="currentBlock"
|
|
|
class="temp-block"
|
|
|
:style="{
|
|
|
- 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`,
|
|
|
+ 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>
|
|
|
|
|
@@ -26,23 +29,14 @@
|
|
|
v-for="(block, blockIndex) in blocks"
|
|
|
:key="block.id"
|
|
|
class="block-item"
|
|
|
- :style="{
|
|
|
- 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'}`,
|
|
|
- position: 'absolute',
|
|
|
- cursor: !editable ? 'no-drop' : 'move',
|
|
|
- backgroundColor: block.isBed ? 'rgba(26, 188, 26, 0.1)' : 'rgba(24, 144, 255, 0.1)',
|
|
|
- }"
|
|
|
+ :style="getBlockStyle(block)"
|
|
|
@mousedown.self="startDrag(block, $event)"
|
|
|
@click="selectBlock(block)"
|
|
|
>
|
|
|
<div
|
|
|
class="resize-handle"
|
|
|
:style="{
|
|
|
- backgroundColor: block.isBed ? '#1abc1a' : '#1890ff',
|
|
|
+ backgroundColor: block?.isBed ? '#1abc1a' : '#1890ff',
|
|
|
}"
|
|
|
@mousedown.stop="startResize(block, $event)"
|
|
|
>
|
|
@@ -50,162 +44,67 @@
|
|
|
</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>
|
|
|
-
|
|
|
- <pre>{{ selectedBlock }}</pre>
|
|
|
- </div> -->
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, watch } from 'vue'
|
|
|
+import { ref, computed, watch, onMounted } 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 // 区域宽度
|
|
|
+ ranges: [number, number, number, number] // 区域范围 [xStart, xEnd, yStart, yEnd]
|
|
|
+ angle: number // 设备方向
|
|
|
+ canvasSize?: number // 画布尺寸
|
|
|
subRegions?: SubRegions[]
|
|
|
editable?: boolean
|
|
|
hasBed?: boolean
|
|
|
}
|
|
|
|
|
|
interface BlockItem {
|
|
|
- // 本地用
|
|
|
- id: string // 唯一标识
|
|
|
- x: number // 区块基于父元素的X偏移量(垂直方向,朝上为正)
|
|
|
- y: number // 区块基于父元素的Y偏移量(水平方向,朝右为正)
|
|
|
- ox: number // 区块基于原点的X偏移量
|
|
|
- oy: number // 区块基于原点的Y偏移量
|
|
|
- width: number // 区块宽度(水平方向)
|
|
|
- height: number // 区块高度(垂直方向)
|
|
|
- isDragging: boolean // 是否正在拖动
|
|
|
- isResizing: boolean // 是否正在调整大小
|
|
|
- isActice: boolean // 是否选中
|
|
|
- isTracking: boolean // 是否开启区域跟踪
|
|
|
- isFalling: boolean // 是否屏蔽区域跌倒检测
|
|
|
- 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-是
|
|
|
+ // 基础属性
|
|
|
+ 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>(), {
|
|
|
- ranges: () => [Infinity, Infinity, Infinity, Infinity],
|
|
|
- width: 0,
|
|
|
- length: 0,
|
|
|
+ canvasSize: 500,
|
|
|
editable: false,
|
|
|
+ hasBed: false,
|
|
|
})
|
|
|
|
|
|
const emit = defineEmits<{
|
|
@@ -214,62 +113,99 @@ const emit = defineEmits<{
|
|
|
(e: 'update', regions: SubRegions[]): void
|
|
|
}>()
|
|
|
|
|
|
-// 检测区域宽度 length
|
|
|
-const areaWidth = computed(() => {
|
|
|
- return Math.abs(props.length)
|
|
|
-})
|
|
|
-// 检测区域高度 width
|
|
|
-const areaHeight = computed(() => {
|
|
|
- return Math.abs(props.width)
|
|
|
+// 调试日志
|
|
|
+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 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 // 像素坐标起点X
|
|
|
+ startY: number // 像素坐标起点Y
|
|
|
+ currentX: number // 像素坐标当前X
|
|
|
+ currentY: number // 像素坐标当前Y
|
|
|
} | null>(null)
|
|
|
const selectedBlock = ref<BlockItem | null>(null)
|
|
|
|
|
|
-const { originX, originY } = getOriginPosition(props.ranges, [0, 0])
|
|
|
-
|
|
|
// 监听subRegions变化,更新blocks
|
|
|
watch(
|
|
|
() => props.subRegions,
|
|
|
(newSubRegions) => {
|
|
|
+ console.log('subRegions changed:', newSubRegions)
|
|
|
if (newSubRegions && newSubRegions.length > 0) {
|
|
|
- blocks.value = newSubRegions.map((item, index) => ({
|
|
|
- id: nanoid(),
|
|
|
- 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,
|
|
|
- isResizing: false,
|
|
|
- isActice: false,
|
|
|
- isTracking: Boolean(item.trackPresence),
|
|
|
- isFalling: Boolean(item.excludeFalling),
|
|
|
- isBed: index === 0 && props.hasBed,
|
|
|
- // 来自接口回显的数据
|
|
|
- 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,
|
|
|
- }))
|
|
|
+ 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 = []
|
|
|
}
|
|
@@ -277,35 +213,71 @@ watch(
|
|
|
{ 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 = () => {
|
|
|
- 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)
|
|
|
+ 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),
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('Region data:', regionData)
|
|
|
+ return regionData
|
|
|
+ })
|
|
|
+
|
|
|
emit('update', subRegionsData)
|
|
|
+ } else {
|
|
|
+ emit('update', [])
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 新建区块处理
|
|
|
const createNewBlock = () => {
|
|
|
- if (blocks.value && blocks.value.length > 5) {
|
|
|
+ console.log('createNewBlock called, current blocks:', blocks.value.length)
|
|
|
+ if (blocks.value && blocks.value.length >= 6) {
|
|
|
message.warn('最多只能创建6个区块')
|
|
|
return
|
|
|
}
|
|
|
isCreating.value = true
|
|
|
+ message.info('请在画布上拖拽创建子区域')
|
|
|
}
|
|
|
|
|
|
defineExpose({ createNewBlock })
|
|
@@ -313,7 +285,9 @@ defineExpose({ createNewBlock })
|
|
|
// 获取容器边界
|
|
|
const getContainerRect = () => {
|
|
|
const container = document.querySelector('.blockArea') as HTMLElement
|
|
|
- return container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 }
|
|
|
+ const rect = container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 }
|
|
|
+ console.log('Container rect:', rect)
|
|
|
+ return rect
|
|
|
}
|
|
|
|
|
|
// 鼠标移动处理
|
|
@@ -321,44 +295,51 @@ const handleMouseMove = (e: MouseEvent) => {
|
|
|
if (!currentBlock.value) return
|
|
|
|
|
|
const rect = getContainerRect()
|
|
|
- // 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))
|
|
|
+ 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
|
|
|
- // 宽度由水平方向(y)差值决定,高度由垂直方向(x)差值决定
|
|
|
- const width = Math.abs(currentY - startY)
|
|
|
- const height = Math.abs(currentX - startX)
|
|
|
+ const width = Math.abs(currentX - startX)
|
|
|
+ const height = Math.abs(currentY - startY)
|
|
|
+
|
|
|
+ console.log('Mouse up, creating block:', { startX, startY, currentX, currentY, width, height })
|
|
|
|
|
|
if (width > 10 && height > 10) {
|
|
|
- const minX = Math.round(Math.min(startX, currentX)) // 垂直方向最小坐标
|
|
|
- const minY = Math.round(Math.min(startY, currentY)) // 水平方向最小坐标
|
|
|
+ const minX = Math.min(startX, currentX)
|
|
|
+ const minY = Math.min(startY, currentY)
|
|
|
|
|
|
- blocks.value.push({
|
|
|
- // 本地用
|
|
|
+ // 计算地理坐标
|
|
|
+ const centerGeo = pixelToGeo(minX + width / 2, minY + height / 2)
|
|
|
+ const geoWidth = width
|
|
|
+ const geoHeight = height
|
|
|
+
|
|
|
+ const newBlock: BlockItem = {
|
|
|
id: nanoid(),
|
|
|
- x: minX, // 垂直方向位置
|
|
|
- y: minY, // 水平方向位置
|
|
|
- ox: minX - originY,
|
|
|
- oy: minY - originX,
|
|
|
- width, // 水平宽度
|
|
|
- height, // 垂直高度
|
|
|
isDragging: false,
|
|
|
isResizing: false,
|
|
|
- isActice: false,
|
|
|
+ isActive: false,
|
|
|
isTracking: false,
|
|
|
isFalling: false,
|
|
|
- // 接口用
|
|
|
- startXx: minY - originX,
|
|
|
- stopXx: minY - originX + width,
|
|
|
- startYy: originY - minX,
|
|
|
- stopYy: originY - minX - height,
|
|
|
+ // 地理坐标
|
|
|
+ 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,
|
|
@@ -367,9 +348,15 @@ const handleMouseUp = () => {
|
|
|
presenceExitDuration: 3,
|
|
|
trackPresence: 0,
|
|
|
excludeFalling: 0,
|
|
|
- })
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('New block created:', newBlock)
|
|
|
+ blocks.value.push(newBlock)
|
|
|
+
|
|
|
emit('create')
|
|
|
updateSubRegionsData()
|
|
|
+ } else {
|
|
|
+ message.warn('区域太小,请绘制更大的区域')
|
|
|
}
|
|
|
|
|
|
currentBlock.value = null
|
|
@@ -383,47 +370,41 @@ const startDrag = (block: BlockItem, e: MouseEvent) => {
|
|
|
if (!editable.value) return
|
|
|
e.stopPropagation()
|
|
|
|
|
|
+ console.log('Start dragging block:', block.id)
|
|
|
+
|
|
|
// 取消选中其他区块
|
|
|
blocks.value.forEach((b) => {
|
|
|
- b.isActice = false
|
|
|
+ b.isActive = false
|
|
|
})
|
|
|
|
|
|
block.isDragging = true
|
|
|
- block.isActice = true
|
|
|
+ block.isActive = true
|
|
|
+
|
|
|
const container = document.querySelector('.blockArea') as HTMLElement
|
|
|
const rect = container.getBoundingClientRect()
|
|
|
- // 垂直方向偏移(x轴)基于clientY,水平方向偏移(y轴)基于clientX
|
|
|
- const offsetX = e.clientY - rect.top - block.x
|
|
|
- const offsetY = e.clientX - rect.left - block.y
|
|
|
+ const offsetX = e.clientX - rect.left - block.pixelX
|
|
|
+ const offsetY = e.clientY - rect.top - block.pixelY
|
|
|
|
|
|
- const initialOx = block.ox
|
|
|
- const initialOy = block.oy
|
|
|
+ const initialPixelX = block.pixelX
|
|
|
+ const initialPixelY = block.pixelY
|
|
|
|
|
|
const moveHandler = (e: MouseEvent) => {
|
|
|
- // 新垂直位置(x轴)= 鼠标垂直位置 - 容器顶部 - 偏移量
|
|
|
- const newX = e.clientY - rect.top - offsetX
|
|
|
- // 新水平位置(y轴)= 鼠标水平位置 - 容器左侧 - 偏移量
|
|
|
- const newY = e.clientX - rect.left - offsetY
|
|
|
+ const newX = e.clientX - rect.left - offsetX
|
|
|
+ const newY = e.clientY - rect.top - offsetY
|
|
|
const containerWidth = container.offsetWidth
|
|
|
const containerHeight = container.offsetHeight
|
|
|
|
|
|
- // 限制边界: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
|
|
|
+ // 限制边界
|
|
|
+ block.pixelX = Math.max(0, Math.min(newX, containerWidth - block.pixelWidth))
|
|
|
+ block.pixelY = Math.max(0, Math.min(newY, containerHeight - block.pixelHeight))
|
|
|
}
|
|
|
|
|
|
const upHandler = () => {
|
|
|
block.isDragging = false
|
|
|
- block.isActice = false
|
|
|
-
|
|
|
- if (block.ox !== initialOx || block.oy !== initialOy) {
|
|
|
- block.startXx = block.oy
|
|
|
- block.stopXx = block.oy + block.width
|
|
|
- block.startYy = originY - block.x
|
|
|
- block.stopYy = originY - block.x - block.height
|
|
|
+ block.isActive = false
|
|
|
|
|
|
+ if (block.pixelX !== initialPixelX || block.pixelY !== initialPixelY) {
|
|
|
+ console.log('Block dragged, updating data')
|
|
|
updateSubRegionsData()
|
|
|
}
|
|
|
|
|
@@ -441,73 +422,60 @@ const startResize = (block: BlockItem, e: MouseEvent) => {
|
|
|
e.stopPropagation()
|
|
|
e.preventDefault()
|
|
|
|
|
|
+ console.log('Start resizing block:', block.id)
|
|
|
+
|
|
|
block.isResizing = true
|
|
|
selectedBlock.value = block
|
|
|
- const startX = e.clientY // 垂直方向起点使用clientY
|
|
|
- const startY = e.clientX // 水平方向起点使用clientX
|
|
|
- const initialWidth = block.width
|
|
|
- const initialHeight = block.height
|
|
|
+
|
|
|
+ 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 moveHandler = (e: MouseEvent) => {
|
|
|
const rect = getContainerRect()
|
|
|
- const deltaX = e.clientY - startX // 垂直方向变化
|
|
|
- const deltaY = e.clientX - startY // 水平方向变化
|
|
|
+ const deltaX = e.clientX - startX
|
|
|
+ const deltaY = e.clientY - startY
|
|
|
+
|
|
|
// 限制最小尺寸和容器边界
|
|
|
- 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 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
|
|
|
}
|
|
|
|
|
|
const upHandler = () => {
|
|
|
block.isResizing = false
|
|
|
selectedBlock.value = null
|
|
|
-
|
|
|
- block.stopXx = block.startXx + block.width
|
|
|
- block.stopYy = block.startYy - block.height
|
|
|
-
|
|
|
+ console.log('Block resized, updating data')
|
|
|
+ updateSubRegionsData()
|
|
|
document.removeEventListener('mousemove', moveHandler)
|
|
|
document.removeEventListener('mouseup', upHandler)
|
|
|
- updateSubRegionsData()
|
|
|
}
|
|
|
|
|
|
document.addEventListener('mousemove', moveHandler)
|
|
|
document.addEventListener('mouseup', upHandler)
|
|
|
}
|
|
|
|
|
|
-// 输入框回车事件
|
|
|
-// 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
|
|
|
+ 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()
|
|
|
- // 交换x/y轴:startX 对应垂直方向(clientY),startY 对应水平方向(clientX)
|
|
|
- const startX = e.clientY - rect.top // 垂直方向起点(x轴)
|
|
|
- const startY = e.clientX - rect.left // 水平方向起点(y轴)
|
|
|
+ const startX = e.clientX - rect.left
|
|
|
+ const startY = e.clientY - rect.top
|
|
|
|
|
|
currentBlock.value = {
|
|
|
startX,
|
|
@@ -524,49 +492,15 @@ const selectBlock = (block: BlockItem) => {
|
|
|
if (!editable.value) return
|
|
|
selectedBlock.value = block
|
|
|
blocks.value.forEach((item) => {
|
|
|
- item.isActice = item === block
|
|
|
+ item.isActive = item === block
|
|
|
})
|
|
|
+ console.log('Block selected:', block.id)
|
|
|
}
|
|
|
|
|
|
-// 关闭子区域属性
|
|
|
-// 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()
|
|
|
-// }
|
|
|
-// }
|
|
|
+// 添加调试信息
|
|
|
+onMounted(() => {
|
|
|
+ console.log('EditableSubregion component mounted')
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="less">
|
|
@@ -578,54 +512,27 @@ const selectBlock = (block: BlockItem) => {
|
|
|
.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;
|
|
|
- }
|
|
|
- }
|
|
|
+ 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);
|
|
|
border: 2px dashed #1890ff;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 2;
|
|
|
}
|
|
|
|
|
|
.block-item {
|
|
|
background: rgba(24, 144, 255, 0.1);
|
|
|
+ pointer-events: all;
|
|
|
+ z-index: 1;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(24, 144, 255, 0.2);
|
|
|
+ }
|
|
|
|
|
|
.resize-handle {
|
|
|
position: absolute;
|
|
@@ -635,11 +542,13 @@ const selectBlock = (block: BlockItem) => {
|
|
|
height: 15px;
|
|
|
background: #1890ff;
|
|
|
cursor: nwse-resize;
|
|
|
- font-size: 12px;
|
|
|
+ font-size: 10px;
|
|
|
color: #fff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+ border-radius: 2px;
|
|
|
+ z-index: 3;
|
|
|
}
|
|
|
}
|
|
|
</style>
|