|
@@ -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>
|