changan-inspection/src/views/inspection/cockpit/components/ManualTakeover/RoboticArm.vue

409 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="robotic-arm-box">
<div class="robotic-arm-status">
<div class="coord-display">
<div class="coord-item">
<div class="coord-key key-x">X</div>
<div class="coord-val" id="coordX">{{ pose.x?.toFixed(3) }}</div>
</div>
<div class="coord-item">
<div class="coord-key key-y">Y</div>
<div class="coord-val" id="coordY">{{ pose.y?.toFixed(3) }}</div>
</div>
<div class="coord-item">
<div class="coord-key key-z">Z</div>
<div class="coord-val" id="coordZ">{{ pose.z?.toFixed(3) }}</div>
</div>
<div class="coord-item">
<div class="coord-key key-x">RX</div>
<div class="coord-val" id="coordX">{{ pose.rx?.toFixed(3) }}</div>
</div>
<div class="coord-item">
<div class="coord-key key-y">RY</div>
<div class="coord-val" id="coordY">{{ pose.ry?.toFixed(3) }}</div>
</div>
<div class="coord-item">
<div class="coord-key key-z">RZ</div>
<div class="coord-val" id="coordZ">{{ pose.rz?.toFixed(3) }}</div>
</div>
</div>
</div>
<div class="axis-group">
<!-- X Axis -->
<div class="axis-row x">
<div class="axis-label x">X</div>
<div class="axis-slider-wrap">
<div class="axis-name">前后平移</div>
<div>
<el-button
type="primary"
circle
@mousedown="move('x', '-')"
@mouseup="stop"
@touchstart.prevent="move('x', '-')"
@touchend.prevent="stop"
>
<el-icon size="24">
<Remove />
</el-icon>
</el-button>
<el-button
type="primary"
circle
@mousedown="move('x', '+')"
@mouseup="stop"
@touchstart.prevent="move('x', '+')"
@touchend.prevent="stop"
>
<el-icon size="24">
<CirclePlus />
</el-icon>
</el-button>
</div>
</div>
</div>
<!-- Y Axis -->
<div class="axis-row y">
<div class="axis-label y">Y</div>
<div class="axis-slider-wrap">
<div class="axis-name">左右平移</div>
<div>
<el-button
type="primary"
circle
@mousedown="move('y', '-')"
@mouseup="stop"
@touchstart.prevent="move('y', '-')"
@touchend.prevent="stop"
>
<el-icon size="24">
<Remove />
</el-icon>
</el-button>
<el-button
type="primary"
circle
@mousedown="move('y', '+')"
@mouseup="stop"
@touchstart.prevent="move('y', '+')"
@touchend.prevent="stop"
>
<el-icon size="24">
<CirclePlus />
</el-icon>
</el-button>
</div>
</div>
</div>
<!-- Z Axis -->
<div class="axis-row z">
<div class="axis-label z">Z</div>
<div class="axis-slider-wrap">
<div class="axis-name">上下平移</div>
<div>
<el-button
type="primary"
circle
@mousedown="move('z', '-')"
@mouseup="stop"
@touchstart.prevent="move('z', '-')"
@touchend.prevent="stop"
>
<el-icon size="24">
<Remove />
</el-icon>
</el-button>
<el-button
type="primary"
circle
@mousedown="move('z', '+')"
@mouseup="stop"
@touchstart.prevent="move('z', '+')"
@touchend.prevent="stop"
>
<el-icon size="24">
<CirclePlus />
</el-icon>
</el-button>
</div>
</div>
</div>
</div>
<div class="speed-container">
<div class="label">速度</div>
<el-slider v-model="speed" :min="0.1" :step="0.1" :max="2" />
<div class="proportion">{{( speed / 2 * 100).toFixed(0) }} %</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { getPoseApi, speedLApi, stopMotionApi } from '@/api/inspection/cockpit';
import { reactive } from 'vue';
const props = defineProps({
robot: {
type: Object,
default: () => ({})
},
type: {
type: String,
default: 'end'
}
})
const pose = ref({
x: 0,
y: 0,
z: 0,
rx: 0,
ry: 0,
rz: 0
})
const speed = ref(1) // 速度百分比,范围 0.1 - 5
const errorNum = ref(0)
/**
* 获取机械臂末端状态信息
*/
const getPose = async (robot) => {
try {
const res = await getPoseApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
terminalId: robot.terminalId
})
if (res.code === 200) {
const { x, y, z, rx, ry, rz } = res.data
pose.value = { x, y, z, rx, ry, rz }
} else {
console.error('获取姿态失败:', res.message)
errorNum.value++
if (errorNum.value > 3) {
clearInterval(timer.value)
}
}
} catch (error) {
errorNum.value++
if (errorNum.value > 3) {
clearInterval(timer.value)
}
console.error('获取姿态失败:', error)
}
}
const speedData = reactive({
vx: 0,
vy: 0,
vz: 0,
wx: 0,
wy: 0,
wz: 0
})
/**
* 机械臂移动
* @param axis 坐标轴
* @param direction 方向
*/
const move = async (axis, direction) => {
speedData.vx = 0
speedData.vy = 0
speedData.vz = 0
speedData.wx = 0
speedData.wy = 0
speedData.wz = 0
if (props.type === 'end') {
// 末端移动
const speedValue = speed.value
switch (axis) {
case 'x':
speedData.vx = direction === '+' ? speedValue : -speedValue
break
case 'y':
speedData.vy = direction === '+' ? speedValue : -speedValue
break
case 'z':
speedData.vz = direction === '+' ? speedValue : -speedValue
break
}
} else if (props.type === 'posture') {
// 关节移动
const jointSpeed = speed.value
switch (axis) {
case 'x':
speedData.wx = direction === '+' ? jointSpeed : -jointSpeed
break
case 'y':
speedData.wy = direction === '+' ? jointSpeed : -jointSpeed
break
case 'z':
speedData.wz = direction === '+' ? jointSpeed : -jointSpeed
break
}
}
speedLApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
terminalId: props.robot.terminalId,
duration: 60,
acceleration: speed.value + 0.1,
...speedData
})
}
/**
* 停止机械臂移动
*/
const stop = async () => {
await stopMotionApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
terminalId: props.robot.terminalId
})
}
const timer = ref(null)
watch(() => props.robot, (newRobot) => {
if (newRobot && newRobot.terminalId) {
if (timer.value) {
clearInterval(timer.value)
}
timer.value = setInterval(() => {
getPose(newRobot)
}, 100)
}
}, { immediate: true })
</script>
<style lang="scss" scoped>
.robotic-arm-box {
margin-top: 12px;
.robotic-arm-status {
.coord-display {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px;
background: #070b1a;
border: 1px solid #1c2a52;
border-radius: 8px;
margin-bottom: 16px;
.coord-item {
width: calc((100% - 24px) / 3);
text-align: center;
padding: 6px 0;
border-radius: 6px;
background: rgba(255, 255, 255, 0.02);
.coord-key {
font-size: 12px;
font-weight: bold;
margin-bottom: 3px;
}
.key-x {
color: #f87171;
}
.key-y {
color: #34d399;
;
}
.key-z {
color: #38bdf8;
;
}
.coord-val {
font-size: 14px;
font-weight: bold;
color: #e8edf8;
}
}
}
}
.axis-group {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
.axis-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: #0c1228;
border: 1px solid #1c2a52;
border-radius: 10px;
transition: all 0.2s ease;
&:hover {
border-color: #2a3f7a;
background: #3b82f608;
}
.axis-label {
width: 28px;
height: 28px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
flex-shrink: 0;
}
.x {
background: #f8717126;
color: #f87171;
border: 1px solid #f871714d;
}
.y {
background: #34d39926;
color: #34d399;
border: 1px solid #34d3994d;
}
.z {
background: #38bdf826;
color: #38bdf8;
border: 1px solid #38bdf84d;
}
.axis-slider-wrap {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
color: #fff;
}
}
}
.speed-container {
display: flex;
align-items: center;
justify-content: space-between;
.label,
.proportion {
width: 100px;
text-align: center;
color: #fff;
}
}
}
</style>