CMVR-AIMA/src/views/device/register/components/DexHand/RightBottomCharts.vue
2026-06-24 10:27:33 +08:00

229 lines
6.4 KiB
Vue

<template>
<div ref="containerRef" style="width: 100%;height: 100%; margin: 0 auto;">
<div class="tabs">
<div
v-for="(tab, index) in tabs"
:key="index"
class="tab"
:class="{ active: activeTab === tab.value }"
@click="switchTab(tab.value)"
>
{{ tab.label }}
</div>
</div>
<div ref="chart" style="width: 100%; height: 100%;"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 解构 props
const props = defineProps({
maxData: {
type: Object,
required: true,
default: () => ({
thumb: { end: 0, tip: 0, middle: 0, palm: 0 },
indexFinger: { end: 0, tip: 0, middle: 0 },
middleFinger: { end: 0, tip: 0, middle: 0 },
ringFinger: { end: 0, tip: 0, middle: 0 },
littleFinger: { end: 0, tip: 0, middle: 0 },
palm: { touch: 0 }
})
},
avgData: {
type: Object,
required: true,
default: () => ({
thumb: { end: 0, tip: 0, middle: 0, palm: 0 },
indexFinger: { end: 0, tip: 0, middle: 0 },
middleFinger: { end: 0, tip: 0, middle: 0 },
ringFinger: { end: 0, tip: 0, middle: 0 },
littleFinger: { end: 0, tip: 0, middle: 0 },
palm: { touch: 0 }
})
}
});
const chart = ref(null);
const containerRef = ref(null); // 新增:引用父容器
const activeTab = ref('avg'); // 默认选中平均值
// Tab 配置
const tabs = [
{ label: '最大值', value: 'max' },
{ label: '平均值', value: 'avg' }
];
let myChart;
// 防抖函数
const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
};
const initChart = () => {
if (!chart.value) {
console.error('Chart element is null');
return;
}
myChart = echarts.init(chart.value);
const option = {
title: { text: '手指触觉数据' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['指端', '指尖', '指中', '指腹', '手掌触觉'] },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: ['小拇指', '无名指', '中指', '食指', '大拇指', '手掌'],
splitArea: { show: true }
},
yAxis: [{ type: 'value', name: '弯曲应力', max: 4000,interval: 1000 }],
series: [
{ name: '指端', type: 'bar', barGap: '1%', barCategoryGap: '10%', itemStyle: { color: '#FF5733' }, label: { show: true, position: 'top' }, emphasis: { focus: 'series' }, data: [] },
{ name: '指尖', type: 'bar', barGap: '1%', barCategoryGap: '10%', itemStyle: { color: '#33FF57' }, label: { show: true, position: 'top' }, emphasis: { focus: 'series' }, data: [] },
{ name: '指中', type: 'bar', barGap: '1%', barCategoryGap: '10%', itemStyle: { color: '#5733FF' }, label: { show: true, position: 'top' }, emphasis: { focus: 'series' }, data: [] },
{ name: '指腹', type: 'bar', barGap: '1%', barCategoryGap: '10%', itemStyle: { color: '#FF33A1' }, label: { show: true, position: 'top' }, emphasis: { focus: 'series' }, data: [] },
{ name: '手掌触觉', type: 'bar', barGap: '1%', barCategoryGap: '10%', itemStyle: { color: '#FFD700' }, label: { show: true, position: 'top' }, emphasis: { focus: 'series' }, data: [] }
]
};
myChart.setOption(option);
};
// 获取每个部分的数据,处理空值
const getPartData = (data, part) => {
return [
data.littleFinger[part] || null,
data.ringFinger[part] || null,
data.middleFinger[part] || null,
data.indexFinger[part] || null,
data.thumb[part] || null,
data.palm[part] || null
];
};
const updateChart = (data) => {
const seriesData = {
end: getPartData(data, 'end'),
tip: getPartData(data, 'tip'),
middle: getPartData(data, 'middle'),
palm: getPartData(data, 'palm'),
touch: getPartData(data, 'touch')
};
myChart.setOption({
series: [
{ name: '指端', data: seriesData.end },
{ name: '指尖', data: seriesData.tip },
{ name: '指中', data: seriesData.middle },
{ name: '指腹', data: seriesData.palm },
{ name: '手掌触觉', data: seriesData.touch }
]
});
};
const switchTab = (tab) => {
activeTab.value = tab;
updateChart(activeTab.value === 'max' ? props.maxData : props.avgData);
};
// 监听 props 变化,更新图表
watch([() => props.maxData, () => props.avgData, activeTab], () => {
updateChart(activeTab.value === 'max' ? props.maxData : props.avgData);
});
const data = ref({
resizeObserver: null,
debouncedResize: null,
});
onMounted(() => {
initChart();
updateChart(activeTab.value === 'max' ? props.maxData : props.avgData);
// 使用 ResizeObserver 监听父容器尺寸变化
const container = containerRef.value;
if (container) {
const resizeObserver = new ResizeObserver(debounce(() => {
if (myChart) {
console.log('Container resized:', container.offsetWidth, container.offsetHeight);
myChart.resize();
}
}, 10));
resizeObserver.observe(container);
data.value.resizeObserver = resizeObserver;
} else {
console.error('Failed to find container for ResizeObserver');
}
// 保留 window 的 resize 事件监听
const debouncedResize = debounce(() => {
if (myChart) {
myChart.resize();
}
}, 10);
window.addEventListener('resize', debouncedResize);
data.value.debouncedResize = debouncedResize;
});
onUnmounted(() => {
// 清理图表实例
if (myChart) {
myChart.dispose();
myChart = null;
}
// 清理 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>
.container {
display: flex;
width: 100%;
margin: 0 auto;
flex-direction: column;
}
.tabs {
position: absolute;
right: 50%;
justify-content: center;
background: rgba(0, 0, 0, 0);
border-radius: 10px 10px 0 0;
padding: 5px 0;
}
.tab {
padding: 10px 20px;
margin: 0 10px;
cursor: pointer;
font-size: 16px;
color: #333;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.tab:hover {
background-color: #f5f5f5;
border-radius: 5px;
}
.tab.active {
border-bottom: 3px solid #007bff;
color: #007bff;
}
</style>