133 lines
3.0 KiB
Vue
133 lines
3.0 KiB
Vue
|
|
<template>
|
||
|
|
<div class="container">
|
||
|
|
<div class="joint-control" v-for="joint in jointList" :key="joint.name">
|
||
|
|
<div class="joint-header">
|
||
|
|
<span class="joint-name">{{ joint.displayName }}</span>
|
||
|
|
<span class="joint-type">{{ joint.type.toUpperCase() }}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="joint-info">
|
||
|
|
<span class="angle-display">{{ formatAngle(joint.value) }}</span>
|
||
|
|
<span class="limits">[{{ formatAngle(joint.min) }}, {{ formatAngle(joint.max) }}]</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="slider-container">
|
||
|
|
<el-slider size="small" v-model="joint.value" :min="joint.min" :max="joint.max" :step="joint.step" @input="(value) => updateJointAngle(joint.name, value)"/>
|
||
|
|
<div class="slider-labels">
|
||
|
|
<span>{{ formatAngle(joint.min) }}</span>
|
||
|
|
<span>{{ formatAngle(joint.max) }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
<script setup>
|
||
|
|
const props = defineProps({
|
||
|
|
jointList: {
|
||
|
|
type: Array,
|
||
|
|
default: () => []
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
const emits = defineEmits(['updateJointAngle'])
|
||
|
|
|
||
|
|
const updateJointAngle = (name, value) => {
|
||
|
|
emits('updateJointAngle', { name, value })
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 角度格式化
|
||
|
|
*/
|
||
|
|
const formatAngle = (radians) => {
|
||
|
|
const degrees = (radians * 180 / Math.PI).toFixed(1)
|
||
|
|
return `${degrees}°`
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.container {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
|
||
|
|
.joint-control {
|
||
|
|
border-radius: 6px;
|
||
|
|
padding: 15px;
|
||
|
|
margin-bottom: 15px;
|
||
|
|
border: 1px solid #cfcfcf;
|
||
|
|
|
||
|
|
.joint-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
|
||
|
|
.joint-name {
|
||
|
|
font-weight: 600;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.joint-type {
|
||
|
|
font-size: 11px;
|
||
|
|
background: rgba(52, 152, 219, 0.3);
|
||
|
|
color: #3498db;
|
||
|
|
padding: 3px 8px;
|
||
|
|
border-radius: 12px;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.joint-info {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
font-size: 13px;
|
||
|
|
|
||
|
|
.angle-display {
|
||
|
|
color: #f1c40f;
|
||
|
|
font-weight: 600;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.limits {
|
||
|
|
color: #95a5a6;
|
||
|
|
font-size: 11px;
|
||
|
|
font-family: monospace;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.slider-container {
|
||
|
|
padding: 0 5px;
|
||
|
|
|
||
|
|
.joint-slider {
|
||
|
|
width: 100%;
|
||
|
|
height: 8px;
|
||
|
|
-webkit-appearance: none;
|
||
|
|
background: #34495e;
|
||
|
|
border-radius: 4px;
|
||
|
|
outline: none;
|
||
|
|
margin-bottom: 8px;
|
||
|
|
cursor: pointer;
|
||
|
|
|
||
|
|
&::-webkit-slider-thumb {
|
||
|
|
-webkit-appearance: none;
|
||
|
|
width: 20px;
|
||
|
|
height: 20px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: #ecf0f1;
|
||
|
|
cursor: pointer;
|
||
|
|
border: 2px solid #3498db;
|
||
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||
|
|
transition: all 0.2s;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.slider-labels {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
font-size: 11px;
|
||
|
|
color: #7f8c8d;
|
||
|
|
margin-top: 5px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|