更新Detection.vue前端页面

This commit is contained in:
root 2025-08-13 14:24:22 +08:00
parent 08d52e2c98
commit 9268a274d6

View File

@ -25,7 +25,7 @@
<!-- handleStartStop -->
<el-button v-if="isRecording" @click="handleStartStop" type="primary" class="start-btn" style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216));
--el-button-border-color: #409EFF;
--el-button-border-color: transparent;width: 120px;height: 30px;font-size: 20px;" >
--el-button-border-color: transparent;width: 120px;height: 30px;font-size: 20px;">
结束
</el-button>
<el-button v-if="isStart && isConnected" @click="saveDetectionData" type="primary" class="start-btn" style="background-image: linear-gradient(to right, #FBB106, #A817C6);
@ -77,18 +77,23 @@
<div style="display: flex;">
<div class="module-header">
<div style="display: flex;align-items: center;">
<div class="module-title">
<div class="module-title" style="width:230px">
<div class="module-title-bg">
<img src="@/assets/svg/u67.svg" alt="" srcset="" style="margin-right: 5px;">
头部姿态
</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 style="display: flex;align-items: center;">
<div :style="{ color: imuStatus == '已连接' ? '#00CC33' : '#808080' }" style="font-size: 14px;">{{
imuStatus
}}</div>
}}</div>
<el-button type="primary" class="start-btn" @click="clearAndStartTracking" :disabled="isRecording"
style="background-color: #0099ff;font-size: 14px;margin-left: 15px;
--el-button-border-color: transparent !important;border-radius: 20px;height:26px;border:none;width: 100px;">
@ -103,6 +108,7 @@
<div style="display: flex;justify-content: space-between;padding: 0px 10px;padding-top: 10px;">
<div style="width: 33%;position: relative;">
<div class="chart-title">旋转角</div>
<div class="chart-titles">{{ headlist.rotation }}</div>
<div id="rotationChartId" style="width: 100%;height: 140px;"></div>
<div class="gauge-group-box">
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{
@ -113,7 +119,8 @@
</div>
<div style="width: 33%;position: relative;">
<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-text1"><span class="gauge-group-box-text2">{{
headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
@ -123,7 +130,8 @@
</div>
<div style="width: 33%;position: relative;">
<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-text1"><span class="gauge-group-box-text2">{{
headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
@ -138,7 +146,7 @@
</div>
<!-- 历史数据表格 -->
<div style="display: flex;justify-content: center;padding: 0px 10px;margin-top: 5px;height: 100%;">
<el-table :data="historyData" border style="width: 100%;overflow: auto;height: calc(100% - 280px);" >
<el-table :data="historyData" border style="width: 100%;overflow: auto;height: calc(100% - 280px);">
<el-table-column prop="id" label="ID" align="center" width="60" />
<el-table-column label="最大旋转角" align="center">
<el-table-column prop="rotLeft" label="左" min-width="60" align="center" />
@ -331,7 +339,8 @@
</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 class="dialog-title">
<div class="dialog-title-item">
@ -358,8 +367,10 @@
</div>
<template #footer>
<span class="dialog-footer">
<el-button style="background: #323232;border:1px solid #787878;color: #ffffff;" @click="resDialogVisible = false">取消</el-button>
<el-button type="primary" style="background:#0099ff;" @click="handleDiagnosticInfo('diagnosed')">暂存</el-button>
<el-button style="background: #323232;border:1px solid #787878;color: #ffffff;"
@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>
@ -465,8 +476,8 @@
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose"
style="margin-right: 0px;background: #323232;border:1px solid #787878;color: #ffffff;">退出</el-button>
<el-button @click="handleClose"
style="margin-right: 0px;background: #323232;border:1px solid #787878;color: #ffffff;">退出</el-button>
<el-button type="primary" style="background: #0099ff;" @click="handleSave" :loading="saveLoading">
保存
</el-button>
@ -485,7 +496,7 @@ import html2canvas from 'html2canvas'
import Header from '@/views/Header.vue'
import { useAuthStore } from '../stores/index.js'
import * as echarts from 'echarts'
import { getBackendUrl ,patientAPI} from '../services/api.js'
import { getBackendUrl, patientAPI } from '../services/api.js'
const authStore = useAuthStore()
const router = useRouter()
const route = useRoute()
@ -669,6 +680,11 @@ const formattedTime = computed(() => {
const secs = seconds.value % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
});
const headlist = ref({
rotation: '0',
tilt: '0',
pitch: '0'
})
//
const startTimer = () => {
if (isRunning.value) return;
@ -974,66 +990,69 @@ const headPoseMaxValues = ref({
const headPoseHistory = ref([])
const headPoseData = ref({})
// IMUDOM
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)
// IMU姿
function handleIMUData(data) {
try {
if (data && data.head_pose) {
const headPose = data.head_pose
if (!data) return
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))
// 姿
// console.log('🎯 IMU姿:', {
// rotation: headPose.rotation, // (-), (+)
// tilt: headPose.tilt, // (-), (+)
// pitch: headPose.pitch // (-), (+)
// })
if (rotationCharts) {
rotationCharts.setOption({
series: [{
data: [{
value: headPose.rotation.toFixed(1)
}]
}]
})
}
if (pitchCharts) {
pitchCharts.setOption({
series: [{
data: [{
value: headPose.pitch.toFixed(1)
}]
}]
})
}
if (tiltCharts) {
tiltCharts.setOption({
series: [{
data: [{
value: headPose.tilt.toFixed(1)
}]
}]
})
}
//
// console.log(`📐 姿 - : ${headPose.rotation.toFixed(1)}°, : ${headPose.tilt.toFixed(1)}°, : ${headPose.pitch.toFixed(1)}°`)
//
// if (isTrackingMaxValues.value) {
updateHeadPoseMaxValues(headPose)
// }
//
//
//
// updateHeadPoseChart({
// rotation: headPose.rotation,
// tilt: headPose.tilt,
// pitch: headPose.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
if (rotationCharts) {
rotationCharts.setOption({
series: [{ data: [{ value: rVal }] }]
})
}
if (pitchCharts) {
pitchCharts.setOption({
series: [{ data: [{ value: pVal }] }]
})
}
if (tiltCharts) {
tiltCharts.setOption({
series: [{ data: [{ value: tVal }] }]
})
}
// 使
updateHeadPoseMaxValues({ rotation, tilt, pitch })
} catch (error) {
console.error('❌ 处理IMU数据失败:', error)
}
@ -2019,21 +2038,38 @@ const initchart = () => {
} else {
console.warn('找不到 ID 为 的 DOM 元素');
}
//
window.addEventListener('resize', () => {
if (rotationCharts) {
rotationCharts.resize();
}
if (pitchCharts) {
pitchCharts.resize();
}
if (tiltCharts) {
tiltCharts.resize();
}
});
//
window.addEventListener('resize', () => {
if (rotationCharts) {
rotationCharts.resize();
}
if (pitchCharts) {
pitchCharts.resize();
}
if (tiltCharts) {
tiltCharts.resize();
}
});
});
}
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(() => {
//
loadPatientInfo()
@ -2689,6 +2725,18 @@ onUnmounted(() => {
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) {
background-color: rgb(81, 81, 81) !important;
}
@ -2707,44 +2755,54 @@ onUnmounted(() => {
background-color: #282828 !important;
border-right: 1px solid rgb(81, 81, 81) !important;
}
:deep(.el-scrollbar__wrap){
:deep(.el-scrollbar__wrap) {
background: #282828 !important;
}
:deep(.tsDialog.el-dialog){
:deep(.tsDialog.el-dialog) {
background-color: #323232;
padding:0px;
padding-bottom:20px;
padding: 0px;
padding-bottom: 20px;
}
:deep(.tsDialog.el-dialog .el-input__wrapper){
:deep(.tsDialog.el-dialog .el-input__wrapper) {
background-color: #242424;
border: none;
box-shadow: none;
}
:deep(.tsDialog.el-dialog .el-select__wrapper){
:deep(.tsDialog.el-dialog .el-select__wrapper) {
background-color: #242424;
border: none;
box-shadow: none;
}
:deep(.tsDialog.el-dialog .el-input__inner){
:deep(.tsDialog.el-dialog .el-input__inner) {
color: #ffffff;
}
:deep(.tsDialog.el-dialog .el-select__placeholder){
:deep(.tsDialog.el-dialog .el-select__placeholder) {
color: #ffffff;
}
:deep(.tsDialog.el-dialog .el-dialog__header){
:deep(.tsDialog.el-dialog .el-dialog__header) {
border-bottom: 1px solid #1e1e1e;
padding:10px 20px;
padding: 10px 20px;
}
:deep(.tsDialog.el-dialog .el-textarea__inner) {
background: #242424;
color: #ffffff;
box-shadow:none;
box-shadow: none;
}
:deep( .el-dialog__body){
padding:20px;
:deep(.el-dialog__body) {
padding: 20px;
}
:deep(.el-dialog__footer){
padding:0px 20px;
:deep(.el-dialog__footer) {
padding: 0px 20px;
}
</style>
<style>