|
@@ -5,21 +5,9 @@
|
|
|
message="检测区域范围未配置或数值较小,请在设备配置调整参数!"
|
|
|
banner
|
|
|
/>
|
|
|
- <!-- <furnitureCard
|
|
|
- v-if="isEditDraggable"
|
|
|
- v-model:is-edit="isEditDraggable"
|
|
|
- :style="{ marginTop: '30px' }"
|
|
|
- @add="addHandler"
|
|
|
- ></furnitureCard> -->
|
|
|
-
|
|
|
<div class="viewer">
|
|
|
<div class="viewer-header">
|
|
|
- <div>
|
|
|
- <div class="viewer-header-title">家具配置</div>
|
|
|
- <div class="viewer-header-subtitle">
|
|
|
- <span>检测范围 {{ areaWidth }} x {{ areaHeight }} cm {{ props.ranges }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="viewer-header-title">家具与子区域配置</div>
|
|
|
<div class="viewer-header-extra">
|
|
|
<a-space>
|
|
|
<span v-if="props.online === 0" style="color: red">⚠️设备离线,不允许编辑保存</span>
|
|
@@ -46,213 +34,20 @@
|
|
|
ref="radarEditorRef"
|
|
|
:coordinates="props.ranges"
|
|
|
:angle="props.angle"
|
|
|
- :furnitureItems="furnitureItems"
|
|
|
- :subRegions="blocks"
|
|
|
+ :furnitureItems="roomStore.localFurnitureItems"
|
|
|
+ :subRegions="roomStore.localSubRegions"
|
|
|
:disabled="!isEditDraggable"
|
|
|
- @update:furnitureItems="mapCanvasList = $event"
|
|
|
/>
|
|
|
</div>
|
|
|
-
|
|
|
- <div v-if="false" class="viewer">
|
|
|
- <div class="viewer-header">
|
|
|
- <div>
|
|
|
- <div class="viewer-header-title">屏蔽子区域配置</div>
|
|
|
- <div class="viewer-header-subtitle"
|
|
|
- >检测范围 {{ areaWidth }} x {{ areaHeight }} cm {{ props.ranges }}</div
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="viewer-header-extra">
|
|
|
- <a-space>
|
|
|
- <a-button size="small" :disabled="!isEditDraggable" @click="createNewBlock">{{
|
|
|
- isCreating ? '创建中...' : '新建区域'
|
|
|
- }}</a-button>
|
|
|
- </a-space>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div v-if="!areaAvailable" class="viewer-content">
|
|
|
- <div
|
|
|
- class="mapBox blockArea"
|
|
|
- :style="{
|
|
|
- width: `${areaWidth}px`,
|
|
|
- height: `${areaHeight}px`,
|
|
|
- cursor: !isEditDraggable ? 'no-drop' : isCreating ? 'crosshair' : 'default',
|
|
|
- }"
|
|
|
- @mousedown="handleMouseDown"
|
|
|
- >
|
|
|
- <furniture-icon
|
|
|
- v-for="(item, index) in furnitureItems"
|
|
|
- :key="index"
|
|
|
- :icon="item.type"
|
|
|
- :width="item.width"
|
|
|
- :height="item.length"
|
|
|
- :style="{
|
|
|
- left: `${item.left}px`,
|
|
|
- top: `${item.top}px`,
|
|
|
- position: 'absolute',
|
|
|
- transform: `rotate(${item.rotate}deg)`,
|
|
|
- cursor: 'default',
|
|
|
- }"
|
|
|
- :draggable="false"
|
|
|
- />
|
|
|
-
|
|
|
- <!-- 绘制临时选区 -->
|
|
|
- <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: !isEditDraggable ? '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>
|
|
|
- </div>
|
|
|
</a-spin>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { ref, computed } from 'vue'
|
|
|
import * as roomApi from '@/api/room'
|
|
|
-import type { FurnitureType, Furniture } from '@/api/room/types'
|
|
|
import { message } from 'ant-design-vue'
|
|
|
-import { nanoid } from 'nanoid'
|
|
|
-import { useDropZone } from '@vueuse/core'
|
|
|
-// import { furnitureIconNameMap, furnitureIconSizeMap } from '@/const/furniture'
|
|
|
-// import type { FurnitureIconType } from '@/types/furniture'
|
|
|
-// import furnitureCard from '../furnitureCard/index.vue'
|
|
|
import RadarEditor from '@/components/RadarEditor/index.vue'
|
|
|
-import { DeleteOutlined, CloseOutlined } from '@ant-design/icons-vue'
|
|
|
-import { getOriginPosition } from '@/utils'
|
|
|
-// import type { FurnitureItem } from '@/types/radar'
|
|
|
+import { useRoomStore } from '@/stores/room'
|
|
|
|
|
|
defineOptions({
|
|
|
name: 'deviceAreaConfig',
|
|
@@ -279,7 +74,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
})
|
|
|
|
|
|
const deviceRoomId = ref('')
|
|
|
-const furnitureItems = ref<Furniture[]>([])
|
|
|
+const roomStore = useRoomStore()
|
|
|
|
|
|
// 检测区域宽度 length
|
|
|
const areaWidth = computed(() => {
|
|
@@ -315,557 +110,60 @@ const fetchRoomLayout = async () => {
|
|
|
}
|
|
|
const { furnitures, roomId, subRegions } = res.data
|
|
|
deviceRoomId.value = roomId || ''
|
|
|
- if (furnitures) {
|
|
|
- furnitureItems.value = furnitures!.map((item) => {
|
|
|
- // 将接口的家具,添加在家具画布上
|
|
|
- mapCanvasList.value.push({
|
|
|
- name: item.name,
|
|
|
- type: item.type,
|
|
|
- width: item.width,
|
|
|
- height: item.length,
|
|
|
- top: item.top,
|
|
|
- left: item.left,
|
|
|
- x: item.x,
|
|
|
- y: item.y,
|
|
|
- rotate: item.rotate,
|
|
|
- nanoid: nanoid(),
|
|
|
- })
|
|
|
- // 将接口的家具,添加在屏蔽子区域画布上
|
|
|
- return {
|
|
|
- ...item,
|
|
|
- width: item.width || 45,
|
|
|
- length: item.length || 45,
|
|
|
- top: item.top || 0,
|
|
|
- left: item.left || 0,
|
|
|
- rotate: item.rotate || 0,
|
|
|
- x: item.x || 0,
|
|
|
- y: item.y || 0,
|
|
|
- nanoid: nanoid(),
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (subRegions) {
|
|
|
- // 将接口的子区域,添加在子区域画布上
|
|
|
- subRegions.forEach((item, index) => {
|
|
|
- blocks.value.push({
|
|
|
- // 本地需要使用的数据
|
|
|
- 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 && mapCanvasList.value.some((item) => item.type === 'bed'),
|
|
|
- // 来自接口回显的数据
|
|
|
- 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,
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- console.log('🚀', blocks.value)
|
|
|
- }
|
|
|
+ roomStore.cacheFurniture(furnitures ?? [])
|
|
|
+ roomStore.cacheSubRegion(subRegions ?? [])
|
|
|
spinning.value = false
|
|
|
} catch (error) {
|
|
|
console.error('❌获取房间布局信息失败', error)
|
|
|
spinning.value = false
|
|
|
}
|
|
|
}
|
|
|
-fetchRoomLayout().finally(() => {
|
|
|
- // 获取房间信息后,初始化雷达图标
|
|
|
- // initRadarIcon()
|
|
|
-})
|
|
|
-
|
|
|
-interface CanvaseItem {
|
|
|
- name: string // 名称
|
|
|
- type: string // 类型,图标icon
|
|
|
- width: number // 家具宽度
|
|
|
- height?: number // 家具长度
|
|
|
- length?: number // 家具长度
|
|
|
- top: number // 距离检测范围上方位置 cm
|
|
|
- left: number // 距离检测范围左边位置 cm
|
|
|
- rotate: number // 旋转角度: 0°,90°,180°,270°
|
|
|
- x?: number // 距离雷达的X距离
|
|
|
- y?: number // 距离雷达的Y距离
|
|
|
- nanoid?: string // 本地使用
|
|
|
- isActice?: boolean // 是否选中 本地使用
|
|
|
- isDragging?: boolean // 是否拖拽 本地使用
|
|
|
-}
|
|
|
+fetchRoomLayout()
|
|
|
|
|
|
const radarEditorRef = ref<InstanceType<typeof RadarEditor>>()
|
|
|
|
|
|
// 画布上的家具列表
|
|
|
-const mapCanvasList = ref<CanvaseItem[]>([])
|
|
|
const isEditDraggable = ref(false)
|
|
|
-// 家具列表添加
|
|
|
-// const addHandler = (icon: FurnitureIconType) => {
|
|
|
-// console.log('addHandler', icon)
|
|
|
-
|
|
|
-// if (icon === 'bed') {
|
|
|
-// const isExist = mapCanvasList.value.some((item) => item.type === icon)
|
|
|
-// if (isExist) {
|
|
|
-// message.error('床已经添加过了,不可重复添加')
|
|
|
-// return
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
-// const originWidth = furnitureIconSizeMap[icon].width || 30
|
|
|
-// const originHeight = furnitureIconSizeMap[icon].height || 30
|
|
|
-
|
|
|
-// const newItem: FurnitureItem = {
|
|
|
-// name: furnitureIconNameMap[icon],
|
|
|
-// type: icon as FurnitureType,
|
|
|
-// width: originWidth,
|
|
|
-// length: originHeight,
|
|
|
-// top: 0,
|
|
|
-// left: 0,
|
|
|
-// rotate: 0,
|
|
|
-// x: 0,
|
|
|
-// y: 0,
|
|
|
-// nanoid: nanoid(),
|
|
|
-// }
|
|
|
-
|
|
|
-// radarEditorRef.value?.addFurniture(newItem)
|
|
|
-// message.success('已添加家具')
|
|
|
-
|
|
|
-// if (icon === 'bed') {
|
|
|
-// blocks.value.unshift({
|
|
|
-// id: nanoid(),
|
|
|
-// x: 20,
|
|
|
-// y: 15,
|
|
|
-// ox: -150,
|
|
|
-// oy: 180,
|
|
|
-// width: originWidth,
|
|
|
-// height: originHeight,
|
|
|
-// isDragging: false,
|
|
|
-// isResizing: false,
|
|
|
-// isActice: false,
|
|
|
-// isTracking: false,
|
|
|
-// isFalling: false,
|
|
|
-// isBed: true,
|
|
|
-// startXx: -150,
|
|
|
-// stopXx: -100,
|
|
|
-// startYy: 180,
|
|
|
-// stopYy: 120,
|
|
|
-// startZz: 0,
|
|
|
-// stopZz: 0,
|
|
|
-// isLowSnr: 1,
|
|
|
-// isDoor: 0,
|
|
|
-// presenceEnterDuration: 3,
|
|
|
-// presenceExitDuration: 3,
|
|
|
-// trackPresence: 0,
|
|
|
-// excludeFalling: 0,
|
|
|
-// })
|
|
|
-// message.success('已添加子区域')
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
-const contentEl = ref<HTMLElement>()
|
|
|
-const currentDragItem = ref<CanvaseItem | null>(null)
|
|
|
-// 内容区域放置处理
|
|
|
-useDropZone(contentEl, {
|
|
|
- onDrop(files: File[] | null, event: DragEvent) {
|
|
|
- if (contentEl.value && currentDragItem.value) {
|
|
|
- const rect = contentEl.value.getBoundingClientRect()
|
|
|
- // 计算基于画布容器的精确坐标
|
|
|
- const x = event.clientX - rect.left
|
|
|
- const y = event.clientY - rect.top
|
|
|
- // 中心点对齐计算
|
|
|
- currentDragItem.value.top = y - currentDragItem.value.height! / 2
|
|
|
- currentDragItem.value.left = x - currentDragItem.value.width / 2
|
|
|
- // 添加边界检查
|
|
|
- // const maxX = contentEl.value.offsetWidth - currentDragItem.value.width
|
|
|
- // const maxY = contentEl.value.offsetHeight - currentDragItem.value.height
|
|
|
- // currentDragItem.value.left = Math.max(0, Math.min(x - currentDragItem.value.width / 2, maxX))
|
|
|
- // currentDragItem.value.top = Math.max(0, Math.min(y - currentDragItem.value.height / 2, maxY))
|
|
|
- }
|
|
|
- },
|
|
|
-})
|
|
|
-
|
|
|
-const clickedDragItem = ref<CanvaseItem | null>()
|
|
|
-
|
|
|
-mapCanvasList.value.forEach((item) => {
|
|
|
- item.isActice = false
|
|
|
-})
|
|
|
-clickedDragItem.value = null
|
|
|
-
|
|
|
-// 关闭子区域属性
|
|
|
-const closeSubregionAttr = () => {
|
|
|
- blocks.value.forEach((item) => {
|
|
|
- item.isActice = false
|
|
|
- })
|
|
|
- selectedBlock.value = null
|
|
|
-}
|
|
|
-
|
|
|
-// 新增区块类型
|
|
|
-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 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 createNewBlock = () => {
|
|
|
- if (blocks.value && blocks.value.length > 5) {
|
|
|
- message.warn('最多只能创建6个区块')
|
|
|
- return
|
|
|
- }
|
|
|
- isCreating.value = true
|
|
|
-}
|
|
|
-
|
|
|
-// 获取容器边界
|
|
|
-const getContainerRect = () => {
|
|
|
- const container = document.querySelector('.blockArea') as HTMLElement
|
|
|
- return container?.getBoundingClientRect() || { left: 0, top: 0 }
|
|
|
-}
|
|
|
-
|
|
|
-// 鼠标事件处理
|
|
|
-const handleMouseDown = (e: MouseEvent) => {
|
|
|
- if (!isEditDraggable.value) return
|
|
|
- console.log('handleMouseDown', e)
|
|
|
- // blocks.value.forEach((item) => {
|
|
|
- // item.isActice = false
|
|
|
- // })
|
|
|
- // selectedBlock.value = null
|
|
|
- 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 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) {
|
|
|
- blocks.value.push({
|
|
|
- // 本地用
|
|
|
- id: nanoid(),
|
|
|
- x: Math.round(Math.min(startX, currentX)),
|
|
|
- y: Math.round(Math.min(startY, currentY)),
|
|
|
- ox: Math.round(Math.min(startX, currentX)) - originX,
|
|
|
- oy: Math.round(Math.min(startY, currentY)) - originY,
|
|
|
- width,
|
|
|
- height,
|
|
|
- isDragging: false,
|
|
|
- isResizing: false,
|
|
|
- isActice: false,
|
|
|
- isTracking: false,
|
|
|
- isFalling: false,
|
|
|
- // 接口用
|
|
|
- startXx: Math.round(Math.min(startX, currentX)) - originX,
|
|
|
- stopXx: Math.round(Math.min(startX, currentX)) - originX + width,
|
|
|
- startYy: Math.round(Math.min(startY, currentY)) - originY,
|
|
|
- stopYy: Math.round(Math.min(startY, currentY)) - originY + height,
|
|
|
- startZz: 0,
|
|
|
- stopZz: 0,
|
|
|
- isLowSnr: 0,
|
|
|
- isDoor: 0,
|
|
|
- presenceEnterDuration: 3,
|
|
|
- presenceExitDuration: 3,
|
|
|
- trackPresence: 0,
|
|
|
- excludeFalling: 0,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- currentBlock.value = null
|
|
|
- isCreating.value = false
|
|
|
- document.removeEventListener('mousemove', handleMouseMove)
|
|
|
- document.removeEventListener('mouseup', handleMouseUp)
|
|
|
-}
|
|
|
-
|
|
|
-// 区块拖动
|
|
|
-const startDrag = (block: BlockItem, e: MouseEvent) => {
|
|
|
- if (!isEditDraggable.value) return
|
|
|
- console.log('startDrag', block)
|
|
|
- e.stopPropagation()
|
|
|
- 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
|
|
|
- block.startXx = block.ox
|
|
|
- block.stopXx = block.ox + block.width
|
|
|
- block.startYy = block.oy
|
|
|
- block.stopYy = block.oy - block.height
|
|
|
- }
|
|
|
-
|
|
|
- const upHandler = () => {
|
|
|
- block.isDragging = false
|
|
|
- block.isActice = false
|
|
|
- document.removeEventListener('mousemove', moveHandler)
|
|
|
- document.removeEventListener('mouseup', upHandler)
|
|
|
- }
|
|
|
-
|
|
|
- document.addEventListener('mousemove', moveHandler)
|
|
|
- document.addEventListener('mouseup', upHandler)
|
|
|
-}
|
|
|
-
|
|
|
-const selectBlock = (block: BlockItem) => {
|
|
|
- if (!isEditDraggable.value) return
|
|
|
- console.log('selectBlock', block)
|
|
|
- selectedBlock.value = block
|
|
|
- blocks.value.forEach((item) => {
|
|
|
- item.isActice = item === block
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const { originX, originY } = getOriginPosition(props.ranges, [
|
|
|
- currentDragItem.value?.left as number,
|
|
|
- currentDragItem.value?.top as number,
|
|
|
-])
|
|
|
-
|
|
|
-// 初始化添加雷达图标
|
|
|
-// const initRadarIcon = () => {
|
|
|
-// console.log('initRadarIcon', mapCanvasList.value, furnitureItems.value)
|
|
|
-// }
|
|
|
|
|
|
// 保存所有配置
|
|
|
const saveAllConfig = () => {
|
|
|
- console.log('保存所有配置')
|
|
|
- // const blockData = blocks.value.map((item) => {
|
|
|
- // return {
|
|
|
- // startXx: item.startXx,
|
|
|
- // stopXx: item.stopXx,
|
|
|
- // startYy: item.startYy,
|
|
|
- // stopYy: item.stopYy,
|
|
|
- // startZz: Number(item.startZz) || 0,
|
|
|
- // stopZz: Number(item.stopZz) || 0,
|
|
|
- // isLowSnr: item.isLowSnr,
|
|
|
- // isDoor: item.isDoor,
|
|
|
- // presenceEnterDuration: item.presenceEnterDuration,
|
|
|
- // presenceExitDuration: item.presenceExitDuration,
|
|
|
- // trackPresence: Number(item.isTracking),
|
|
|
- // excludeFalling: Number(item.isFalling),
|
|
|
- // }
|
|
|
- // })
|
|
|
+ console.log('保存所有配置', {
|
|
|
+ furnitureItems: roomStore.localFurnitureItems,
|
|
|
+ subRegions: roomStore.localSubRegions,
|
|
|
+ })
|
|
|
|
|
|
- const blockData =
|
|
|
- Array.isArray(JSON.parse(localStorage.getItem('subRegions') ?? '[]')) &&
|
|
|
- JSON.parse(localStorage.getItem('subRegions') ?? '[]').map((item) => {
|
|
|
- return {
|
|
|
- startXx: item.startXx,
|
|
|
- stopXx: item.stopXx,
|
|
|
- startYy: item.startYy,
|
|
|
- stopYy: item.stopYy,
|
|
|
- startZz: Number(item.startZz) || 0,
|
|
|
- stopZz: Number(item.stopZz) || 0,
|
|
|
- isLowSnr: item.isLowSnr,
|
|
|
- isDoor: item.isDoor,
|
|
|
- presenceEnterDuration: item.presenceEnterDuration,
|
|
|
- presenceExitDuration: item.presenceExitDuration,
|
|
|
- trackPresence: Number(item.isTracking),
|
|
|
- excludeFalling: Number(item.isFalling),
|
|
|
- }
|
|
|
- })
|
|
|
- console.log('当前所有区块配置:', blockData)
|
|
|
try {
|
|
|
const res = roomApi.saveRoomInfo({
|
|
|
roomId: deviceRoomId.value,
|
|
|
devId: props.devId,
|
|
|
- furnitures: mapCanvasList.value
|
|
|
- .filter((item) => item.type !== 'radar')
|
|
|
- .map((item) => {
|
|
|
- return {
|
|
|
- name: item.name,
|
|
|
- type: item.type as FurnitureType,
|
|
|
- width: item.width,
|
|
|
- length: item.height || (item.length as number),
|
|
|
- top: item.top,
|
|
|
- left: item.left,
|
|
|
- rotate: item.rotate as 0 | 90 | 180 | 270,
|
|
|
- x: item?.x || 0,
|
|
|
- y: item?.y || 0,
|
|
|
- }
|
|
|
- }),
|
|
|
- subRegions: blockData,
|
|
|
+ // furnitures: mapCanvasList.value
|
|
|
+ // .filter((item) => item.type !== 'radar')
|
|
|
+ // .map((item) => {
|
|
|
+ // return {
|
|
|
+ // name: item.name,
|
|
|
+ // type: item.type as FurnitureType,
|
|
|
+ // width: item.width,
|
|
|
+ // length: item.height || (item.length as number),
|
|
|
+ // top: item.top,
|
|
|
+ // left: item.left,
|
|
|
+ // rotate: item.rotate as 0 | 90 | 180 | 270,
|
|
|
+ // x: item?.x || 0,
|
|
|
+ // y: item?.y || 0,
|
|
|
+ // }
|
|
|
+ // }),
|
|
|
+ // subRegions: blockData,
|
|
|
})
|
|
|
- console.log('保存所有配置 成功', res)
|
|
|
+ console.log('保存所有配置 ✅', res)
|
|
|
message.success('保存成功')
|
|
|
emit('success')
|
|
|
} catch (error) {
|
|
|
- console.error('保存所有配置 失败', error)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const startResize = (block: BlockItem, e: MouseEvent) => {
|
|
|
- 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))
|
|
|
-
|
|
|
- // 改变了区块的长款,元素的结束位置也相应变化
|
|
|
- block.stopXx = block.ox + block.width
|
|
|
- block.stopYy = block.oy + block.height
|
|
|
- }
|
|
|
-
|
|
|
- const upHandler = () => {
|
|
|
- block.isResizing = false
|
|
|
- selectedBlock.value = null
|
|
|
- document.removeEventListener('mousemove', moveHandler)
|
|
|
- document.removeEventListener('mouseup', upHandler)
|
|
|
- }
|
|
|
-
|
|
|
- document.addEventListener('mousemove', moveHandler)
|
|
|
- document.addEventListener('mouseup', upHandler)
|
|
|
-}
|
|
|
-
|
|
|
-const blockInputPressEnter = (e: Event, el: BlockItem, attr: string) => {
|
|
|
- console.log('blockInputPressEnter', e, el, attr)
|
|
|
- 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 + originX - el.width
|
|
|
- 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 + originY - el.height
|
|
|
- el.height = el.stopYy - el.startYy
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const blockInputBlur = (e: Event, el: BlockItem, attr: string) => {
|
|
|
- console.log('blockInputBlur', e, el, attr)
|
|
|
- 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 + originX - el.width
|
|
|
- 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 + originY - el.height
|
|
|
- el.height = el.stopYy - el.startYy
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const deleteBlockArea = (id: string) => {
|
|
|
- if (id) {
|
|
|
- blocks.value = blocks.value.filter((item) => item.id !== id)
|
|
|
- selectedBlock.value = null
|
|
|
+ console.error('保存所有配置 ❌', error)
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
.viewer {
|
|
|
- padding: 10px;
|
|
|
- min-width: 500px;
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
&-header {
|
|
@@ -878,10 +176,6 @@ const deleteBlockArea = (id: string) => {
|
|
|
font-weight: 600;
|
|
|
line-height: 24px;
|
|
|
}
|
|
|
- &-subtitle {
|
|
|
- font-size: 14px;
|
|
|
- color: #666;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
&-content {
|
|
@@ -889,94 +183,4 @@ const deleteBlockArea = (id: string) => {
|
|
|
gap: 20px;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-.mapBox {
|
|
|
- background-color: #e0e0e0;
|
|
|
- background-image:
|
|
|
- linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
|
|
|
- linear-gradient(to right, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
|
|
|
- background-size: 20px 20px;
|
|
|
- position: relative;
|
|
|
- flex-shrink: 0;
|
|
|
-
|
|
|
- // 添加黑边框
|
|
|
- &::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: -5px;
|
|
|
- left: -5px;
|
|
|
- width: calc(100% + 10px);
|
|
|
- height: calc(100% + 10px);
|
|
|
- border: 5px solid rgba(0, 0, 0, 0.8);
|
|
|
- box-sizing: border-box;
|
|
|
- pointer-events: none;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.mapConfig {
|
|
|
- background-color: #f5f5f5;
|
|
|
- border-radius: 10px;
|
|
|
- padding: 12px;
|
|
|
- &-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;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.dragging-item {
|
|
|
- opacity: 0.5;
|
|
|
- transition: opacity 0.2s ease;
|
|
|
-}
|
|
|
</style>
|