CMVR-IOT-UI/src/views/device/register/components/DexHand/LeftTopHand.vue

270 lines
7.2 KiB
Vue
Raw Normal View History

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);
const containerRef = ref(null); // 新增:引用父容器
2025-08-21 14:11:58 +08:00
const defaultHandImageInfo = { width: 891, height: 981 };
const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height;
2025-09-12 09:12:58 +08:00
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)
});
2025-08-21 14:11:58 +08:00
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) {
2025-09-12 09:12:58 +08:00
console.log(deviceId.value, terminalId.value)
2025-08-21 14:11:58 +08:00
loading.value = true;
2025-09-12 09:12:58 +08:00
setDexHandAngle({ deviceId: deviceId.value, terminalId: terminalId.value, value: value / 100, id: i }).then(() => {
2025-08-21 14:11:58 +08:00
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 = () => {
2025-09-12 09:12:58 +08:00
status({ deviceId: deviceId.value, terminalId: terminalId.value }).then((res) => {
2025-08-21 14:11:58 +08:00
const newTopSeriesData = res.data.handsList.map(item => item.force);
emit('update:topSeriesData', newTopSeriesData);
});
};
const data = ref({
debouncedResize: null,
resizeObserver: null,
});
2025-08-21 14:11:58 +08:00
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');
}
2025-08-21 14:11:58 +08:00
const debouncedResize = debounce(updateSliderPositions, 0);
window.addEventListener('resize', debouncedResize);
data.value.debouncedResize = debouncedResize;
2025-08-21 14:11:58 +08:00
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;
}
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>