on-site-robots-screen/src/views/home/components/modal-content/preset-setting.vue

259 lines
7.2 KiB
Vue
Raw Normal View History

2025-06-24 18:12:02 +08:00
<template>
<!-- 预置位配置 -->
<DialogModal @onHandleCloseModal="onHandleCloseModal" :modalTitle="modalTitle" :height="`90vh`">
<!-- 平面图操作区域 -->
<div class="plane-map-container" ref="planeMapContainer">
<svg
ref="svgMap"
class="floor-plan"
:viewBox="`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`"
preserveAspectRatio="xMidYMid meet"
@click="handleMapClick"
>
<!-- 平面图背景 -->
<image
v-if="imgLoaded"
:href="demoImg"
:width="viewBox.width"
:height="viewBox.height"
preserveAspectRatio="xMidYMid slice"
style="object-fit: cover"
/>
<!-- 动态渲染标记点 -->
<circle
v-for="(point, index) in points"
:key="index"
:cx="point.x"
:cy="point.y"
r="8"
fill="red"
@click.stop="selectPoint(index)"
@mousedown="startDrag(index, $event)"
/>
<!-- 动态渲染连线 -->
<line
v-for="(line, index) in lines"
:key="'line-' + index"
:x1="line.start.x"
:y1="line.start.y"
:x2="line.end.x"
:y2="line.end.y"
stroke="blue"
stroke-width="2"
/>
</svg>
<div class="map-control-container">
<n-button type="info" @click="handleZoomIn">放大</n-button>
<n-button type="info" @click="handleZoomOut">缩小</n-button>
</div>
</div>
</DialogModal>
</template>
<script setup>
import DialogModal from '@/components/DialogModal/index.vue'
import { refThrottled, useMouseInElement } from '@vueuse/core'
import { ref, onMounted, nextTick, watch } from 'vue'
// import { CashOutline as CashIcon } from '@vicons/ionicons5'
import { NIcon } from 'naive-ui'
import demoImg from '@/assets/demo.png'
const emits = defineEmits(['onHandleCloseModal'])
const modalTitle = ref('预置位配置')
const svgMap = refThrottled(null)
const points = ref([])
const lines = ref([])
const imgLoaded = ref(false)
const imgNaturalSize = ref({ width: 0, height: 0 })
const props = defineProps({
presetSettingVisible: {
type: Boolean,
default: false,
},
})
// 新增:响应式 viewBox 控制
const viewBox = ref({
x: 0,
y: 0,
width: 0,
height: 0,
})
// 新增:容器尺寸跟踪
const containerSize = ref({ width: 0, height: 0 })
const planeMapContainer = ref(null)
// 关闭模态框
const onHandleCloseModal = () => {
emits('onHandleCloseModal')
}
// 拖拽标记点
const startDrag = (index, e) => {
e.preventDefault()
const { x: startX, y: startY } = points.value[index]
const onMove = (moveEvent) => {
const rect = svgMap.value.getBoundingClientRect()
points.value[index] = {
x: moveEvent.clientX - rect.left,
y: moveEvent.clientY - rect.top,
}
}
const onUp = () => {
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
}
window.addEventListener('mousemove', onMove)
window.addEventListener('mouseup', onUp)
}
// 连线示例(假设连接第一个和第二个点)
const connectPoints = () => {
if (points.value.length >= 2) {
lines.value.push({
start: points.value[0],
end: points.value[1],
})
}
}
onMounted(() => {
// 监听容器尺寸变化
nextTick(() => {
const resizeObserver = new ResizeObserver((entries) => {
if (entries[0]) {
containerSize.value = {
width: entries[0].contentRect.width,
height: entries[0].contentRect.height,
}
}
})
if (planeMapContainer.value) {
resizeObserver.observe(planeMapContainer.value)
}
})
})
// 修改点击事件处理(考虑缩放后的坐标)
const handleMapClick = (e) => {
if (!svgMap.value) return
const pt = svgMap.value.createSVGPoint()
pt.x = e.clientX
pt.y = e.clientY
const svgPt = pt.matrixTransform(svgMap.value.getScreenCTM().inverse())
points.value.push({
x: svgPt.x,
y: svgPt.y,
})
}
watch(
() => props.presetSettingVisible,
(newVal) => {
if (newVal) {
nextTick(() => {
loadImage()
})
}
},
{
immediate: true,
},
)
const initMapSize = () => {
if (!planeMapContainer.value) return
containerSize.value = {
width: planeMapContainer.value.clientWidth,
height: planeMapContainer.value.clientHeight,
}
// 初始化 viewBox根据您的业务逻辑调整
viewBox.value.width = containerSize.value.width
viewBox.value.height = containerSize.value.height
}
// 加载图片并获取真实尺寸
const loadImage = () => {
const img = new Image()
img.onload = () => {
imgNaturalSize.value = {
width: img.naturalWidth,
height: img.naturalHeight,
}
imgLoaded.value = true
adjustViewBox()
}
img.onerror = () => {
console.error('图片加载失败,请检查路径:', demoImg)
}
img.src = demoImg // 使用导入的图片路径
}
// 调整viewBox
const adjustViewBox = () => {
if (!planeMapContainer.value || !imgNaturalSize.value.width) return
const container = planeMapContainer.value
const containerRatio = container.clientWidth / container.clientHeight
const imgRatio = imgNaturalSize.value.width / imgNaturalSize.value.height
if (containerRatio > imgRatio) {
// 容器更宽,按高度适配
viewBox.value = {
x: (imgNaturalSize.value.width - imgNaturalSize.value.height * containerRatio) / 2,
y: 0,
width: imgNaturalSize.value.height * containerRatio,
height: imgNaturalSize.value.height,
}
} else {
// 容器更高,按宽度适配
viewBox.value = {
x: 0,
y: (imgNaturalSize.value.height - imgNaturalSize.value.width / containerRatio) / 2,
width: imgNaturalSize.value.width,
height: imgNaturalSize.value.width / containerRatio,
}
}
}
const handleZoomIn = () => {
console.log('放大')
viewBox.value.width = viewBox.value.width * 1.1
viewBox.value.height = viewBox.value.height * 1.1
}
const handleZoomOut = () => {
console.log('缩小')
viewBox.value.width = viewBox.value.width * 0.9
viewBox.value.height = viewBox.value.height * 0.9
}
</script>
<style lang="scss" scoped>
.plane-map-container {
width: 100%;
height: 100%;
position: relative;
.map-control-container {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 10px;
}
}
</style>