2025-08-21 14:11:58 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="left-top-hand" v-loading="loading">
|
|
|
|
|
<el-button
|
|
|
|
|
class="copy-button"
|
|
|
|
|
type="button"
|
|
|
|
|
:icon="isCopied ? 'Check' : 'CopyDocument'"
|
|
|
|
|
:disabled="isCopied"
|
|
|
|
|
@click="copyParams"
|
|
|
|
|
aria-label="复制当前参数"
|
|
|
|
|
>
|
|
|
|
|
{{ isCopied ? '完成' : '复制' }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<img src="@/assets/images/hand.png" ref="handImage" class="hand-image" />
|
|
|
|
|
<div v-for="(slider, index) in sliders" :key="index" class="slider-container" :style="slider.style">
|
|
|
|
|
<span :class="`slider-title slider-title-${index}`">{{ slider.value }}</span>
|
|
|
|
|
<el-slider
|
|
|
|
|
v-model="slider.value"
|
|
|
|
|
:vertical="slider.vertical"
|
|
|
|
|
:height="slider.height + 'px'"
|
|
|
|
|
@change="updateSeriesData(slider.value, index)"
|
|
|
|
|
:class="`slider-${index}`"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
import { status, setDexHandAngle } from '@/api/device/dexHand';
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['update:topSeriesData']);
|
|
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const handImage = ref(null);
|
2025-09-05 14:52:28 +08:00
|
|
|
const containerRef = ref(null); // 新增:引用父容器
|
2025-08-21 14:11:58 +08:00
|
|
|
const defaultHandImageInfo = { width: 891, height: 981 };
|
|
|
|
|
const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height;
|
|
|
|
|
|
|
|
|
|
const sliders = ref([
|
|
|
|
|
{ value: 0, defalutHeight: 466, height: 0, x: 120, y: 140, vertical: true, style: {} }, // 小拇指
|
|
|
|
|
{ value: 0, defalutHeight: 466, height: 0, x: 230, y: 140, vertical: true, style: {} }, // 无名指
|
|
|
|
|
{ value: 0, defalutHeight: 466, height: 0, x: 373, y: 140, vertical: true, style: {} }, // 中指
|
|
|
|
|
{ value: 0, defalutHeight: 466, height: 0, x: 468, y: 140, vertical: true, style: {} }, // 食指
|
|
|
|
|
{ value: 0, defalutHeight: 346, height: 0, x: 608, y: 360, vertical: true, style: {} }, // 大拇指弯曲
|
|
|
|
|
{ value: 0, defalutHeight: 633, height: 0, x: 85, y: 723, vertical: false, style: {} }, // 大拇指旋转
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
function updateSeriesData(value, i) {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
setDexHandAngle({ deviceId: 'hand1', terminalId: '25449ff3fcc7b27da5f69462c5efcec2', value: value / 100, id: i }).then(() => {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
updateToSeriesData();
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isCopied = ref(false);
|
|
|
|
|
|
|
|
|
|
const copyParams = () => {
|
|
|
|
|
navigator.clipboard.writeText(JSON.stringify(sliders.value))
|
|
|
|
|
.then(() => {
|
|
|
|
|
isCopied.value = true;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
isCopied.value = false;
|
|
|
|
|
}, 3000);
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
console.error('复制失败:', err);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const debounce = (func, delay) => {
|
|
|
|
|
let timeoutId;
|
|
|
|
|
return (...args) => {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
timeoutId = setTimeout(() => func.apply(null, args), delay);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateSliderPositions = () => {
|
|
|
|
|
const img = handImage.value;
|
|
|
|
|
if (img) {
|
|
|
|
|
const { width, height } = img.getBoundingClientRect();
|
|
|
|
|
console.log('Window resized - New dimensions:', width, height);
|
|
|
|
|
const widthRatio = width / height;
|
|
|
|
|
let realWidth = 0;
|
|
|
|
|
let realHeight = 0;
|
|
|
|
|
if (widthRatio > imageAspectRatio) {
|
|
|
|
|
realHeight = height;
|
|
|
|
|
realWidth = realHeight * imageAspectRatio;
|
|
|
|
|
} else {
|
|
|
|
|
realWidth = width;
|
|
|
|
|
realHeight = realWidth / imageAspectRatio;
|
|
|
|
|
}
|
|
|
|
|
const realImageAspectRatio = realWidth / defaultHandImageInfo.width;
|
|
|
|
|
console.log('Real dimensions:', realWidth, realHeight, realImageAspectRatio);
|
|
|
|
|
|
|
|
|
|
sliders.value.forEach((slider, index) => {
|
|
|
|
|
slider.height = slider.defalutHeight * realImageAspectRatio;
|
|
|
|
|
slider.style = {
|
|
|
|
|
top: (height / 2 - realHeight / 2) + slider.y * realImageAspectRatio - 16 + 'px',
|
|
|
|
|
left: (width / 2 - realWidth / 2) + slider.x * realImageAspectRatio - 16 + 'px',
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
zIndex: 10,
|
|
|
|
|
width: slider.height + 'px',
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateToSeriesData = () => {
|
|
|
|
|
status({ deviceId: 'hand1', terminalId: '25449ff3fcc7b27da5f69462c5efcec2' }).then((res) => {
|
|
|
|
|
const newTopSeriesData = res.data.handsList.map(item => item.force);
|
|
|
|
|
emit('update:topSeriesData', newTopSeriesData);
|
|
|
|
|
});
|
|
|
|
|
};
|
2025-09-05 14:52:28 +08:00
|
|
|
const data = ref({
|
|
|
|
|
debouncedResize: null,
|
|
|
|
|
resizeObserver: null,
|
|
|
|
|
});
|
2025-08-21 14:11:58 +08:00
|
|
|
onMounted(() => {
|
|
|
|
|
updateSliderPositions();
|
2025-09-05 14:52:28 +08:00
|
|
|
// 使用 ResizeObserver 监听父容器尺寸变化
|
|
|
|
|
const container = containerRef.value || handImage.value?.parentElement;
|
|
|
|
|
if (container) {
|
|
|
|
|
const resizeObserver = new ResizeObserver(debounce(updateSliderPositions, 10));
|
|
|
|
|
resizeObserver.observe(container);
|
|
|
|
|
data.value.resizeObserver = resizeObserver;
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Failed to find container for ResizeObserver');
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 14:11:58 +08:00
|
|
|
const debouncedResize = debounce(updateSliderPositions, 0);
|
|
|
|
|
window.addEventListener('resize', debouncedResize);
|
2025-09-05 14:52:28 +08:00
|
|
|
data.value.debouncedResize = debouncedResize;
|
2025-08-21 14:11:58 +08:00
|
|
|
updateToSeriesData();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
2025-09-05 14:52:28 +08:00
|
|
|
// 清理 ResizeObserver
|
|
|
|
|
if (data.value.resizeObserver) {
|
|
|
|
|
data.value.resizeObserver.disconnect();
|
|
|
|
|
data.value.resizeObserver = null;
|
|
|
|
|
}
|
|
|
|
|
// 清理 window 的 resize 事件
|
|
|
|
|
if (data.value.debouncedResize) {
|
|
|
|
|
window.removeEventListener('resize', data.value.debouncedResize);
|
|
|
|
|
data.value.debouncedResize = null;
|
|
|
|
|
}
|
2025-08-21 14:11:58 +08:00
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.left-top-hand {
|
|
|
|
|
position: relative;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hand-image {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-container {
|
|
|
|
|
position: absolute;
|
|
|
|
|
zIndex: 10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-container ::v-deep(.el-slider__button) {
|
|
|
|
|
width: 25px !important;
|
|
|
|
|
height: 12px !important;
|
|
|
|
|
background-color: #409eff !important;
|
|
|
|
|
border-radius: 0 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-5 ::v-deep(.el-slider__button) {
|
|
|
|
|
width: 12px !important;
|
|
|
|
|
height: 25px !important;
|
|
|
|
|
background-color: #409eff !important;
|
|
|
|
|
border-radius: 0 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-container .el-slider__runway {
|
|
|
|
|
background-color: #007bff;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-title {
|
|
|
|
|
color: #409eff;
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: -20px;
|
|
|
|
|
left: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-title-5 {
|
|
|
|
|
bottom: 8px;
|
|
|
|
|
left: -30px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.image {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 50%;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.slider-container {
|
|
|
|
|
width: 5vw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy-button {
|
|
|
|
|
position: absolute;
|
|
|
|
|
background-color: #FFFfff;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
border: 0;
|
|
|
|
|
color: #646362;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
outline: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy-button:hover {
|
|
|
|
|
background-color: #FFF5E1;
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy-button:active {
|
|
|
|
|
background-color: #FFDAB9;
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy-button:focus {
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(255, 165, 0, 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy-button:disabled {
|
|
|
|
|
background-color: #F0E8E0;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
transform: none;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|