feat: map rotation with overlay fix — rotate only img, keep overlay with points + nav click
This commit is contained in:
+14
-12
@@ -53,21 +53,23 @@
|
|||||||
<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' }">
|
<div class="map-overlay">
|
||||||
<img :src="mapImageUrl" @error="onMapError" style="width:100%;display:block">
|
<!-- AGV 实时位置 -->
|
||||||
<!-- 地图覆盖层:显示点位坐标 -->
|
<div v-if="navCurrentPos && nav2Available"
|
||||||
<div class="map-overlay">
|
class="map-dot agv-dot"
|
||||||
<!-- 点位坐标点 -->
|
:style="{ left: getMapX(navCurrentPos) + '%', top: getMapY(navCurrentPos) + '%' }"
|
||||||
<div v-for="(p, pi) in missionConfig.positions" :key="'pdot-'+pi"
|
title="AGV 当前位置">
|
||||||
class="map-dot point-dot"
|
</div>
|
||||||
:style="{ left: getMapX(p.coords) + '%', top: getMapY(p.coords) + '%' }"
|
<!-- 点位坐标点 -->
|
||||||
:title="p.coords ? p.coords.map(c => c.toFixed(2)).join(', ') : ''">
|
<div v-for="(p, pi) in missionConfig.positions" :key="'pdot-'+mapVersion+'-'+pi"
|
||||||
</div>
|
class="map-dot point-dot"
|
||||||
|
:style="{ left: getMapX(p.coords) + '%', top: getMapY(p.coords) + '%' }"
|
||||||
|
:title="p.coords ? p.coords.map(c => c.toFixed(2)).join(', ') : ''">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user