feat:增加人工头

This commit is contained in:
lixiaolong 2026-01-20 10:01:12 +08:00
parent 85be4085b8
commit ba3acc5da8
3 changed files with 326 additions and 0 deletions

View File

@ -39,6 +39,10 @@ export const controlModuleList = [
{
label: "灵巧手",
value: "hand"
},
{
label: "人工头",
value: "head"
}
]

View File

@ -77,6 +77,14 @@ export const constantRoutes = [
name: "摄像头示教",
meta: { title: "摄像头详情" },
hidden: true,
},
{
path: "head/:id",
component: () =>
import("@/views/device/register/components/Head/index.vue"),
name: "人工头示教",
meta: { title: "人工头详情" },
hidden: true,
},
{
path: "speaker/:id",

View File

@ -0,0 +1,314 @@
<template>
<div class="head-control">
<h2 class="title">人工头舵机控制</h2>
<div class="main-content">
<!-- 左侧10个Yaw预设输入框 -->
<div class="preset-panel left-panel">
<h3>Yaw 预设位左右转头</h3>
<div v-for="(preset, index) in leftPresets" :key="index" class="preset-item">
<span class="label"> {{ index + 1 }}</span>
<el-input-number
v-model="preset.value"
:min="0"
:max="1"
:step="0.01"
:precision="2"
size="small"
controls-position="right"
@change="onPresetChange"
/>
</div>
<el-button type="primary" size="small" @click="applyYawFromPresets">应用当前 Yaw</el-button>
</div>
<!-- 中央图片 + 20个控制点 -->
<div class="image-container">
<img
src="https://cdn.head-acoustics.com/_processed_/3/5/csm_HMS_V_frontal_seitlich_67c86d4fad.jpg"
alt="人工头"
class="head-image"
/>
<!-- 20 个控制点 -->
<div
v-for="(point, index) in points"
:key="index"
class="control-point"
:style="{ left: point.x + '%', top: point.y + '%' }"
:class="{ active: point.active }"
@click="applyPreset(index)"
:title="`点 ${index + 1}: Yaw=${point.yaw.toFixed(2)}, Pitch=${point.pitch.toFixed(2)}`"
>
{{ index + 1 }}
</div>
</div>
<!-- 右侧10个Pitch预设输入框 -->
<div class="preset-panel right-panel">
<h3>Pitch 预设位上下点头</h3>
<div v-for="(preset, index) in rightPresets" :key="index" class="preset-item">
<span class="label"> {{ index + 1 }}</span>
<el-input-number
v-model="preset.value"
:min="0"
:max="1"
:step="0.01"
:precision="2"
size="small"
controls-position="right"
@change="onPresetChange"
/>
</div>
<el-button type="primary" size="small" @click="applyPitchFromPresets">应用当前 Pitch</el-button>
</div>
</div>
<!-- 当前值显示与下发 -->
<div class="current-display">
<div class="current-item">
<span>当前 Yaw</span>
<span class="value">{{ servo1.toFixed(2) }}</span>
</div>
<div class="current-item">
<span>当前 Pitch</span>
<span class="value">{{ servo2.toFixed(2) }}</span>
</div>
<el-button type="primary" @click="sendServoValues">立即下发舵机值</el-button>
<el-button @click="resetToCenter">复位到中心 (0.50, 0.50)</el-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// import { setServo } from '@/api/device/head'; //
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
const deviceId = ref('head1');
const servo1 = ref(0.5); // Yaw
const servo2 = ref(0.5); // Pitch
// 10Yaw10
const leftPresets = ref([
{ value: 0.10 }, { value: 0.15 }, { value: 0.08 }, { value: 0.12 }, { value: 0.18 },
{ value: 0.05 }, { value: 0.10 }, { value: 0.15 }, { value: 0.08 }, { value: 0.12 }
]);
// 10Pitch10Pitch
const rightPresets = ref([
{ value: 0.20 }, { value: 0.30 }, { value: 0.40 }, { value: 0.50 }, { value: 0.60 },
{ value: 0.70 }, { value: 0.80 }, { value: 0.90 }, { value: 0.95 }, { value: 1.00 }
]);
//
const onPresetChange = () => {
// points yaw/pitch
leftPresets.value.forEach((p, i) => {
points.value[i].yaw = p.value;
});
rightPresets.value.forEach((p, i) => {
points.value[i + 10].pitch = p.value;
});
};
//
const applyPreset = (index) => {
points.value.forEach(p => p.active = false);
points.value[index].active = true;
const yaw = points.value[index].yaw;
const pitch = points.value[index].pitch;
servo1.value = yaw;
servo2.value = pitch;
// /
if (index < 10) {
leftPresets.value.forEach((p, i) => p.value = i === index ? yaw : p.value);
} else {
rightPresets.value.forEach((p, i) => p.value = i === (index - 10) ? pitch : p.value);
}
sendServoValues();
};
// Yaw
let lastLeftIndex = null;
let lastRightIndex = null;
// Yaw使
// Yaw
const applyYawFromPresets = () => {
servo1.value = leftPresets.value[0].value; // 使
sendServoValues();
};
const applyPitchFromPresets = () => {
servo2.value = rightPresets.value[0].value; // 使
sendServoValues();
};
const sendServoValues = async () => {
try {
// await setServo({
// terminalId: terminalId.value,
// deviceId: deviceId.value,
// servo1: servo1.value,
// servo2: servo2.value,
// });
} catch (error) {
console.error('舵机下发失败:', error);
}
};
const resetToCenter = () => {
servo1.value = 0.5;
servo2.value = 0.5;
points.value.forEach(p => p.active = false);
sendServoValues();
};
// 20
const points = ref([
// 10
{ x: 18, y: 20, yaw: 0.10, pitch: 0.20, active: false },
{ x: 22, y: 30, yaw: 0.15, pitch: 0.30, active: false },
{ x: 15, y: 40, yaw: 0.08, pitch: 0.40, active: false },
{ x: 20, y: 50, yaw: 0.12, pitch: 0.50, active: false },
{ x: 25, y: 60, yaw: 0.18, pitch: 0.60, active: false },
{ x: 12, y: 70, yaw: 0.05, pitch: 0.70, active: false },
{ x: 18, y: 80, yaw: 0.10, pitch: 0.80, active: false },
{ x: 22, y: 90, yaw: 0.15, pitch: 0.90, active: false },
{ x: 15, y: 95, yaw: 0.08, pitch: 0.95, active: false },
{ x: 20, y: 98, yaw: 0.12, pitch: 1.00, active: false },
// 10Yaw
{ x: 82, y: 20, yaw: 0.90, pitch: 0.20, active: false },
{ x: 78, y: 30, yaw: 0.85, pitch: 0.30, active: false },
{ x: 85, y: 40, yaw: 0.92, pitch: 0.40, active: false },
{ x: 80, y: 50, yaw: 0.88, pitch: 0.50, active: false },
{ x: 75, y: 60, yaw: 0.82, pitch: 0.60, active: false },
{ x: 88, y: 70, yaw: 0.95, pitch: 0.70, active: false },
{ x: 82, y: 80, yaw: 0.90, pitch: 0.80, active: false },
{ x: 78, y: 90, yaw: 0.85, pitch: 0.90, active: false },
{ x: 85, y: 95, yaw: 0.92, pitch: 0.95, active: false },
{ x: 80, y: 98, yaw: 0.88, pitch: 1.00, active: false },
]);
</script>
<style scoped>
.head-control {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
background: #fff;
border-radius: 20px;
height: calc(100vh - 125px);
display: flex;
flex-direction: column;
}
.title {
text-align: center;
margin-bottom: 20px;
font-size: 24px;
}
.main-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
}
.preset-panel {
width: 220px;
background: #f5f7fa;
padding: 20px;
border-radius: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.preset-panel h3 {
margin: 0 0 10px 0;
text-align: center;
font-size: 16px;
}
.preset-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.label {
width: 50px;
font-size: 14px;
}
.image-container {
position: relative;
max-width: 600px;
}
.head-image {
width: 100%;
height: auto;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.control-point {
position: absolute;
width: 40px;
height: 40px;
background: rgba(64, 158, 255, 0.8);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
transform: translate(-50%, -50%);
transition: all 0.2s;
border: 3px solid #fff;
}
.control-point:hover {
background: #409eff;
transform: translate(-50%, -50%) scale(1.2);
}
.control-point.active {
background: #f56c6c;
transform: translate(-50%, -50%) scale(1.4);
}
.current-display {
display: flex;
justify-content: center;
align-items: center;
gap: 40px;
margin-top: 30px;
flex-wrap: wrap;
}
.current-item {
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
}
.value {
font-weight: bold;
color: #409eff;
min-width: 60px;
}
</style>