This commit is contained in:
root 2025-08-06 17:13:11 +08:00
commit 19474e8617
3 changed files with 181 additions and 130 deletions

View File

@ -113,6 +113,11 @@ export const deviceAPI = {
return api.post('/api/devices/calibrate')
},
// 校准IMU头部姿态传感器
calibrateIMU() {
return api.post('/api/devices/calibrate/imu')
},
// 测试设备
testDevice() {
return api.post('/api/devices/test')

View File

@ -39,6 +39,13 @@
<el-table-column prop="updated_at" label="最后一次检查时间" min-width="60" />
<el-table-column prop="gender" label="性别" min-width="60" />
<el-table-column prop="num" label="测试次数" min-width="60" />
<el-table-column prop="status" label="测试状态" min-width="60">
<template #default="scope">
<span v-if="!scope.row.status" style="font-size: 14px;color:#F59A23;">未处理</span>
<span v-else style="font-size: 14px;color:#ffffff;">已处理</span>
</template>
</el-table-column>
<el-table-column prop="doctor" label="测试医生" min-width="60" />
<el-table-column fixed="right" label="操作" width="80">
<template #default="scope">
<el-button link type="primary" size="small" @click="delClick(scope.row.id)">删除</el-button>
@ -347,6 +354,7 @@ const filteredPatients = computed(() => {
if (!searchKeyword.value) {
return patients.value
}
return patients.value.filter(patient =>
patient.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)

View File

@ -83,11 +83,7 @@
<div style="margin-left: 10px;font-size: 14px;">{{ imuStatus }}</div>
</div>
<div style="display: flex; gap: 10px;">
<el-button
type="primary"
class="start-btn"
@click="clearAndStartTracking"
:disabled="isRecording"
<el-button type="primary" class="start-btn" @click="clearAndStartTracking" :disabled="isRecording"
style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216));
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 150px;">
清零并保存
@ -99,40 +95,36 @@
<!-- 仪表盘区域 -->
<div style="display: flex;justify-content: space-between;padding: 0px 10px;padding-top: 10px;">
<div style="width: 33%;">
<div id="headChart1" style="width: 100%;height: 140px;"></div>
<!-- <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> -->
<div id="rotationChartId" style="width: 100%;height: 140px;"></div>
<div class="gauge-group-box">
<div class="gauge-group-box-text1">旋转</div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.rotationLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">{{ headPoseMaxValues.rotationRightMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{
headPoseMaxValues.rotationLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span class="gauge-group-box-text2">{{
headPoseMaxValues.rotationRightMax.toFixed(1) }}°</span></div>
</div>
</div>
<div style="width: 33%;">
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
<div id="pitchChartId" style="width: 100%;height: 140px;"></div>
<div class="gauge-group-box">
<div class="gauge-group-box-text1">倾斜</div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{
headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span class="gauge-group-box-text2">{{
headPoseMaxValues.tiltRightMax.toFixed(1) }}°</span></div>
</div>
</div>
<div style="width: 33%;">
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
<div id="tiltChartId" style="width: 100%;height: 140px;"></div>
<div class="gauge-group-box">
<div class="gauge-group-box-text1">俯仰</div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">{{ headPoseMaxValues.pitchUpMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{
headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span class="gauge-group-box-text2">{{
headPoseMaxValues.pitchUpMax.toFixed(1) }}°</span></div>
</div>
</div>
</div>
<!-- 历史数据表格 -->
<div class="gauge-table-title">
历史数据..
</div>
<div style="display: flex;justify-content: center;padding: 0px 25px;">
<el-table :data="historyData" style="width: 96%;height: 160px;overflow: auto;">
<div style="display: flex;justify-content: center;padding: 0px 25px;margin-top: 20px;">
<el-table :data="historyData" border style="width: 96%;overflow: auto;" :height="160">
<el-table-column prop="id" label="ID" width="60" />
<el-table-column label="最大旋转角" align="center">
<el-table-column prop="rotLeft" label="左" min-width="60" align="center" />
@ -167,11 +159,11 @@
<div class="foot-container-left" style="font-size: 14px;">
<div>
<span>左前足</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">{{ footPressure.left_front }}</span>
</div>
<div class="foot-container-margintop">
<span>左后足</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">{{ footPressure.left_rear }}</span>
</div>
</div>
<div class="foot-container-content">
@ -183,26 +175,28 @@
<span>右足</span>
</div>
</div>
<img src="@/assets/zu.png" alt="">
<img :src="footImgSrc" style="width: 300px;height: 300px;" alt="">
<div style="display: flex;justify-content: center;margin-top: 8px;font-size: 14px;">
<div>
<span>左足总压力</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;"
class="foot-container-paddingcolor">{{ footPressure.left_total }}</span>
</div>
<div class="foot-container-marginleft">
<span>右足总压力</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;"
class="foot-container-paddingcolor">{{ footPressure.right_total }}</span>
</div>
</div>
</div>
<div class="foot-container-right" style="font-size: 14px;">
<div>
<span>右前足</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">{{ footPressure.right_front }}</span>
</div>
<div class="foot-container-margintop">
<span>右后足</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">54%</span>
<span style="font-size: 24px;" class="foot-container-paddingcolor">{{ footPressure.right_rear }}</span>
</div>
</div>
</div>
@ -404,9 +398,9 @@ const handleClose = () => {
const diagnosticForm = ref({})
//
const historyData = ref([
{ id: 3, rotLeft: '-55.2°', rotRight: '54.2°', tiltLeft: '-17.7°', tiltRight: '18.2°', pitchDown: '-20.2°', pitchUp: '10.5°' },
{ id: 2, rotLeft: '-55.8°', rotRight: '56.2°', tiltLeft: '-17.5°', tiltRight: '17.9°', pitchDown: '-21.2°', pitchUp: '12.1°' },
{ id: 1, rotLeft: '-56.1°', rotRight: '55.7°', tiltLeft: '-17.5°', tiltRight: '18.5°', pitchDown: '-22.2°', pitchUp: '11.5°' }
// { id: 3, rotLeft: '-55.2°', rotRight: '54.2°', tiltLeft: '-17.7°', tiltRight: '18.2°', pitchDown: '-20.2°', pitchUp: '10.5°' },
// { id: 2, rotLeft: '-55.8°', rotRight: '56.2°', tiltLeft: '-17.5°', tiltRight: '17.9°', pitchDown: '-21.2°', pitchUp: '12.1°' },
// { id: 1, rotLeft: '-56.1°', rotRight: '55.7°', tiltLeft: '-17.5°', tiltRight: '18.5°', pitchDown: '-22.2°', pitchUp: '11.5°' }
])
const chartoption = ref({
backgroundColor: '#000000',
@ -435,26 +429,26 @@ const chartoption = ref({
length: '85%',
width: 2,
offsetCenter: [0, '5%'],
itemStyle:{
color:'#ff8d00'
itemStyle: {
color: '#ff8d00'
},
},
anchor: {
show: true,
showAbove: true,
size: 18,
icon:'circle',
icon: 'circle',
itemStyle: {
borderWidth: 3,
color:'#ff8d00',
borderColor:'#ff8d00',
color: '#ff8d00',
borderColor: '#ff8d00',
}
},
axisLine: {
roundCap: true,
lineStyle: {
width: 3,
color:[[1, '#0089ff']]
color: [[1, '#0089ff']]
}
},
axisTick: {
@ -523,6 +517,8 @@ function connectWebSocket() {
startVideoStream()
startIMUStreaming();
startPressureStreaming();
//
initchart()
})
//
@ -694,6 +690,7 @@ const headPoseMaxValues = ref({
// 姿
const headPoseHistory = ref([])
const headPoseData = ref({})
//
const isTrackingMaxValues = ref(false)
@ -703,22 +700,48 @@ function handleIMUData(data) {
try {
if (data && data.head_pose) {
const headPose = data.head_pose
// 姿
console.log('🎯 更新IMU头部姿态数据:', {
rotation: headPose.rotation, // (-), (+)
tilt: headPose.tilt, // (-), (+)
pitch: headPose.pitch // (-), (+)
})
//
console.log(`📐 头部姿态角度 - 旋转: ${headPose.rotation.toFixed(1)}°, 倾斜: ${headPose.tilt.toFixed(1)}°, 俯仰: ${headPose.pitch.toFixed(1)}°`)
//
if (isTrackingMaxValues.value) {
updateHeadPoseMaxValues(headPose)
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)
// }
//
//
@ -751,7 +774,7 @@ function updateHeadPoseMaxValues(headPose) {
headPose.rotation
)
}
//
if (headPose.tilt < 0) {
//
@ -766,7 +789,7 @@ function updateHeadPoseMaxValues(headPose) {
headPose.tilt
)
}
//
if (headPose.pitch < 0) {
//
@ -781,16 +804,16 @@ function updateHeadPoseMaxValues(headPose) {
headPose.pitch
)
}
//
console.log('📊 当前头部姿态最值:', {
rotationLeft: headPoseMaxValues.value.rotationLeftMax.toFixed(1),
rotationRight: headPoseMaxValues.value.rotationRightMax.toFixed(1),
tiltLeft: headPoseMaxValues.value.tiltLeftMax.toFixed(1),
tiltRight: headPoseMaxValues.value.tiltRightMax.toFixed(1),
pitchUp: headPoseMaxValues.value.pitchUpMax.toFixed(1),
pitchDown: headPoseMaxValues.value.pitchDownMax.toFixed(1)
})
// //
// console.log('📊 姿:', {
// rotationLeft: headPoseMaxValues.value.rotationLeftMax.toFixed(1),
// rotationRight: headPoseMaxValues.value.rotationRightMax.toFixed(1),
// tiltLeft: headPoseMaxValues.value.tiltLeftMax.toFixed(1),
// tiltRight: headPoseMaxValues.value.tiltRightMax.toFixed(1),
// pitchUp: headPoseMaxValues.value.pitchUpMax.toFixed(1),
// pitchDown: headPoseMaxValues.value.pitchDownMax.toFixed(1)
// })
} catch (error) {
console.error('❌ 更新头部姿态最值失败:', error)
}
@ -800,7 +823,7 @@ function updateHeadPoseMaxValues(headPose) {
function clearAndStartTracking() {
try {
saveMaxValuesToHistory()
// 0
headPoseMaxValues.value = {
rotationLeftMax: 0,
@ -810,26 +833,21 @@ function clearAndStartTracking() {
pitchUpMax: 0,
pitchDownMax: 0
}
//
isTrackingMaxValues.value = true
console.log('🔄 头部姿态最值已清零,开始跟踪')
ElMessage.success('头部姿态最值已清零,开始跟踪')
// console.log('🔄 姿')
// ElMessage.success('姿')
} catch (error) {
console.error('❌ 清零最值失败:', error)
ElMessage.error('清零最值失败')
// console.error(' :', error)
ElMessage.error('清零失败')
}
}
//
function saveMaxValuesToHistory() {
try {
if (!isTrackingMaxValues.value) {
ElMessage.warning('请先点击清零开始跟踪')
return
}
//
const currentMaxValues = {
id: headPoseHistory.value.length + 1,
@ -841,20 +859,20 @@ function saveMaxValuesToHistory() {
pitchDownMax: Number(headPoseMaxValues.value.pitchDownMax.toFixed(1)),
timestamp: new Date().toLocaleString()
}
//
headPoseHistory.value.push(currentMaxValues)
//
isTrackingMaxValues.value = false
console.log('💾 头部姿态最值已保存:', currentMaxValues)
ElMessage.success(`头部姿态最值已保存(第${currentMaxValues.id}组)`)
// console.log('💾 姿:', currentMaxValues)
// ElMessage.success(`姿${currentMaxValues.id}`)
//
updateHistoryTable()
} catch (error) {
console.error('❌ 保存最值失败:', error)
// console.error(' :', error)
ElMessage.error('保存最值失败')
}
}
@ -865,7 +883,7 @@ function updateHistoryTable() {
// 姿
if (headPoseHistory.value.length > 0) {
const latestData = headPoseHistory.value[headPoseHistory.value.length - 1]
// historyData姿
const newHistoryItem = {
id: latestData.id,
@ -877,66 +895,64 @@ function updateHistoryTable() {
pitchUp: latestData.pitchUpMax,
timestamp: latestData.timestamp
}
// historyData
if (historyData.value) {
historyData.value.push(newHistoryItem)
}
console.log('📋 历史数据表格已更新')
}
} catch (error) {
console.error('❌ 更新历史数据表格失败:', error)
}
}
const footPressure = ref({
left_front: '',
left_rear: '',
right_front: '',
right_rear: '',
left_total: '',
right_total: '',
total_pressure: ''
})
const footImgSrc = ref('')
//
function handlePressureData(data) {
try {
if (data && data.foot_pressure) {
const pressureData = data.foot_pressure
//
console.log('👣 接收到压力传感器数据:')
//
if (pressureData.pressure_zones) {
const zones = pressureData.pressure_zones
console.log(' 分区压力值:')
console.log(` 左前足: ${zones.left_front} N`)
console.log(` 左后足: ${zones.left_rear} N`)
console.log(` 右前足: ${zones.right_front} N`)
console.log(` 右后足: ${zones.right_rear} N`)
console.log(` 左足总压力: ${zones.left_total} N`)
console.log(` 右足总压力: ${zones.right_total} N`)
console.log(` 总压力: ${zones.total_pressure} N`)
footPressure.value = pressureData.pressure_zones
}
//
if (pressureData.balance_analysis) {
const balance = pressureData.balance_analysis
console.log(' 平衡分析:')
console.log(` 平衡比例: ${(balance.balance_ratio * 100).toFixed(1)}%`)
console.log(` 压力中心偏移: ${balance.pressure_center_offset}%`)
console.log(` 平衡状态: ${balance.balance_status}`)
console.log(` 左足前后比: ${(balance.left_front_ratio * 100).toFixed(1)}%`)
console.log(` 右足前后比: ${(balance.right_front_ratio * 100).toFixed(1)}%`)
}
// if (pressureData.balance_analysis) {
// const balance = pressureData.balance_analysis
// console.log(' :')
// console.log(` : ${(balance.balance_ratio * 100).toFixed(1)}%`)
// console.log(` : ${balance.pressure_center_offset}%`)
// console.log(` : ${balance.balance_status}`)
// console.log(` : ${(balance.left_front_ratio * 100).toFixed(1)}%`)
// console.log(` : ${(balance.right_front_ratio * 100).toFixed(1)}%`)
// }
//
if (pressureData.pressure_image) {
console.log(' 📊 接收到压力分布图片 (base64格式)')
// console.log(' 📊 (base64)')
//
updatePressureImage(pressureData.pressure_image)
if (pressureData.pressure_image && pressureData.pressure_image.length > 0) {
footImgSrc.value = pressureData.pressure_image
} else {
console.warn('⚠️ 收到空的压力传感器数据图')
}
}
}
} catch (error) {
console.error('❌ 处理压力传感器数据失败:', error)
}
}
// IMU姿
function startIMUStreaming() {
if (socket && socket.connected) {
@ -1429,21 +1445,19 @@ async function saveRecording() {
mimeType: currentMimeType || 'video/webm;codecs=vp9'
})
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json()
if (result.success) {
//
dialogVisible.value = true
console.log('🎬 录像保存成功:', result.filepath)
ElMessage.success({
message: `录像保存成功!文件路径: ${result.filepath}`,
duration: 5000
})
isRecording.value = false
dialogVisible.value = true
//
if (patientInfo.value.sessionId) {
try {
@ -1460,7 +1474,6 @@ async function saveRecording() {
// patientInfo.value.sessionId = null
console.log('✅ 会话正式结束会话ID已清空')
} else {
dialogVisible.value = false
throw new Error(result.message || '保存失败')
}
@ -1470,7 +1483,6 @@ async function saveRecording() {
message: `保存录像失败: ${error.message}`,
duration: 5000
})
dialogVisible.value = false
// 使ID
patientInfo.value.sessionId = null
console.log('⚠️ 录像保存失败但会话已结束会话ID已清空')
@ -1639,27 +1651,51 @@ const handleBeforeUnload = () => {
}
}
const creatorId = ref('')
let headCharts = null;
let rotationCharts = null;
let pitchCharts = null;
let tiltCharts = null;
const initchart = () => {
// DOM
nextTick(() => {
const chartDom = document.getElementById('headChart1');
const chartDom = document.getElementById('rotationChartId');
if (chartDom) {
//
if (headCharts) {
headCharts.dispose();
if (rotationCharts) {
rotationCharts.dispose();
}
headCharts = echarts.init(chartDom);
headCharts.setOption(chartoption.value);
rotationCharts = echarts.init(chartDom);
rotationCharts.setOption(chartoption.value);
//
window.addEventListener('resize', () => {
if (headCharts) {
headCharts.resize();
if (rotationCharts) {
rotationCharts.resize();
}
});
} else {
console.warn('找不到 ID 为 headChart1 的 DOM 元素');
console.warn('找不到 ID 为 的 DOM 元素');
}
const chartDom2 = document.getElementById('pitchChartId');
if (chartDom2) {
//
if (pitchCharts) {
pitchCharts.dispose();
}
pitchCharts = echarts.init(chartDom2);
pitchCharts.setOption(chartoption.value);
} else {
console.warn('找不到 ID 为 的 DOM 元素');
}
const chartDom3 = document.getElementById('tiltChartId');
if (chartDom3) {
//
if (tiltCharts) {
tiltCharts.dispose();
}
tiltCharts = echarts.init(chartDom3);
tiltCharts.setOption(chartoption.value);
} else {
console.warn('找不到 ID 为 的 DOM 元素');
}
});
}
@ -1677,7 +1713,6 @@ onMounted(() => {
console.log(authStore.currentUser)
creatorId.value = authStore.currentUser.id
}
initchart()
})
onUnmounted(() => {
@ -2175,6 +2210,9 @@ onUnmounted(() => {
:deep(.el-table .el-table__cell) {
padding: 4px 0px;
}
:deep(.el-table__body-wrapper) {
border-bottom: 1px solid #ffffff !important;
}
</style>
<style>
.dashboard-container .el-table {