feat:增加人工头
This commit is contained in:
parent
85be4085b8
commit
ba3acc5da8
@ -39,6 +39,10 @@ export const controlModuleList = [
|
|||||||
{
|
{
|
||||||
label: "灵巧手",
|
label: "灵巧手",
|
||||||
value: "hand"
|
value: "hand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "人工头",
|
||||||
|
value: "head"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,14 @@ export const constantRoutes = [
|
|||||||
name: "摄像头示教",
|
name: "摄像头示教",
|
||||||
meta: { title: "摄像头详情" },
|
meta: { title: "摄像头详情" },
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "head/:id",
|
||||||
|
component: () =>
|
||||||
|
import("@/views/device/register/components/Head/index.vue"),
|
||||||
|
name: "人工头示教",
|
||||||
|
meta: { title: "人工头详情" },
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "speaker/:id",
|
path: "speaker/:id",
|
||||||
|
|||||||
314
src/views/device/register/components/Head/index.vue
Normal file
314
src/views/device/register/components/Head/index.vue
Normal 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
|
||||||
|
|
||||||
|
// 左侧10个Yaw预设(对应左侧10个点)
|
||||||
|
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 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 右侧10个Pitch预设(对应右侧10个点,但Pitch值可独立调整)
|
||||||
|
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 },
|
||||||
|
|
||||||
|
// 右侧10点(Yaw偏大)
|
||||||
|
{ 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>
|
||||||
Loading…
Reference in New Issue
Block a user