229 lines
6.4 KiB
Vue
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> |