2026-06-29 16:19:37 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="robot-teach-container">
|
|
|
|
|
|
<!-- 主体内容区 -->
|
|
|
|
|
|
<div class="main-wrap">
|
|
|
|
|
|
<!-- 左侧数据面板 -->
|
|
|
|
|
|
<div class="left-panel">
|
|
|
|
|
|
<!-- TCP 笛卡尔坐标 -->
|
|
|
|
|
|
<div class="panel-card">
|
|
|
|
|
|
<div class="row dual-row">
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">X</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.x" suffix="mm" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">RX</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.rx" suffix="°" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row dual-row">
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">Y</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.y" suffix="mm" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">RY</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.ry" suffix="°" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row dual-row">
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">Z</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.z" suffix="mm" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="coord-item">
|
|
|
|
|
|
<span class="key">RZ</span>
|
|
|
|
|
|
<el-input v-model.number="tcp.rz" suffix="°" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="joint-list">
|
|
|
|
|
|
<div class="title">关节控制</div>
|
|
|
|
|
|
<div v-for="joint in jointControls" :key="joint.name" class="joint-item">
|
|
|
|
|
|
<label :for="joint.name">{{ joint.name }}</label>
|
|
|
|
|
|
<el-slider v-model="joint.value" :min="joint.min" :max="joint.max" :step="0.01" @input="updateJoint(joint.name, joint.value)"/>
|
|
|
|
|
|
<span class="angle">{{ (joint.value * 180 / Math.PI).toFixed(1) }}°</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="speed-container">
|
|
|
|
|
|
<div class="label">速度:</div>
|
|
|
|
|
|
<el-slider v-model="speed" :min="1" :max="100" />
|
|
|
|
|
|
<div class="proportion">{{ speed }} %</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 中间3D机械臂空白区域 -->
|
|
|
|
|
|
<div class="center-view">
|
|
|
|
|
|
<UrdfView ref="urdfViewerRef" />
|
|
|
|
|
|
<!-- 底部功能按钮 -->
|
|
|
|
|
|
<!-- <div class="center-bottom-btns">
|
|
|
|
|
|
<el-button type="success" size="large">零力示教</el-button>
|
|
|
|
|
|
<el-button size="large">回到原点</el-button>
|
|
|
|
|
|
<el-button size="large">Z轴对齐</el-button>
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧摇杆操作面板 -->
|
|
|
|
|
|
<div class="right-panel">
|
|
|
|
|
|
<div class="controller">
|
|
|
|
|
|
<div class="title">末端控制</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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<el-icon size="24"><CirclePlus /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="controller">
|
|
|
|
|
|
<div class="title">姿态控制</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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<el-icon size="24"><Remove /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="primary" circle >
|
|
|
|
|
|
<el-icon size="24"><CirclePlus /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { nextTick, onMounted, ref } from 'vue'
|
|
|
|
|
|
import UrdfView from './UrdfView.vue'
|
|
|
|
|
|
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
|
|
|
|
const urdfViewerRef = ref()
|
|
|
|
|
|
const jointControls = ref()
|
|
|
|
|
|
|
|
|
|
|
|
// TCP坐标数据
|
|
|
|
|
|
const tcp = ref({
|
|
|
|
|
|
x: 46.691,
|
|
|
|
|
|
y: 385.321,
|
|
|
|
|
|
z: 141.877,
|
|
|
|
|
|
rx: -174.595,
|
|
|
|
|
|
ry: 4.212,
|
|
|
|
|
|
rz: -72.937
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 速度
|
|
|
|
|
|
const speed = ref(56)
|
|
|
|
|
|
|
|
|
|
|
|
const updateJoint = (jointName, angle) => {
|
|
|
|
|
|
urdfViewerRef.value.updateJoint(jointName, angle)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
jointControls.value = urdfViewerRef.value.jointControls
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.robot-teach-container {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: $bg-deep;
|
|
|
|
|
|
color: $text;
|
|
|
|
|
|
font-family: $font-b;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
|
|
|
|
|
|
// 主体三栏布局
|
|
|
|
|
|
.main-wrap {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
// 左侧面板
|
|
|
|
|
|
.left-panel {
|
|
|
|
|
|
width: 400px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
// background: #030729;
|
|
|
|
|
|
background: $bg-body;
|
|
|
|
|
|
border-right: 1px solid rgba(0, 195, 255, 0.08);
|
|
|
|
|
|
|
|
|
|
|
|
.panel-card {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.joint-list {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
// background: #161933;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
color: #ccc;
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.joint-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border: 1px solid rgba(0, 195, 255, 0.08);
|
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
background: #0a0b10;
|
|
|
|
|
|
|
2026-06-30 17:32:54 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #2a3f7a;
|
|
|
|
|
|
background: rgba(59, 130, 246, 0.031372549);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 16:19:37 +08:00
|
|
|
|
label {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
input[type=range] {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.angle {
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.speed-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
|
|
|
|
.label,
|
|
|
|
|
|
.proportion {
|
|
|
|
|
|
width: 100px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 中间3D区域
|
|
|
|
|
|
.center-view {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 右侧摇杆面板
|
|
|
|
|
|
.right-panel {
|
|
|
|
|
|
width: 400px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
// background: #030729;
|
|
|
|
|
|
background: $bg-body;
|
|
|
|
|
|
border-left: 1px solid rgba(0, 195, 255, 0.08);
|
|
|
|
|
|
|
|
|
|
|
|
.controller {
|
|
|
|
|
|
margin-bottom: 60px;
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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: #0a0b10;
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// element-plus深色输入框覆盖
|
|
|
|
|
|
:deep(.el-input__wrapper) {
|
|
|
|
|
|
background-color: #2d323e;
|
|
|
|
|
|
box-shadow: $shadow;
|
|
|
|
|
|
|
|
|
|
|
|
.el-input__inner {
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-slider__runway) {
|
|
|
|
|
|
background: #333842;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|