-
This commit is contained in:
+75
-9
@@ -9,7 +9,8 @@ createApp({
|
|||||||
missionConfig: { rows: 3, cols: 3, grid: [], machines: [] },
|
missionConfig: { rows: 3, cols: 3, grid: [], machines: [] },
|
||||||
selectedMachine: null,
|
selectedMachine: null,
|
||||||
sequence: [],
|
sequence: [],
|
||||||
poseForm: { name: '', photo_type: 'front', description: '' },
|
poseForm: {},
|
||||||
|
newPoseForm: {},
|
||||||
// 地图
|
// 地图
|
||||||
mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' },
|
mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' },
|
||||||
mapMsg: '',
|
mapMsg: '',
|
||||||
@@ -45,6 +46,7 @@ createApp({
|
|||||||
agvMoveInterval: null,
|
agvMoveInterval: null,
|
||||||
agvCameraUrl: API + '/api/camera/refresh',
|
agvCameraUrl: API + '/api/camera/refresh',
|
||||||
agvCameraTimer: null,
|
agvCameraTimer: null,
|
||||||
|
armCameraTimer: null,
|
||||||
// 机型展开
|
// 机型展开
|
||||||
expandedModelId: null,
|
expandedModelId: null,
|
||||||
showAddModelModal: false,
|
showAddModelModal: false,
|
||||||
@@ -61,6 +63,11 @@ createApp({
|
|||||||
this.refreshAngles()
|
this.refreshAngles()
|
||||||
this.loadQrConfigs()
|
this.loadQrConfigs()
|
||||||
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
|
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
|
||||||
|
// 机械臂摄像头自动刷新(每2秒)
|
||||||
|
this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now()
|
||||||
|
this.armCameraTimer = setInterval(() => {
|
||||||
|
this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now()
|
||||||
|
}, 2000)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasQr() {
|
hasQr() {
|
||||||
@@ -90,6 +97,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.armCameraTimer) { clearInterval(this.armCameraTimer); this.armCameraTimer = null }
|
||||||
if (this.nav2Timer) clearInterval(this.nav2Timer)
|
if (this.nav2Timer) clearInterval(this.nav2Timer)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -280,30 +288,88 @@ createApp({
|
|||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
},
|
},
|
||||||
// === 姿态管理(属于机型)===
|
// === 姿态管理(属于机型)===
|
||||||
async addPose(modelId) {
|
async addPose(modelId, type, name) {
|
||||||
const form = this.poseForm[modelId]
|
if (!name) name = '姿态' + (((this.getModel(modelId)?.poses?.length) || 0) + 1)
|
||||||
if (!form) return
|
|
||||||
await fetch(API + '/api/models/poses/add', {
|
await fetch(API + '/api/models/poses/add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model_id: modelId,
|
model_id: modelId,
|
||||||
name: form.name || '姿态' + ((this.getModel(modelId)?.poses?.length || 0) + 1),
|
name: name,
|
||||||
photo_type: form.photo_type,
|
photo_type: type || 'front',
|
||||||
arm_angles: this.currentAngles,
|
arm_angles: this.currentAngles,
|
||||||
speed: 500,
|
speed: 500,
|
||||||
description: form.description || ''
|
description: ''
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
form.name = ''
|
const key = modelId + '_' + (type || 'front')
|
||||||
form.description = ''
|
if (this.newPoseForm[key] !== undefined) this.newPoseForm[key] = ''
|
||||||
},
|
},
|
||||||
async deletePose(modelId, poseId) {
|
async deletePose(modelId, poseId) {
|
||||||
if (!confirm('确定删除该姿态?')) return
|
if (!confirm('确定删除该姿态?')) return
|
||||||
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' })
|
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' })
|
||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
},
|
},
|
||||||
|
async refreshPoseAngles(modelId, poseId) {
|
||||||
|
if (!this.armConnected) { alert('机械臂未连接'); return }
|
||||||
|
try {
|
||||||
|
const res = await fetch(API + '/api/arm/get_angles')
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok && data.angles) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (model && model.poses) {
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (pose) {
|
||||||
|
// Update local immediately for reactive UI
|
||||||
|
pose.arm_angles = [...data.angles]
|
||||||
|
// Persist to backend
|
||||||
|
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ arm_angles: data.angles })
|
||||||
|
})
|
||||||
|
await this.loadAllModels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { alert('刷新角度失败: ' + e.message) }
|
||||||
|
},
|
||||||
|
async applyPoseAngles(modelId, poseId) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose || !pose.arm_angles) { alert('无效的姿态数据'); return }
|
||||||
|
try {
|
||||||
|
const res = await fetch(API + '/api/arm/set_angles', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ angles: pose.arm_angles, speed: 50 })
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) { alert('姿态已应用到机械臂') }
|
||||||
|
else { alert('应用失败: ' + (data.error || '未知错误')) }
|
||||||
|
} catch (e) { alert('应用姿态失败: ' + e.message) }
|
||||||
|
},
|
||||||
|
adjustPoseAngle(modelId, poseId, jointIdx, delta) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose) return
|
||||||
|
if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0]
|
||||||
|
if (!pose.arm_angles[jointIdx]) pose.arm_angles[jointIdx] = 0
|
||||||
|
pose.arm_angles[jointIdx] = Math.round((pose.arm_angles[jointIdx] + delta) * 10) / 10
|
||||||
|
this.setAngle(jointIdx, pose.arm_angles[jointIdx])
|
||||||
|
},
|
||||||
|
async updatePoseAngleAndMove(modelId, poseId, jointIdx, value) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose) return
|
||||||
|
if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0]
|
||||||
|
pose.arm_angles[jointIdx] = parseFloat(value) || 0
|
||||||
|
await this.setAngle(jointIdx, pose.arm_angles[jointIdx])
|
||||||
|
},
|
||||||
getModel(id) {
|
getModel(id) {
|
||||||
return this.models.find(m => m.id === id)
|
return this.models.find(m => m.id === id)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ createApp({
|
|||||||
missionConfig: { rows: 3, cols: 3, grid: [], machines: [] },
|
missionConfig: { rows: 3, cols: 3, grid: [], machines: [] },
|
||||||
selectedMachine: null,
|
selectedMachine: null,
|
||||||
sequence: [],
|
sequence: [],
|
||||||
poseForm: { name: '', photo_type: 'front', description: '' },
|
poseForm: {},
|
||||||
|
newPoseForm: {},
|
||||||
// 地图
|
// 地图
|
||||||
mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' },
|
mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' },
|
||||||
mapMsg: '',
|
mapMsg: '',
|
||||||
@@ -45,6 +46,7 @@ createApp({
|
|||||||
agvMoveInterval: null,
|
agvMoveInterval: null,
|
||||||
agvCameraUrl: API + '/api/camera/refresh',
|
agvCameraUrl: API + '/api/camera/refresh',
|
||||||
agvCameraTimer: null,
|
agvCameraTimer: null,
|
||||||
|
armCameraTimer: null,
|
||||||
// 机型展开
|
// 机型展开
|
||||||
expandedModelId: null,
|
expandedModelId: null,
|
||||||
showAddModelModal: false,
|
showAddModelModal: false,
|
||||||
@@ -61,6 +63,11 @@ createApp({
|
|||||||
this.refreshAngles()
|
this.refreshAngles()
|
||||||
this.loadQrConfigs()
|
this.loadQrConfigs()
|
||||||
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
|
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
|
||||||
|
// 机械臂摄像头自动刷新(每2秒)
|
||||||
|
this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now()
|
||||||
|
this.armCameraTimer = setInterval(() => {
|
||||||
|
this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now()
|
||||||
|
}, 2000)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasQr() {
|
hasQr() {
|
||||||
@@ -90,6 +97,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.armCameraTimer) { clearInterval(this.armCameraTimer); this.armCameraTimer = null }
|
||||||
if (this.nav2Timer) clearInterval(this.nav2Timer)
|
if (this.nav2Timer) clearInterval(this.nav2Timer)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -280,30 +288,88 @@ createApp({
|
|||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
},
|
},
|
||||||
// === 姿态管理(属于机型)===
|
// === 姿态管理(属于机型)===
|
||||||
async addPose(modelId) {
|
async addPose(modelId, type, name) {
|
||||||
const form = this.poseForm[modelId]
|
if (!name) name = '姿态' + (((this.getModel(modelId)?.poses?.length) || 0) + 1)
|
||||||
if (!form) return
|
|
||||||
await fetch(API + '/api/models/poses/add', {
|
await fetch(API + '/api/models/poses/add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model_id: modelId,
|
model_id: modelId,
|
||||||
name: form.name || '姿态' + ((this.getModel(modelId)?.poses?.length || 0) + 1),
|
name: name,
|
||||||
photo_type: form.photo_type,
|
photo_type: type || 'front',
|
||||||
arm_angles: this.currentAngles,
|
arm_angles: this.currentAngles,
|
||||||
speed: 500,
|
speed: 500,
|
||||||
description: form.description || ''
|
description: ''
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
form.name = ''
|
const key = modelId + '_' + (type || 'front')
|
||||||
form.description = ''
|
if (this.newPoseForm[key] !== undefined) this.newPoseForm[key] = ''
|
||||||
},
|
},
|
||||||
async deletePose(modelId, poseId) {
|
async deletePose(modelId, poseId) {
|
||||||
if (!confirm('确定删除该姿态?')) return
|
if (!confirm('确定删除该姿态?')) return
|
||||||
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' })
|
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' })
|
||||||
await this.loadAllModels()
|
await this.loadAllModels()
|
||||||
},
|
},
|
||||||
|
async refreshPoseAngles(modelId, poseId) {
|
||||||
|
if (!this.armConnected) { alert('机械臂未连接'); return }
|
||||||
|
try {
|
||||||
|
const res = await fetch(API + '/api/arm/get_angles')
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok && data.angles) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (model && model.poses) {
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (pose) {
|
||||||
|
// Update local immediately for reactive UI
|
||||||
|
pose.arm_angles = [...data.angles]
|
||||||
|
// Persist to backend
|
||||||
|
await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ arm_angles: data.angles })
|
||||||
|
})
|
||||||
|
await this.loadAllModels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { alert('刷新角度失败: ' + e.message) }
|
||||||
|
},
|
||||||
|
async applyPoseAngles(modelId, poseId) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose || !pose.arm_angles) { alert('无效的姿态数据'); return }
|
||||||
|
try {
|
||||||
|
const res = await fetch(API + '/api/arm/set_angles', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ angles: pose.arm_angles, speed: 50 })
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) { alert('姿态已应用到机械臂') }
|
||||||
|
else { alert('应用失败: ' + (data.error || '未知错误')) }
|
||||||
|
} catch (e) { alert('应用姿态失败: ' + e.message) }
|
||||||
|
},
|
||||||
|
adjustPoseAngle(modelId, poseId, jointIdx, delta) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose) return
|
||||||
|
if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0]
|
||||||
|
if (!pose.arm_angles[jointIdx]) pose.arm_angles[jointIdx] = 0
|
||||||
|
pose.arm_angles[jointIdx] = Math.round((pose.arm_angles[jointIdx] + delta) * 10) / 10
|
||||||
|
this.setAngle(jointIdx, pose.arm_angles[jointIdx])
|
||||||
|
},
|
||||||
|
async updatePoseAngleAndMove(modelId, poseId, jointIdx, value) {
|
||||||
|
const model = this.getModel(modelId)
|
||||||
|
if (!model || !model.poses) return
|
||||||
|
const pose = model.poses.find(p => p.id === poseId)
|
||||||
|
if (!pose) return
|
||||||
|
if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0]
|
||||||
|
pose.arm_angles[jointIdx] = parseFloat(value) || 0
|
||||||
|
await this.setAngle(jointIdx, pose.arm_angles[jointIdx])
|
||||||
|
},
|
||||||
getModel(id) {
|
getModel(id) {
|
||||||
return this.models.find(m => m.id === id)
|
return this.models.find(m => m.id === id)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>设置 - AGV 拍摄系统</title>
|
<title>设置 - AGV 拍摄系统</title>
|
||||||
<link rel="stylesheet" href="/static/css/style.css?v=20260520h">
|
<link rel="stylesheet" href="/static/css/style.css?v=20260520i">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
<!-- 正面姿态 -->
|
<!-- 正面姿态 -->
|
||||||
<div style="padding:16px;background:#0f1923">
|
<div style="padding:16px;background:#0f1923">
|
||||||
<h4 style="margin:0 0 12px 0;color:#388e3c">🟢 正面姿态</h4>
|
<h4 style="margin:0 0 12px 0;color:#388e3c">🟢 正面姿态</h4>
|
||||||
<div v-for="pose in m.poses.filter(p => p.photo_type === 'front')" :key="pose.id" style="background:#0f1923;padding:12px;border:1px solid #2a3441;border-radius:6px;margin-bottom:8px">
|
<div v-for="pose in (m.poses || []).filter(p => p.photo_type === 'front')" :key="pose.id" style="background:#0f1923;padding:12px;border:1px solid #2a3441;border-radius:6px;margin-bottom:8px">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||||
<strong>{% raw %}{{ pose.name || '正面姿态' }}{% endraw %}</strong>
|
<strong>{% raw %}{{ pose.name || '正面姿态' }}{% endraw %}</strong>
|
||||||
<button class="btn btn-danger btn-small" @click="deletePose(m.id, pose.id)">删除</button>
|
<button class="btn btn-danger btn-small" @click="deletePose(m.id, pose.id)">删除</button>
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
<!-- 背面姿态 -->
|
<!-- 背面姿态 -->
|
||||||
<div style="padding:16px;background:#0d1420">
|
<div style="padding:16px;background:#0d1420">
|
||||||
<h4 style="margin:0 0 12px 0;color:#d32f2f">🔴 背面姿态</h4>
|
<h4 style="margin:0 0 12px 0;color:#d32f2f">🔴 背面姿态</h4>
|
||||||
<div v-for="pose in m.poses.filter(p => p.photo_type === 'back')" :key="pose.id" style="background:#0f1923;padding:12px;border:1px solid #2a3441;border-radius:6px;margin-bottom:8px">
|
<div v-for="pose in (m.poses || []).filter(p => p.photo_type === 'back')" :key="pose.id" style="background:#0f1923;padding:12px;border:1px solid #2a3441;border-radius:6px;margin-bottom:8px">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||||
<strong>{% raw %}{{ pose.name || '背面姿态' }}{% endraw %}</strong>
|
<strong>{% raw %}{{ pose.name || '背面姿态' }}{% endraw %}</strong>
|
||||||
<button class="btn btn-danger btn-small" @click="deletePose(m.id, pose.id)">删除</button>
|
<button class="btn btn-danger btn-small" @click="deletePose(m.id, pose.id)">删除</button>
|
||||||
@@ -676,7 +676,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/vue3.global.prod.js?v=20260520h"></script>
|
<script src="/static/js/vue3.global.prod.js?v=20260520i"></script>
|
||||||
<script src="/static/js/setting.js?v=20260520h"></script>
|
<script src="/static/js/setting.js?v=20260520i"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user