2026-07-03 09:49:29 +08:00
|
|
|
|
<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="4" />
|
|
|
|
|
|
<div class="proportion">{{( speed / 4 * 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 getPose = async (robot) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPoseApi({
|
2026-07-14 09:04:31 +08:00
|
|
|
|
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
|
2026-07-03 09:49:29 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
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({
|
2026-07-14 09:04:31 +08:00
|
|
|
|
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
|
2026-07-03 09:49:29 +08:00
|
|
|
|
terminalId: props.robot.terminalId,
|
|
|
|
|
|
duration: 60,
|
|
|
|
|
|
acceleration: speed.value + 0.1,
|
|
|
|
|
|
...speedData
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 停止机械臂移动
|
|
|
|
|
|
*/
|
|
|
|
|
|
const stop = async () => {
|
|
|
|
|
|
await stopMotionApi({
|
2026-07-14 09:04:31 +08:00
|
|
|
|
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID,
|
2026-07-03 09:49:29 +08:00
|
|
|
|
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>
|