更新Detection.vue前端页面
This commit is contained in:
parent
08d52e2c98
commit
9268a274d6
@ -77,11 +77,16 @@
|
|||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<div class="module-header">
|
<div class="module-header">
|
||||||
<div style="display: flex;align-items: center;">
|
<div style="display: flex;align-items: center;">
|
||||||
<div class="module-title">
|
<div class="module-title" style="width:230px">
|
||||||
<div class="module-title-bg">
|
<div class="module-title-bg">
|
||||||
<img src="@/assets/svg/u67.svg" alt="" srcset="" style="margin-right: 5px;">
|
<img src="@/assets/svg/u67.svg" alt="" srcset="" style="margin-right: 5px;">
|
||||||
头部姿态
|
头部姿态
|
||||||
</div>
|
</div>
|
||||||
|
<el-button type="primary" class="start-btn" @click="calibrationClick" :disabled="isRecording"
|
||||||
|
style="background-color: #0099ff;font-size: 14px;
|
||||||
|
--el-button-border-color: transparent !important;border-radius: 20px;height:26px;border:none;width: 100px;">
|
||||||
|
校准
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -103,6 +108,7 @@
|
|||||||
<div style="display: flex;justify-content: space-between;padding: 0px 10px;padding-top: 10px;">
|
<div style="display: flex;justify-content: space-between;padding: 0px 10px;padding-top: 10px;">
|
||||||
<div style="width: 33%;position: relative;">
|
<div style="width: 33%;position: relative;">
|
||||||
<div class="chart-title">旋转角</div>
|
<div class="chart-title">旋转角</div>
|
||||||
|
<div class="chart-titles">{{ headlist.rotation }}</div>
|
||||||
<div id="rotationChartId" style="width: 100%;height: 140px;"></div>
|
<div id="rotationChartId" style="width: 100%;height: 140px;"></div>
|
||||||
<div class="gauge-group-box">
|
<div class="gauge-group-box">
|
||||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{
|
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{
|
||||||
@ -113,7 +119,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="width: 33%;position: relative;">
|
<div style="width: 33%;position: relative;">
|
||||||
<div class="chart-title">倾斜角</div>
|
<div class="chart-title">倾斜角</div>
|
||||||
<div id="pitchChartId" style="width: 100%;height: 140px;"></div>
|
<div class="chart-titles">{{ headlist.tilt }}</div>
|
||||||
|
<div id="tiltChartId" style="width: 100%;height: 140px;"></div>
|
||||||
<div class="gauge-group-box">
|
<div class="gauge-group-box">
|
||||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{
|
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{
|
||||||
headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
|
headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
|
||||||
@ -123,7 +130,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="width: 33%;position: relative;">
|
<div style="width: 33%;position: relative;">
|
||||||
<div class="chart-title">俯仰角</div>
|
<div class="chart-title">俯仰角</div>
|
||||||
<div id="tiltChartId" style="width: 100%;height: 140px;"></div>
|
<div class="chart-titles">{{ headlist.pitch }}</div>
|
||||||
|
<div id="pitchChartId" style="width: 100%;height: 140px;"></div>
|
||||||
<div class="gauge-group-box">
|
<div class="gauge-group-box">
|
||||||
<div class="gauge-group-box-text1">俯:<span class="gauge-group-box-text2">{{
|
<div class="gauge-group-box-text1">俯:<span class="gauge-group-box-text2">{{
|
||||||
headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
|
headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
|
||||||
@ -331,7 +339,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog class="tsDialog" v-model="resDialogVisible" center title="诊断信息" width="600" :before-close="reshandleClose">
|
<el-dialog class="tsDialog" v-model="resDialogVisible" center title="诊断信息" width="600"
|
||||||
|
:before-close="reshandleClose">
|
||||||
<div style="margin-top:10px">
|
<div style="margin-top:10px">
|
||||||
<div class="dialog-title">
|
<div class="dialog-title">
|
||||||
<div class="dialog-title-item">
|
<div class="dialog-title-item">
|
||||||
@ -358,8 +367,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button style="background: #323232;border:1px solid #787878;color: #ffffff;" @click="resDialogVisible = false">取消</el-button>
|
<el-button style="background: #323232;border:1px solid #787878;color: #ffffff;"
|
||||||
<el-button type="primary" style="background:#0099ff;" @click="handleDiagnosticInfo('diagnosed')">暂存</el-button>
|
@click="resDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" style="background:#0099ff;"
|
||||||
|
@click="handleDiagnosticInfo('diagnosed')">暂存</el-button>
|
||||||
<el-button type="primary" style="background:#0099ff;" @click="handleDiagnosticInfo('completed')">
|
<el-button type="primary" style="background:#0099ff;" @click="handleDiagnosticInfo('completed')">
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -669,6 +680,11 @@ const formattedTime = computed(() => {
|
|||||||
const secs = seconds.value % 60;
|
const secs = seconds.value % 60;
|
||||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
});
|
});
|
||||||
|
const headlist = ref({
|
||||||
|
rotation: '0',
|
||||||
|
tilt: '0',
|
||||||
|
pitch: '0'
|
||||||
|
})
|
||||||
// 开始计时器
|
// 开始计时器
|
||||||
const startTimer = () => {
|
const startTimer = () => {
|
||||||
if (isRunning.value) return;
|
if (isRunning.value) return;
|
||||||
@ -974,66 +990,69 @@ const headPoseMaxValues = ref({
|
|||||||
const headPoseHistory = ref([])
|
const headPoseHistory = ref([])
|
||||||
const headPoseData = ref({})
|
const headPoseData = ref({})
|
||||||
|
|
||||||
|
// IMU更新节流与抖动阈值(降低频繁DOM与图表更新导致的卡顿)
|
||||||
|
let lastIMUUpdateTs = 0
|
||||||
|
const IMU_MIN_INTERVAL_MS = 33 // 约30Hz
|
||||||
|
let lastIMUValues = { rotation: null, tilt: null, pitch: null }
|
||||||
|
const IMU_CHANGE_EPS = 0.1 // 小于0.1°的变化忽略
|
||||||
|
|
||||||
// 最值跟踪状态
|
// 最值跟踪状态
|
||||||
const isTrackingMaxValues = ref(false)
|
const isTrackingMaxValues = ref(false)
|
||||||
|
|
||||||
// 处理IMU头部姿态数据
|
// 处理IMU头部姿态数据
|
||||||
function handleIMUData(data) {
|
function handleIMUData(data) {
|
||||||
try {
|
try {
|
||||||
if (data && data.head_pose) {
|
if (!data) return
|
||||||
const headPose = data.head_pose
|
headlist.value.rotation = data.rotation // 旋转角度
|
||||||
|
headlist.value.tilt = data.tilt // 倾斜角度
|
||||||
|
headlist.value.pitch = data.pitch // 俯仰角度
|
||||||
|
// 兼容两种载荷结构:
|
||||||
|
// 1) { rotation, tilt, pitch }
|
||||||
|
// 2) { head_pose: { rotation, tilt, pitch } }
|
||||||
|
const rotation = (data.rotation ?? (data.head_pose && data.head_pose.rotation))
|
||||||
|
const tilt = (data.tilt ?? (data.head_pose && data.head_pose.tilt))
|
||||||
|
const pitch = (data.pitch ?? (data.head_pose && data.head_pose.pitch))
|
||||||
|
|
||||||
|
if (rotation === undefined || tilt === undefined || pitch === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now()
|
||||||
|
|
||||||
|
// 若距离上次更新时间很短且变化不明显,则跳过避免频繁DOM更新
|
||||||
|
const tooSoon = (now - lastIMUUpdateTs) < IMU_MIN_INTERVAL_MS
|
||||||
|
const notSignificant = (
|
||||||
|
lastIMUValues.rotation !== null && Math.abs(rotation - lastIMUValues.rotation) < IMU_CHANGE_EPS &&
|
||||||
|
lastIMUValues.tilt !== null && Math.abs(tilt - lastIMUValues.tilt) < IMU_CHANGE_EPS &&
|
||||||
|
lastIMUValues.pitch !== null && Math.abs(pitch - lastIMUValues.pitch) < IMU_CHANGE_EPS
|
||||||
|
)
|
||||||
|
if (tooSoon && notSignificant) return
|
||||||
|
|
||||||
|
lastIMUUpdateTs = now
|
||||||
|
lastIMUValues = { rotation, tilt, pitch }
|
||||||
|
|
||||||
|
const rVal = Math.round(rotation * 10) / 10
|
||||||
|
const pVal = Math.round(pitch * 10) / 10
|
||||||
|
const tVal = Math.round(tilt * 10) / 10
|
||||||
|
|
||||||
// 更新头部姿态数据
|
|
||||||
// console.log('🎯 更新IMU头部姿态数据:', {
|
|
||||||
// rotation: headPose.rotation, // 旋转角:左旋(-), 右旋(+)
|
|
||||||
// tilt: headPose.tilt, // 倾斜角:左倾(-), 右倾(+)
|
|
||||||
// pitch: headPose.pitch // 俯仰角:俯角(-), 仰角(+)
|
|
||||||
// })
|
|
||||||
if (rotationCharts) {
|
if (rotationCharts) {
|
||||||
rotationCharts.setOption({
|
rotationCharts.setOption({
|
||||||
series: [{
|
series: [{ data: [{ value: rVal }] }]
|
||||||
data: [{
|
|
||||||
value: headPose.rotation.toFixed(1)
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (pitchCharts) {
|
if (pitchCharts) {
|
||||||
pitchCharts.setOption({
|
pitchCharts.setOption({
|
||||||
series: [{
|
series: [{ data: [{ value: pVal }] }]
|
||||||
data: [{
|
|
||||||
value: headPose.pitch.toFixed(1)
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (tiltCharts) {
|
if (tiltCharts) {
|
||||||
tiltCharts.setOption({
|
tiltCharts.setOption({
|
||||||
series: [{
|
series: [{ data: [{ value: tVal }] }]
|
||||||
data: [{
|
|
||||||
value: headPose.tilt.toFixed(1)
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 显示角度值(保留一位小数)
|
|
||||||
// console.log(`📐 头部姿态角度 - 旋转: ${headPose.rotation.toFixed(1)}°, 倾斜: ${headPose.tilt.toFixed(1)}°, 俯仰: ${headPose.pitch.toFixed(1)}°`)
|
|
||||||
|
|
||||||
// 如果正在跟踪最值,则更新最值数据
|
// 更新最值跟踪逻辑使用原始数值(不做四舍五入)
|
||||||
// if (isTrackingMaxValues.value) {
|
updateHeadPoseMaxValues({ rotation, tilt, pitch })
|
||||||
updateHeadPoseMaxValues(headPose)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 这里可以添加数据可视化逻辑
|
|
||||||
// 例如更新图表或显示数值
|
|
||||||
|
|
||||||
// 如果有图表组件,可以在这里更新数据
|
|
||||||
// updateHeadPoseChart({
|
|
||||||
// rotation: headPose.rotation,
|
|
||||||
// tilt: headPose.tilt,
|
|
||||||
// pitch: headPose.pitch
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 处理IMU数据失败:', error)
|
console.error('❌ 处理IMU数据失败:', error)
|
||||||
}
|
}
|
||||||
@ -2033,7 +2052,24 @@ const initchart = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const calibrationClick = async () => {
|
||||||
|
const response = await fetch(`${BACKEND_URL}/api/devices/calibrate/imu`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({})
|
||||||
|
})
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json()
|
||||||
|
if (result.success) {
|
||||||
|
ElMessage.success(result.message)
|
||||||
|
} else {
|
||||||
|
ElMessage.error(result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 加载患者信息
|
// 加载患者信息
|
||||||
loadPatientInfo()
|
loadPatientInfo()
|
||||||
@ -2689,6 +2725,18 @@ onUnmounted(() => {
|
|||||||
color: #EEF1FA;
|
color: #EEF1FA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-titles {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 7px;
|
||||||
|
z-index: 2000;
|
||||||
|
font-family: 'Arial Negreta', 'Arial Normal', 'Arial', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #EEF1FA;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-table--border .el-table__inner-wrapper:after, .el-table--border:after, .el-table--border:before, .el-table__inner-wrapper:before) {
|
:deep(.el-table--border .el-table__inner-wrapper:after, .el-table--border:after, .el-table--border:before, .el-table__inner-wrapper:before) {
|
||||||
background-color: rgb(81, 81, 81) !important;
|
background-color: rgb(81, 81, 81) !important;
|
||||||
}
|
}
|
||||||
@ -2707,42 +2755,52 @@ onUnmounted(() => {
|
|||||||
background-color: #282828 !important;
|
background-color: #282828 !important;
|
||||||
border-right: 1px solid rgb(81, 81, 81) !important;
|
border-right: 1px solid rgb(81, 81, 81) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-scrollbar__wrap) {
|
:deep(.el-scrollbar__wrap) {
|
||||||
background: #282828 !important;
|
background: #282828 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog) {
|
:deep(.tsDialog.el-dialog) {
|
||||||
background-color: #323232;
|
background-color: #323232;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-input__wrapper) {
|
:deep(.tsDialog.el-dialog .el-input__wrapper) {
|
||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-select__wrapper) {
|
:deep(.tsDialog.el-dialog .el-select__wrapper) {
|
||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-input__inner) {
|
:deep(.tsDialog.el-dialog .el-input__inner) {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-select__placeholder) {
|
:deep(.tsDialog.el-dialog .el-select__placeholder) {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-dialog__header) {
|
:deep(.tsDialog.el-dialog .el-dialog__header) {
|
||||||
border-bottom: 1px solid #1e1e1e;
|
border-bottom: 1px solid #1e1e1e;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.tsDialog.el-dialog .el-textarea__inner) {
|
:deep(.tsDialog.el-dialog .el-textarea__inner) {
|
||||||
background: #242424;
|
background: #242424;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-dialog__body) {
|
:deep(.el-dialog__body) {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-dialog__footer) {
|
:deep(.el-dialog__footer) {
|
||||||
padding: 0px 20px;
|
padding: 0px 20px;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user