feat: 巡检机器人

This commit is contained in:
zhanghao 2026-08-04 15:17:03 +08:00
parent 8d8bcb4e26
commit d2130ffaf5
2 changed files with 149 additions and 9 deletions

5
components.d.ts vendored
View File

@ -12,7 +12,6 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
@ -21,10 +20,10 @@ declare module 'vue' {
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTag: typeof import('element-plus/es')['ElTag']
IPlayer: typeof import('./src/components/IPlayer/index.vue')['default']
LandscapeGuard: typeof import('./src/components/LandscapeGuard.vue')['default']

View File

@ -3,7 +3,7 @@
<!-- 主体内容区 -->
<div class="main-wrap">
<!-- 左侧数据面板 -->
<div class="left-panel">
<div class="left-panel" :class="{ 'left-panel--dual-arm': isExplanationRobot }">
<!-- TCP 笛卡尔坐标 -->
<div class="panel-card">
<div class="row dual-row">
@ -44,7 +44,7 @@
</div>
</div>
<div class="joint-list">
<div v-if="!isExplanationRobot" 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>
@ -65,6 +65,34 @@
</div>
</div>
<div v-else class="joint-list joint-list--dual-arm">
<div class="title">关节控制</div>
<div class="arm-columns">
<div v-for="arm in armJointGroups" :key="arm.key" class="arm-column">
<div class="arm-title">{{ arm.title }}</div>
<div v-for="joint in arm.joints" :key="joint.name" class="joint-item">
<label :for="joint.name">{{ joint.name }}</label>
<el-input v-model="joint.value" readonly>
<template #prepend>
<el-button class="press-control"
@pointerdown="startPointerControl($event, () => updateJoint(joint.name, '-'))"
@pointerup="endPointerControl" @pointercancel="endPointerControl">
-
</el-button>
</template>
<template #append>
<el-button class="press-control"
@pointerdown="startPointerControl($event, () => updateJoint(joint.name, '+'))"
@pointerup="endPointerControl" @pointercancel="endPointerControl">
+
</el-button>
</template>
</el-input>
</div>
</div>
</div>
</div>
<div class="speed-container">
<div class="label">速度</div>
<div class="speed-slider" @mouseenter="hoveredSlider = 'joint'" @mouseleave="hoveredSlider = null">
@ -78,7 +106,7 @@
<!-- 中间3D机械臂空白区域 -->
<div class="center-view">
<UrdfView ref="urdfViewerRef" />
<UrdfView ref="urdfViewerRef" :modelPath="modelPath"/>
<!-- 底部功能按钮 -->
<!-- <div class="center-bottom-btns">
<el-button type="success" size="large">零力示教</el-button>
@ -91,6 +119,13 @@
<div class="right-panel">
<div class="controller">
<div class="title">末端控制</div>
<div v-if="isExplanationRobot" class="arm-selector">
<div class="arm-selector__label">控制机械臂</div>
<el-radio-group v-model="selectedEndArm" class="arm-selector__options">
<el-radio-button value="left">左臂</el-radio-button>
<el-radio-button value="right">右臂</el-radio-button>
</el-radio-group>
</div>
<div class="axis-group">
<!-- X Axis -->
<div class="axis-row x">
@ -171,6 +206,13 @@
<div class="controller">
<div class="title">姿态控制</div>
<div v-if="isExplanationRobot" class="arm-selector">
<div class="arm-selector__label">控制机械臂</div>
<el-radio-group v-model="selectedPostureArm" class="arm-selector__options">
<el-radio-button value="left">左臂</el-radio-button>
<el-radio-button value="right">右臂</el-radio-button>
</el-radio-group>
</div>
<div class="axis-group">
<!-- X Axis -->
<div class="axis-row x">
@ -254,7 +296,7 @@
</template>
<script setup>
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
import UrdfView from './UrdfView.vue'
import { getPoseApi, getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, speedLApi } from '@/api/arm'
import { Remove, CirclePlus } from '@element-plus/icons-vue'
@ -264,7 +306,31 @@ const robotStore = useRobotStore()
const deviceId = robotStore.almDeviceId
const urdfViewerRef = ref()
const jointControls = ref()
const modelPath = computed(() => {
if (isExplanationRobot.value) {
return 'upper_body/robot.urdf'
} else {
return '/inspection/elfin10/elfin10.urdf.xacro'
}
})
const jointControls = ref([])
const isExplanationRobot = computed(() => robotStore.robotType === 'explanation')
const selectedEndArm = ref('left')
const selectedPostureArm = ref('left')
const armJointGroups = computed(() => [
{
key: 'left',
title: '左臂',
joints: jointControls.value.filter(joint => /^left_arm_/i.test(joint.name))
},
{
key: 'right',
title: '右臂',
joints: jointControls.value.filter(joint => /^right_arm_/i.test(joint.name))
}
])
// TCP
const tcp = ref({
@ -512,11 +578,16 @@ onUnmounted(() => {
//
.left-panel {
width: 400px;
flex-shrink: 0;
padding: 20px;
overflow-y: auto;
background: $color-background-panel;
border-right: 1px solid $color-accent-border;
&--dual-arm {
width: 640px;
}
.panel-card {
padding: 12px;
@ -525,11 +596,27 @@ onUnmounted(() => {
gap: 20px;
margin-bottom: 12px;
.coord-item {
display: flex;
flex: 1;
align-items: center;
gap: 8px;
min-width: 0;
.key {
width: 26px;
flex-shrink: 0;
}
}
.number {
width: 170px;
flex: 1;
min-width: 0;
border-radius: 5px;
padding: 6px;
background: $color-background-card;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
@ -546,6 +633,19 @@ onUnmounted(() => {
margin-bottom: 10px;
}
.arm-columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.arm-title {
margin-bottom: 10px;
font-size: 18px;
font-weight: bold;
text-align: center;
}
.joint-item {
display: flex;
@ -607,6 +707,17 @@ onUnmounted(() => {
margin: 0;
}
}
&--dual-arm {
.joint-item {
padding: 8px;
label {
width: 82px;
font-size: 14px;
}
}
}
}
@ -625,6 +736,36 @@ onUnmounted(() => {
background: $color-background-panel;
border-left: 1px solid $color-accent-border;
.arm-selector {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
padding: 12px 14px;
border: 1px solid $color-accent-border;
border-radius: 8px;
background: $color-background-base;
&__label {
flex-shrink: 0;
font-size: 16px;
font-weight: bold;
}
&__options {
flex: 1;
:deep(.el-radio-button) {
width: 50%;
}
:deep(.el-radio-button__inner) {
width: 100%;
}
}
}
.controller {
margin-bottom: 48px;