270 lines
7.2 KiB
Vue
270 lines
7.2 KiB
Vue
<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);
|
||
const containerRef = ref(null); // 新增:引用父容器
|
||
const defaultHandImageInfo = { width: 891, height: 981 };
|
||
const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height;
|
||
const terminalId = ref('');
|
||
const deviceId = ref('');
|
||
// 1. 定义 props
|
||
const props = defineProps({
|
||
initialDeviceId: { // 示例 prop,用于从弹窗接收cameraId
|
||
type: String,
|
||
default: null,
|
||
},
|
||
initialTerminalId: { // 示例 prop,用于从弹窗接收 terminalId
|
||
type: String,
|
||
default: null,
|
||
},
|
||
});
|
||
|
||
// 2. 使用 watchEffect 来响应 props 和路由的变化
|
||
watchEffect(() => {
|
||
terminalId.value = props.initialTerminalId;
|
||
deviceId.value = props.initialDeviceId;
|
||
console.log(deviceId.value, terminalId.value,props)
|
||
});
|
||
|
||
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) {
|
||
console.log(deviceId.value, terminalId.value)
|
||
loading.value = true;
|
||
setDexHandAngle({ deviceId: deviceId.value, terminalId: terminalId.value, 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: deviceId.value, terminalId: terminalId.value }).then((res) => {
|
||
const newTopSeriesData = res.data.handsList.map(item => item.force);
|
||
emit('update:topSeriesData', newTopSeriesData);
|
||
});
|
||
};
|
||
const data = ref({
|
||
debouncedResize: null,
|
||
resizeObserver: null,
|
||
});
|
||
onMounted(() => {
|
||
updateSliderPositions();
|
||
// 使用 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');
|
||
}
|
||
|
||
const debouncedResize = debounce(updateSliderPositions, 0);
|
||
window.addEventListener('resize', debouncedResize);
|
||
data.value.debouncedResize = debouncedResize;
|
||
updateToSeriesData();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
// 清理 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;
|
||
}
|
||
});
|
||
</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> |