From 4fd124e4bd5799e10b3d8bfe01a4ab9435031836 Mon Sep 17 00:00:00 2001 From: ywb <347742090@qq.com> Date: Sun, 17 May 2026 21:54:22 +0800 Subject: [PATCH] fix: add CSS rotate transform to map-container div (was missing, causing no visual rotation) --- agv_app/setting.html | 2 +- agv_app/templates/setting.js | 356 +++++++++++++++++++++++++++-------- 2 files changed, 279 insertions(+), 79 deletions(-) diff --git a/agv_app/setting.html b/agv_app/setting.html index 0e19716..a3c88ed 100644 --- a/agv_app/setting.html +++ b/agv_app/setting.html @@ -56,7 +56,7 @@ -
+
diff --git a/agv_app/templates/setting.js b/agv_app/templates/setting.js index 217f9e8..e20f9de 100644 --- a/agv_app/templates/setting.js +++ b/agv_app/templates/setting.js @@ -8,12 +8,18 @@ createApp({ // 任务配置 missionConfig: { rows: 3, cols: 3, grid: [], machines: [] }, selectedMachine: null, + sequence: [], + poseForm: { name: '', photo_type: 'front', description: '' }, // 地图 mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' }, mapMsg: '', mapLoaded: false, mapImageUrl: '', mapMeta: null, + mapRotation: 0, + mapVersion: 0, + navCurrentPos: null, + nav2Available: false, // 点位 points: [], newPointName: '', @@ -24,7 +30,6 @@ createApp({ selectedModelId: null, newModelName: '', newModelSerial: '', - poseForm: {}, // 机械臂 armConnected: false, currentAngles: [], @@ -45,6 +50,7 @@ createApp({ mounted() { this.refresh() this.refreshAngles() + this.nav2Timer = setInterval(this.refreshNavStatus, 3000) }, watch: { tab(val) { @@ -63,6 +69,7 @@ createApp({ beforeUnmount() { Object.values(this.jogIntervals).forEach(i => clearInterval(i)) if (this.agvCameraTimer) clearInterval(this.agvCameraTimer) + if (this.nav2Timer) clearInterval(this.nav2Timer) }, methods: { async refresh() { @@ -73,7 +80,6 @@ createApp({ this.armConnected = data.arm_connected this.cameraOpened = data.camera_opened this.mapLoaded = data.map_loaded - // 如果地图已加载,自动获取地图图像和元数据 if (data.map_loaded) { this.mapImageUrl = API + '/api/map/image?t=' + Date.now() try { @@ -85,6 +91,8 @@ createApp({ } catch (e) {} await this.loadAllPoints() await this.loadAllModels() + await this.loadAllMachines() + await this.loadMissionConfig() }, // === 地图 === async loadMap() { @@ -108,6 +116,51 @@ createApp({ onMapError() { this.mapMsg = '❌ 地图图像加载失败' }, + rotateMap(deg) { + 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) { if (!coords || !this.mapMeta) return 50 const [x, y, yaw] = coords @@ -177,7 +230,6 @@ createApp({ const res = await fetch(API + '/api/models/list') const data = await res.json() this.models = data.models || [] - // 初始化 poseForm this.models.forEach(m => { if (!this.poseForm[m.id]) { this.poseForm[m.id] = { name: '', photo_type: 'front', description: '' } @@ -234,61 +286,55 @@ createApp({ getModel(id) { return this.models.find(m => m.id === id) }, - // === 机械臂 === - - clearSelection() { this.selectedMachine = null }, - async saveMachineCoords() { - if (!this.selectedMachine) return + // === 任务配置 === + async loadMissionConfig() { try { - const res = await fetch(API + '/api/mission/machines/' + this.selectedMachine.id, { - method: 'PATCH', + const res = await fetch(API + '/api/mission/config') + const data = await res.json() + if (data.ok && data.config) { + this.missionConfig.rows = data.config.rows || 3 + this.missionConfig.cols = data.config.cols || 3 + this.missionConfig.grid = data.config.grid || [] + } + } catch (e) { console.error('加载任务配置失败', e) } + }, + async generateGrid() { + try { + const res = await fetch(API + '/api/mission/config', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - front: this.selectedMachine.front, - back: this.selectedMachine.back + rows: this.missionConfig.rows, + cols: this.missionConfig.cols, + grid: [] }) }) - if (res.ok) { - this.mapMsg = '✅ 机器坐标已保存' - setTimeout(() => this.mapMsg = '', 2000) + const data = await res.json() + if (data.ok) { + this.missionConfig.grid = data.config.grid || [] + alert('✅ 网格已生成 (' + this.missionConfig.rows + '×' + this.missionConfig.cols + ')') } else { - alert('保存失败: ' + res.status) + alert('❌ 网格生成失败') + } + } catch (e) { alert('请求失败: ' + e.message) } + }, + async saveMissionConfig() { + try { + const res = await fetch(API + '/api/mission/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + rows: this.missionConfig.rows, + cols: this.missionConfig.cols, + grid: this.missionConfig.grid + }) + }) + const data = await res.json() + if (data.ok) { + alert('✅ 网格配置已保存') } } catch (e) { alert('保存失败: ' + e.message) } }, - selectMachine(machine) { - // 确保 front/back 永远有 coords 数组,避免 v-model 赋值失败 - if (!machine.front) machine.front = { coords: [0, 0, 0], poses: [] } - else if (!Array.isArray(machine.front.coords)) machine.front.coords = [0, 0, 0] - if (!machine.back) machine.back = { coords: [0, 0, 0], poses: [] } - else if (!Array.isArray(machine.back.coords)) machine.back.coords = [0, 0, 0] - this.selectedMachine = machine - console.log('selectedMachine:', machine) - }, - onCellClick(ri, ci) { - let m = this.getMachineAt(ri, ci) - if (!m) { - // 自动创建机器记录 - this.createMachine(ri, ci).then(() => { - m = this.getMachineAt(ri, ci) - if (m) this.selectMachine(m) - }) - } else { - this.selectMachine(m) - } - }, - async createMachine(ri, ci) { - try { - const res = await fetch(API + '/api/mission/machines', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ row: ri, col: ci, front: { coords: [0, 0, 0], poses: [] }, back: { coords: [0, 0, 0], poses: [] } }) - }) - await this.loadAllMachines() - return res.ok - } catch (e) { alert('创建机器失败: ' + e.message); return false } - }, - async loadAllMachines() { try { const res = await fetch(API + '/api/mission/machines') @@ -308,6 +354,189 @@ createApp({ const prevMachine = this.getMachineAt(ri - 1, ci) return prevMachine ? prevMachine.back : machine.front }, + onCellClick(ri, ci) { + const m = this.getMachineAt(ri, ci) + if (!m) { + // 无机器 → 创建机器记录并选中 + this.createMachine(ri, ci).then(ok => { + if (ok) { + const created = this.getMachineAt(ri, ci) + if (created) this.selectMachine(created) + } + }) + } else { + // 有机器 → 选中 + this.selectMachine(m) + } + }, + async createMachine(ri, ci) { + try { + const machineId = 'm_' + ri + '_' + ci + const res = await fetch(API + '/api/mission/machines/add', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: machineId, + row: ri, + col: ci, + front: { coords: [0, 0, 0], poses: [] }, + back: { coords: [0, 0, 0], poses: [] } + }) + }) + const data = await res.json() + if (!data.ok && data.error !== '该位置已有机器') { + alert('创建机器失败: ' + (data.error || '未知错误')) + return false + } + await this.loadAllMachines() + return true + } catch (e) { alert('创建机器失败: ' + e.message); return false } + }, + selectMachine(machine) { + if (!machine.front) machine.front = { coords: [0, 0, 0], poses: [] } + else if (!Array.isArray(machine.front.coords)) machine.front.coords = [0, 0, 0] + if (!machine.back) machine.back = { coords: [0, 0, 0], poses: [] } + else if (!Array.isArray(machine.back.coords)) machine.back.coords = [0, 0, 0] + this.selectedMachine = machine + }, + clearSelection() { + this.selectedMachine = null + }, + async deleteMachine(machineId) { + if (!confirm('确定删除此机器?')) return + try { + await fetch(API + '/api/mission/machines/' + machineId, { method: 'DELETE' }) + this.selectedMachine = null + await this.loadAllMachines() + } catch (e) { alert('删除失败: ' + e.message) } + }, + async saveMachineCoords() { + if (!this.selectedMachine) return + try { + const res = await fetch(API + '/api/mission/machines/' + this.selectedMachine.id, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + front: this.selectedMachine.front, + back: this.selectedMachine.back + }) + }) + if (res.ok) { + this.mapMsg = '✅ 机器坐标已保存' + setTimeout(() => this.mapMsg = '', 2000) + } else { + alert('保存失败: ' + res.status) + } + } catch (e) { alert('保存失败: ' + e.message) } + }, + async readPosition(side) { + if (!this.agvConnected) { alert('AGV 未连接'); return } + try { + const res = await fetch(API + '/api/agv/position') + const data = await res.json() + if (data.ok && data.position) { + const [x, y, theta] = data.position + if (side === 'front') { + this.selectedMachine.front.coords = [x, y, theta] + } else { + this.selectedMachine.back.coords = [x, y, theta] + } + } else { + alert('读取位置失败: ' + (data.error || '未知错误')) + } + } catch (e) { alert('读取位置失败: ' + e.message) } + }, + async addPoseToMachine(machineId, side) { + const name = this.poseForm.name || '姿态' + (((this.selectedMachine && this.selectedMachine[side] && this.selectedMachine[side].poses) || []).length + 1) + try { + const res = await fetch(API + '/api/mission/poses/' + machineId + '/' + side, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: name, + arm_angles: this.currentAngles.length === 6 ? this.currentAngles : [0, 0, 0, 0, 0, 0], + speed: 500, + description: '' + }) + }) + const data = await res.json() + if (data.ok) { + this.poseForm.name = '' + await this.loadAllMachines() + // 重新选中当前机器以刷新姿态列表 + const updated = this.getMachineAt(this.selectedMachine.row, this.selectedMachine.col) + if (updated) this.selectMachine(updated) + } else { + alert('添加姿态失败: ' + (data.error || '未知错误')) + } + } catch (e) { alert('添加姿态失败: ' + e.message) } + }, + async deletePose(machineId, side, poseId) { + if (!confirm('确定删除此姿态?')) return + try { + await fetch(API + '/api/mission/poses/' + machineId + '/' + side + '/' + poseId, { method: 'DELETE' }) + await this.loadAllMachines() + if (this.selectedMachine) { + const updated = this.getMachineAt(this.selectedMachine.row, this.selectedMachine.col) + if (updated) this.selectMachine(updated) + } + } catch (e) { alert('删除姿态失败: ' + e.message) } + }, + async capturePosition(ri, ci, side) { + if (!this.agvConnected) { alert('请先连接AGV'); return } + let machine = this.getMachineAt(ri, ci) + if (!machine) { + try { + const machineId = 'm_' + ri + '_' + ci + const res = await fetch(API + '/api/mission/machines/add', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: machineId, + row: ri, + col: ci, + front: { coords: [0, 0, 0], poses: [] }, + back: { coords: [0, 0, 0], poses: [] } + }) + }) + if (!res.ok) throw new Error('创建失败') + await this.loadAllMachines() + machine = this.getMachineAt(ri, ci) + } catch (e) { alert('创建机器失败: ' + e.message); return } + } + try { + const res = await fetch(API + '/api/agv/position') + const pos = await res.json() + let x = 0, y = 0, theta = 0 + if (pos.ok && pos.position && Array.isArray(pos.position)) { + x = pos.position[0] || 0 + y = pos.position[1] || 0 + theta = pos.position[2] || 0 + } else { + alert('读取位置失败: ' + (pos.error || '未知错误')) + return + } + if (!machine) { machine = this.getMachineAt(ri, ci) } + if (!machine) { alert('机器记录不存在'); return } + if (side === 'front') { machine.front.coords = [x, y, theta] } else { machine.back.coords = [x, y, theta] } + await fetch(API + '/api/mission/machines/' + machine.id, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(machine) + }) + alert((side === 'front' ? '正面' : '背面') + '点位已更新: (' + x.toFixed(2) + ',' + y.toFixed(2) + ',' + theta.toFixed(2) + ')') + } catch (e) { alert('读取位置失败: ' + e.message) } + }, + async refreshSequence() { + try { + const res = await fetch(API + '/api/mission/generate_sequence') + const data = await res.json() + if (data.ok) { + this.sequence = data.sequence || [] + } + } catch (e) { console.error('刷新序列失败', e) } + }, + // === 机械臂 === async refreshAngles() { if (!this.armConnected) return try { @@ -405,34 +634,5 @@ createApp({ alert('❌ 复位请求失败: ' + e.message) } }, - async capturePosition(ri, ci, side) { - if (!this.agvConnected) { alert('请先连接AGV'); return } - let machine = this.getMachineAt(ri, ci) - if (!machine) { - try { - const res = await fetch(API + '/api/mission/machines', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ row: ri, col: ci, front: { coords: [0, 0, 0], poses: [] }, back: { coords: [0, 0, 0], poses: [] } }) - }) - if (!res.ok) throw new Error('创建失败') - await this.loadAllMachines() - machine = this.getMachineAt(ri, ci) - } catch (e) { alert('创建机器失败: ' + e.message); return } - } - try { - const res = await fetch(API + '/api/agv/position') - const pos = await res.json() - let x = 0, y = 0, theta = 0 - if (pos.position && Array.isArray(pos.position)) { x = pos.position[0]||0; y = pos.position[1]||0; theta = pos.position[2]||0 } - if (!machine) { machine = this.getMachineAt(ri, ci) } - if (!machine) { alert('机器记录不存在'); return } - if (side === 'front') { machine.front.coords = [x, y, theta] } else { machine.back.coords = [x, y, theta] } - await fetch(API + '/api/mission/machines/' + machine.id, { - method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(machine) - }) - alert((side==='front'?'正面':'背面')+'点位已更新: ('+x.toFixed(2)+','+y.toFixed(2)+','+theta.toFixed(2)+')') - } catch (e) { alert('读取位置失败: '+e.message) } - }, } }).mount('#app')