feat: map rotation with overlay fix — rotate only img, keep overlay with points + nav click

This commit is contained in:
ywb
2026-05-17 09:24:34 +08:00
parent 4b964b7067
commit 6a5fce819f
2 changed files with 61 additions and 12 deletions
+9 -7
View File
@@ -53,24 +53,26 @@
<span style="font-size:12px;color:#888">旋转:</span> <span style="font-size:12px;color:#888">旋转:</span>
<button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="rotateMap(-90)">↶ 90°</button> <button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="rotateMap(-90)">↶ 90°</button>
<button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="rotateMap(90)">↷ 90°</button> <button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="rotateMap(90)">↷ 90°</button>
<button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="mapRotation = 0">重置</button> <button class="btn btn-secondary" style="padding:4px 10px;font-size:12px" @click="resetMapView">重置</button>
</div> </div>
</div> </div>
<div class="map-container" style="position:relative;background:#111;border-radius:8px;overflow:hidden"> <div class="map-container" style="position:relative;background:#111;border-radius:8px;overflow:hidden">
<!-- 地图旋转 wrapper --> <img :src="mapImageUrl" @error="onMapError" @click="onMapClick" style="width:100%;display:block;cursor:crosshair" title="点击地图导航到该位置">
<div :style="{ transform: 'rotate(' + mapRotation + 'deg)', transition: 'transform 0.3s ease' }">
<img :src="mapImageUrl" @error="onMapError" style="width:100%;display:block">
<!-- 地图覆盖层:显示点位坐标 -->
<div class="map-overlay"> <div class="map-overlay">
<!-- AGV 实时位置 -->
<div v-if="navCurrentPos && nav2Available"
class="map-dot agv-dot"
:style="{ left: getMapX(navCurrentPos) + '%', top: getMapY(navCurrentPos) + '%' }"
title="AGV 当前位置">
</div>
<!-- 点位坐标点 --> <!-- 点位坐标点 -->
<div v-for="(p, pi) in missionConfig.positions" :key="'pdot-'+pi" <div v-for="(p, pi) in missionConfig.positions" :key="'pdot-'+mapVersion+'-'+pi"
class="map-dot point-dot" class="map-dot point-dot"
:style="{ left: getMapX(p.coords) + '%', top: getMapY(p.coords) + '%' }" :style="{ left: getMapX(p.coords) + '%', top: getMapY(p.coords) + '%' }"
:title="p.coords ? p.coords.map(c => c.toFixed(2)).join(', ') : ''"> :title="p.coords ? p.coords.map(c => c.toFixed(2)).join(', ') : ''">
</div> </div>
</div> </div>
</div> </div>
</div>
</section> </section>
</div> </div>
+47
View File
@@ -17,6 +17,9 @@ createApp({
mapImageUrl: '', mapImageUrl: '',
mapMeta: null, mapMeta: null,
mapRotation: 0, mapRotation: 0,
mapVersion: 0,
navCurrentPos: null,
nav2Available: false,
// 点位 // 点位
points: [], points: [],
newPointName: '', newPointName: '',
@@ -47,6 +50,7 @@ createApp({
mounted() { mounted() {
this.refresh() this.refresh()
this.refreshAngles() this.refreshAngles()
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
}, },
watch: { watch: {
tab(val) { tab(val) {
@@ -65,6 +69,7 @@ createApp({
beforeUnmount() { beforeUnmount() {
Object.values(this.jogIntervals).forEach(i => clearInterval(i)) Object.values(this.jogIntervals).forEach(i => clearInterval(i))
if (this.agvCameraTimer) clearInterval(this.agvCameraTimer) if (this.agvCameraTimer) clearInterval(this.agvCameraTimer)
if (this.nav2Timer) clearInterval(this.nav2Timer)
}, },
methods: { methods: {
async refresh() { async refresh() {
@@ -114,6 +119,48 @@ createApp({
rotateMap(deg) { rotateMap(deg) {
this.mapRotation = (this.mapRotation + deg) % 360 this.mapRotation = (this.mapRotation + deg) % 360
}, },
resetMapView() {
this.mapRotation = 0
this.mapVersion++
},
async refreshNavStatus() {
try {
const res = await fetch(API + '/api/navigate/status')
if (res.ok) {
const data = await res.json()
this.nav2Available = data.nav2_available
if (data.current_pos) {
this.navCurrentPos = data.current_pos
}
}
} catch (e) {}
},
async onMapClick(e) {
if (!this.mapMeta || !this.agvConnected) return
const rect = e.target.getBoundingClientRect()
const px = (e.clientX - rect.left) / rect.width
const py = (e.clientY - rect.top) / rect.height
const { resolution, origin } = this.mapMeta
const wx = origin[0] + px * resolution * this.mapMeta.width
const wy = origin[1] + (1 - py) * resolution * this.mapMeta.height
try {
const res = await fetch(API + '/api/navigate/to', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x: wx, y: wy })
})
const data = await res.json()
if (data.ok) {
this.mapMsg = '✅ 导航目标已发送'
this.mapVersion++
} else {
this.mapMsg = '❌ ' + (data.error || '导航失败')
}
} catch (e) {
this.mapMsg = '❌ 导航请求失败'
}
setTimeout(() => { this.mapMsg = '' }, 3000)
},
getMapX(coords) { getMapX(coords) {
if (!coords || !this.mapMeta) return 50 if (!coords || !this.mapMeta) return 50
const [x, y, yaw] = coords const [x, y, yaw] = coords