From fbe332a8a67990ba24b7cc480e3258c7310c9058 Mon Sep 17 00:00:00 2001 From: limengnan <420004014@qq.com> Date: Thu, 27 Nov 2025 16:02:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=A3=80=E6=B5=8B=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/renderer/src/assets/new/close.png | Bin 0 -> 336 bytes frontend/src/renderer/src/views/Dashboard.vue | 12 +- frontend/src/renderer/src/views/Detection.vue | 2468 +++++++++++++---- .../renderer/src/views/DiagnosticMessage.vue | 203 ++ .../src/renderer/src/views/PatientCreate.vue | 9 +- .../src/renderer/src/views/PatientProfile.vue | 1622 +---------- .../renderer/src/views/PatientProfile2.vue | 1601 +++++++++++ 7 files changed, 3832 insertions(+), 2083 deletions(-) create mode 100644 frontend/src/renderer/src/assets/new/close.png create mode 100644 frontend/src/renderer/src/views/DiagnosticMessage.vue create mode 100644 frontend/src/renderer/src/views/PatientProfile2.vue diff --git a/frontend/src/renderer/src/assets/new/close.png b/frontend/src/renderer/src/assets/new/close.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa4f1df76220151a1fefe8b1247c035122a94e2 GIT binary patch literal 336 zcmV-W0k8gvP)bl`*n}APk0aUt{A;iX4F!3TkgaY2iJ%6^)&C9svuu zhBYl)=0thROp-rC1pur>Jc>|0K!`|B`xfsYV~n+y$U;PGtuf{ZLPV{# zQi{UbC`u`<^@kV$0DyDuoJ%P==Q!tZ5g8eyh$sR^l-@qW4(HPZO5sP)cN(#M)v2>z zqX{9Tl%$l)vh;nwuItq_&p{h&7zQB(W6XQsbzO#Xvmz;_{k5vf$a0{qG%2O`K7=5I zxc6$QX8+Il%NVlEij-MeA|=oIsp~q%h=@(oL0000 + @@ -228,11 +232,12 @@ import { useAuthStore } from '../stores/index.js' import Header from '@/views/Header.vue' import PatientCreate from '@/views/PatientCreate.vue' import Detection from '@/views/Detection.vue' - import { color } from 'echarts' +import PatientProfile from '@/views/PatientProfile.vue' const router = useRouter() const authStore = useAuthStore() -const isDetection = ref(true) // 显示检查页面 +const isDetection = ref(false) // 显示检查页面 +const isPatientProfile =ref(false) const patienttype = ref('add') const patienttotal = ref(0) // 响应式数据 @@ -421,7 +426,8 @@ const savePatient = async () => { } } const viewPatientProfile = () => { - router.push(`/patient/${selectedPatient.value.id}`) + isPatientProfile.value = true + // router.push(`/patient/${selectedPatient.value.id}`) } const startDetection = () => { diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index cbbd4d3b..ee99998d 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -3,15 +3,20 @@
-
检测中...
-
{{ patientInfo.name }}
-
+
检测中...
+
{{ patientInfo.name }}
+
{{ patientInfo.gender =='male'||patientInfo.gender =='男' ?'男':'女' }}
-
49
- 结束监测 +
{{ calculateAge(patientInfo.birth_date) }}
+ 结束监测
-
+
@@ -28,265 +33,387 @@
-
- +
+
-
- +
+
- - - -
-
-
- -
身体姿态
-
-
- -
- {{ femtoboltStatus }} +
+ + + +
+
+
+ +
身体姿态
+
+
+ +
+ {{ femtoboltStatus }} +
+
+ 深度相机视频流 +
-
- 深度相机视频流 -
-
- - - -
-
-
- -
头部姿态
- - 校准 + + + +
+
+
+ +
头部姿态
+ + 校准 + + + 清零 - - 清零 - -
-
- -
- {{ imuStatus }}
-
-
- -
-
-
-
-
左侧最大
-
-155.2°
-
-
-
- 倾斜角: - 5.1° -
-
- - 左: - -155.2° - - - 右: - -155.2° - +
+ {{ }} + +
+ {{ imuStatus }}
-
-
- 旋转角: - -111.2° -
- -
-
-
-
-
右侧最大
-
-155.2°
-
-
-
- 俯仰角: - 5.1° + +
+
+
+
+
左侧最大
+
{{ + headPoseMaxValues.rotationLeftMax.toFixed(1) }}°
-
- - 俯: - -155.2° - - - 仰: - -155.2° - +
+
+ 倾斜角: + {{ headlist.tilt }}° +
+
+ + 左: + + {{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}° + + + + 右: + + {{ headPoseMaxValues.tiltRightMax.toFixed(1) }}° + + +
+
+
+
+
+ 旋转角: + {{ headlist.rotation }}° +
+ +
+
+
+
+
右侧最大
+
+ {{ headPoseMaxValues.rotationRightMax.toFixed(1) }}° +
+
+
+
+ 俯仰角: + {{ headlist.pitch }}° +
+
+ + 俯: + + {{ headPoseMaxValues.pitchDownMax.toFixed(1) }}° + + + + 仰: + + {{ headPoseMaxValues.pitchUpMax.toFixed(1) }}° + + +
-
-
-
-
- -
足部压力
-
-
- -
- {{ pressureStatus }} +
+
+
+ +
足部压力
-
-
-
-
-
-
-
- 左前足: - 54% -
-
- 左后足: - 54% -
-
- 左足总压力: - 54% +
+ +
+ {{ pressureStatus }}
-
-
-
左足
-
右足
-
-
- -
-
-
-
-
-
-
-
- 右前足: - 54% -
-
- 右后足: - 54% -
-
- 右足总压力: - 54% +
+
+
+
+
+ 左前足: + + {{ footPressure.left_front }}% + +
+
+ 左后足: + + {{ footPressure.left_rear }}% + +
+
+ 左足总压力: + + {{ footPressure.left_total}}% + +
+
+
+
+
+
左足
+
右足
+
+
+ +
+
+
+
+
+
+
+
+ 右前足: + + {{ footPressure.right_front }}% + +
+
+ 右后足: + + {{ footPressure.right_rear }}% + +
+
+ 右足总压力: + + {{ footPressure.right_total}}% + +
-
-
-
- - -
-
-
- -
用户信息
-
-
-
+ + +
+
+
+ +
用户信息
+
+
+
+
+
+ + +
+ +
+
+
+
{{ patientInfo.name }}
+
{{ patientInfo.gender =='male'||patientInfo.gender =='男' ?'男':'女' }}
+
+
{{ calculateAge(patientInfo.birth_date) }}
+
+
+ 用户ID:{{ patientInfo.id }} +
+
+ +
+
+ +
+
居住地
+
+ {{ patientInfo.residence ==''||patientInfo.residence ==null ?'—':patientInfo.residence}} +
+
+
+
身高
+
+ {{ patientInfo.height ==''||patientInfo.height ==null ?'—':patientInfo.height}}cm +
+
+
+
体重
+
+ {{ patientInfo.weight ==''||patientInfo.weight ==null ?'—':patientInfo.weight}}kg +
+
+
+
鞋码
+
+ {{ patientInfo.shoe_size ==''||patientInfo.shoe_size ==null ?'—':patientInfo.shoe_size}}码
+
- -
- -
-
-
-
姓名:
-
-
-
49
-
-
- 用户ID:2024081101 -
-
- -
-
- -
-
居住地
-
北京市-朝阳区
-
-
-
身高
-
68cm
-
-
-
体重
-
75kg
-
-
-
鞋码
-
42码
-
- -
-
-
-
-
-
- -
视频
-
-
- -
- {{ femtoboltStatus }}
-
-
- camera1 +
+
+
+ +
视频
+
+
+ +
+ {{ cameraStatus }} +
+
-
- camera2 +
+ camera1 +
+
+ camera2 +
+ + +
+ +
+
+
+
提示
+
- - +
本次操作未保存有效数据,不予记录。
+
+ 确定 +
+
+
+ + +
+ +
+ +
+ +
+
+
+
+
相机参数设置
+ +
+
+
+
+
深度相机
+
+
+
距离范围
+ +
+ +
+
+
+
足部相机
+
+
+
相机上
+ +
+ 1号 + 2号 + 3号 + 4号 + 5号 +
+
+
+
+
相机下
+ +
+ 1号 + 2号 + 3号 + 4号 + 5号 +
+
+
+
+ 退出 + + 保存 + +
+
+
+
+ + + + + + +
@@ -300,8 +427,12 @@ import { useAuthStore } from '../stores/index.js' import * as echarts from 'echarts' import { getBackendUrl, patientAPI } from '../services/api.js' import noImageSvg from '@/assets/no-image.svg' -const authStore = useAuthStore() +import DiagnosticMessage from '@/views/DiagnosticMessage.vue' +import PatientCreate from '@/views/PatientCreate.vue' +import HistoryDashboard from '@/views/PatientProfile.vue' + const emit = defineEmits([ 'endChange']); +const asd = ref(0) const props = defineProps({ selectedPatient: { required: false, @@ -309,49 +440,267 @@ const props = defineProps({ default: null } }) +const isCloseCreat =ref(false) // 是否打开患者信息编辑框 +const isoperation = ref(false) // 是否保存数据 +const isTip =ref(false) +const isStartVideo = ref(false) +function startVideoClick() { + startRecord() + isoperation.value = true + isStartVideo.value = true +} +function stopVideoClick() { + stopRecord() + isStartVideo.value = false +} + function endClick() { + if(isoperation.value == true){ + isDiagnosticMessage.value = true + }else{ + isTip.value = true + } +} +function handleCancel(){ + isTip.value = false +} + function handleCameraCancel(){ + cameraDialogVisible.value = false + } +function closeTipClick(){ + emit('endChange',false) +} -// 四个设备的独立连接状态 -const cameraStatus = ref('未连接') // 相机设备状态 -const femtoboltStatus = ref('未连接') // 深度相机(FemtoBolt)设备状态 -const imuStatus = ref('未连接') // IMU设备状态 -const pressureStatus = ref('未连接') // 压力传感器设备状态 -const footImgSrc = ref('') // 足底压力传感器图片 - - -const patientInfo =ref({}) -const seconds = ref(0); -const isRunning = ref(false); -const timerId = ref(null); -const blinkState = ref(false); - +const isDiagnosticMessage = ref(false) +const isBig =ref(false) +const authStore = useAuthStore() +const router = useRouter() +const route = useRoute() +const isRecording = ref(false) +const isConnected = ref(false) const rtspImgSrc = ref('') -const camera1ImgSrc = ref('') // 相机视频流1 -const camera2ImgSrc = ref('')// 相机视频流2 - +const camera1ImgSrc = ref('') +const camera2ImgSrc = ref('') const depthCameraImgSrc = ref('') // 深度相机视频流 const screenshotLoading = ref(false) const cameraDialogVisible =ref(false) // 设置相机参数弹框 const contenGridRef =ref(null) // 实时检查整体box const wholeBodyRef = ref(null) // 身体姿态ref const videoImgRef =ref(null) // 视频流图片ref +const camera1Ref = ref(null) +const camera2Ref = ref(null) +const historyDialogVisible = ref(false) +// 录像相关变量 +let mediaRecorder = null +let recordedChunks = [] +let recordingStream = null +// 患者信息(从页面获取或通过API获取) +const patientInfo = ref({ + id: '', + name: '', + gender: '', + birth_date: '', + age: '', + height: '', + weight: '', + shoe_size: '', + phone: '', + nationality: '', + created_at: '', + sessionId: null // 检查记录ID,实际使用时应从路由或API获取 +}) -// 处理开始/停止按钮点击 -async function handleStartStop() { - if (isRecording.value) { - // 停止录制视频 - await stopRecord() - } else { - // 开始录制视频 - await startRecord() - } +// WebSocket相关变量 +let socket = null +let devicesSocket = null +let cameraSocket = null +let femtoboltSocket = null +let imuSocket = null +let pressureSocket = null +let restartSocket = null +let frameCount = 0 + +// 后端服务器地址配置 +const BACKEND_URL = getBackendUrl() +const resDialogVisible = ref(false) +const reshandleClose = () => { + resDialogVisible.value = false } +const cameraHandleClose = () => { + cameraDialogVisible.value = false +} +const dialogVisible = ref(false) +const handleClose = () => { + dialogVisible.value = false +} +// 表单引用 +const patientFormRef = ref() +// 表单数据 +const saveLoading = ref(false) +const patientForm = ref({ + id: '', + name: '', + gender: '', + birth_date: '', + nationality: '', + residence: '', + height: '', + weight: '', + shoe_size: '', + phone: '', + occupation: '', + workplace: '', + email: '' +}) +const occupationOptions = ref(["学生", "教师", "医生", "护士", "工程师", "程序员", "设计师", +"会计师", "律师", "警察", "消防员", "军人", "公务员", "销售", "市场营销", +"人力资源", "行政", "财务", "咨询师", "建筑师", "科研人员", "记者", "编辑", +"作家", "艺术家", "音乐家", "演员", "导演", "摄影师", "厨师", "服务员", +"司机", "快递员", "外卖员", "农民", "工人", "电工", "焊工", "机械师", +"飞行员", "空乘", "船员", "导游", "翻译", "心理咨询师", "社会工作者", +"运动员", "教练", "经纪人", "投资人", "企业家", "自由职业者"]) +const nationalityOptions = ref(["汉族", "满族", "蒙古族", "回族", "藏族", "维吾尔族", "苗族", "彝族", "壮族", +"布依族", "朝鲜族", "侗族", "瑶族", "白族", "土家族", "哈尼族", "哈萨克族", "傣族", +"黎族", "傈僳族", "佤族", "畲族", "高山族", "拉祜族", "水族", "东乡族", "纳西族", +"景颇族", "柯尔克孜族", "土族", "达斡尔族", "仫佬族", "羌族", "布朗族", "撒拉族", +"毛南族", "仡佬族", "锡伯族", "阿昌族", "普米族", "塔吉克族", "怒族", "乌孜别克族", +"俄罗斯族", "鄂温克族", "德昂族", "保安族", "裕固族", "京族", "塔塔尔族", "独龙族", +"鄂伦春族", "赫哲族", "门巴族", "珞巴族", "基诺族"]) +const diagnosticForm = ref({ + diagnosis_info: '', + treatment_info: '', + suggestion_info: '' +}) +const cameraForm = ref({ // 相机参数 + camera1:{ + device_index: '', // 序号 + }, + camera2:{ + device_index: '', // 序号 + }, + femtobolt:{ + algorithm_type: '', // 算法类型 + depth_mode: '', // 相机模式 + depth_range_min: '', // 距离范围最小值 + depth_range_max: '', // 距离范围最大值 + }, + imu:{ + port: '', // IMU串口号 + } +}) +const calculatedAge = ref(null) +//修改 + +// 模拟历史数据 +const historyData = ref([]) +const chartoption = ref({ + backgroundColor: '#242424', + grid: { top: 0, right: 0, bottom: 0, left: 0 }, + animation: false, + series: [ + { + type: 'gauge', + radius: '180%', + center: ['50%', '91%'], + startAngle: 180, + endAngle: 0, + min: -90, + max: 90, + splitNumber: 10, + itemStyle: { + color: '#58D9F9', + shadowColor: 'rgba(0,138,255,0.45)', + shadowBlur: 10, + shadowOffsetX: 2, + shadowOffsetY: 2 + }, + progress: { + show: false, + }, + pointer: { + length: '85%', + width: 2, + offsetCenter: [0, '5%'], + itemStyle: { + color: '#ff8d00' + }, + }, + anchor: { + show: true, + showAbove: true, + size: 18, + icon: 'circle', + itemStyle: { + borderWidth: 3, + color: '#ff8d00', + borderColor: '#ff8d00', + } + }, + axisLine: { + roundCap: true, + lineStyle: { + width: 3, + color: [[1, '#0089ff']] + } + }, + axisTick: { + splitNumber: 2, + lineStyle: { + width: 2, + color: '#ffffff' + } + }, + splitLine: { + length: 12, + lineStyle: { + width: 4, + color: '#0089ff' + } + }, + axisLabel: { + //刻度颜色 + distance: 10, + color: '#ffffff', + fontSize: 10 + }, + title: { + show: false + }, + detail: { + show: false, + }, + data: [ + { + value: 0 + } + ] + } + ] +}) +// 四个设备的独立连接状态 +const cameraStatus = ref('未连接') // 相机设备状态 +const femtoboltStatus = ref('未连接') // 深度相机(FemtoBolt)设备状态 +const imuStatus = ref('未连接') // IMU设备状态 +const pressureStatus = ref('未连接') // 压力传感器设备状态 + +// 为了向后兼容,保留videoStatus但映射到cameraStatus +const videoStatus = computed(() => cameraStatus.value) +// 计时器状态 +const seconds = ref(0); +const isRunning = ref(false); +const timerId = ref(null); +const blinkState = ref(false); +const formattedTime = computed(() => { -const formattedTime = computed(() => { // 计算格式化后的时间 录制时间 const mins = Math.floor(seconds.value / 60); 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; @@ -390,210 +739,106 @@ const resetTimer = () => { seconds.value = 0; blinkState.value = false; }; -const startRecord = async () => { // 开始录屏 - try { - console.log('🚀 正在开始录屏...') - startTimer() - // 验证患者信息 - if (!patientInfo.value || !patientInfo.value.sessionId) { - throw new Error('缺少患者信息,无法开始录屏') - } - let screen_location = contenGridRef.value.getBoundingClientRect() - let femtobolt_location = wholeBodyRef.value.getBoundingClientRect() - let camera1_location = camera1Ref.value?.getBoundingClientRect() - let camera2_location = camera2Ref.value?.getBoundingClientRect() - let titile_height = 24 - // 调用后端API开始录屏 - const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/start_record`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - patient_id: patientId.value, - // 可以添加其他录屏参数 - creator_id: creatorId.value, - screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)], - camera1_location:[ - Math.round(camera1_location.x), Math.round(camera1_location.y)+ titile_height, - Math.round(camera1_location.width), Math.round(camera1_location.height) - ], - camera2_location:[ - Math.round(camera2_location.x), Math.round(camera2_location.y)+ titile_height, - Math.round(camera2_location.width), Math.round(camera2_location.height) - ], - femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height)], - - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - - const result = await response.json() - - if (result.success) { - // 保存会话ID和检测开始时间 - patientInfo.value.detectionStartTime = Date.now() - console.log('✅ 录屏会话创建成功,会话ID:', patientInfo.value.sessionId) - isRecording.value = true - ElMessage.success('录屏已开始') +const validateForm = async () => { + try { + await patientFormRef.value.validate() + return true + } catch (error) { + ElMessage.error('请完善必填信息') + return false + } +} +//保存基础信息 +const handleSave = async () => { + if (!(await validateForm())) return + saveLoading.value = true + try { + await savePatient() + ElMessage.success('修改成功') + dialogVisible.value = false + saveLoading.value = false + loadPatientInfo() + } catch (error) { + ElMessage.error('修改失败:' + error.message) + saveLoading.value = false + } finally { + saveLoading.value = false + } +} +const savePatient = async () => { + const patientData = { + id: patientForm.value.id, + name: patientForm.value.name, + gender: patientForm.value.gender, + age: calculatedAge.value, + birth_date: patientForm.value.birth_date, + height: patientForm.value.height, + weight: patientForm.value.weight, + shoe_size: patientForm.value.shoe_size, + phone: patientForm.value.phone, + occupation: patientForm.value.occupation, + email: patientForm.value.email, + nationality: patientForm.value.nationality, + residence: patientForm.value.residence, + workplace: patientForm.value.workplace + } + try { + const response = await patientAPI.updatePatient(patientForm.value.id, patientData) + if (response.success) { + return response.data } else { - throw new Error(result.message || '开始录屏失败') + throw new Error(response.message || '修改失败') } } catch (error) { - ElMessage.error(`开始录屏失败: ${error.message}`) throw error } } - -const stopRecord = async () => { // 停止录屏 - try { - resetTimer() - // 计算检测持续时间 - let duration = 0 - if (patientInfo.value.detectionStartTime) { - duration = Math.floor((Date.now() - patientInfo.value.detectionStartTime) / 1000) - } - - // 调用后端API停止检测 - const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop_record`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - duration: duration - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - isRecording.value = false - - } catch (error) { - console.error('❌ 停止检测失败:', error) - ElMessage.error(`停止检测失败: ${error.message}`) +const isPreventCombo = ref(false) +function routeTo(path = '/') { + if (isPreventCombo.value === true) { + ElMessage.warning(`请勿连续点击回退按钮!`) + return } + isPreventCombo.value = true + setTimeout(() => { + isPreventCombo.value = false + }, 2000) + router.push(path) } -const isStartVideo = ref(false) -function startVideoClick() { - startRecord() - isStartVideo.value = true -} -function stopVideoClick() { - stopRecord() - isStartVideo.value = false -} -function endClick() { - emit('endChange',false) - ElMessage.success('结束监测') -} -const patientId = ref("") // 患者ID -const loadPatientInfo = async () => { // 加载患者信息 - try { - debugger - // 从路由参数获取患者ID - // patientId.value = props.selectedPatient.id - patientId.value = '202511150005' - if (patientId.value == '' || patientId.value == null) { - console.warn('未找到患者ID参数') - return +function cameraUpdate() { // 相机设置数据更新弹框 + cameraForm.value = { // 相机参数 + camera1:{ + device_index: '', // 序号1 + }, + camera2:{ + device_index: '', // 序号2 + }, + femtobolt:{ + algorithm_type: '', // 算法类型 + depth_mode: '', // 相机模式 + depth_range_min: '', // 距离范围最小值 + depth_range_max: '', // 距离范围最大值 + }, + imu:{ + port: '', // IMU串口号 } - - // 调用API获取患者信息 - const response = await fetch(`${BACKEND_URL}/api/patients/${patientId.value}`) - if (response.ok) { - const result = await response.json() - if (result.success) { - patientInfo.value = { ...result.data, sessionId: null } - - console.log('患者信息加载成功:', patientInfo.value) - } else { - throw new Error(result.message) - } - } else { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - } catch (error) { - console.error('加载患者信息失败:', error) - ElMessage.warning('加载患者信息失败,请检查网络连接') } + // 加载相机参数信息 + getDevicesInit() + } -onMounted(() => { - if (authStore.currentUser) { - creatorId.value = authStore.currentUser.id - } - - // 加载患者信息 - loadPatientInfo() - // 启动检测 - // startDetection() - // 页面加载时自动连接WebSocket - // connectWebSocket() - - // 监听页面关闭或刷新事件 - window.addEventListener('beforeunload', handleBeforeUnload) - -}) -// 开始检测 -async function startDetection() { - try { - console.log('🚀 正在开始检测...') - - // 调用后端API开始检测 - const response = await fetch(`${BACKEND_URL}/api/detection/start`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - patient_id: route.params.id, - // 可以添加其他检测参数 - creator_id: creatorId.value, - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - const result = await response.json() - if (result.success) { - console.log('✅ 检测开始成功') - // 保存会话ID和检测开始时间 - patientInfo.value.sessionId = result.session_id - } else { - throw new Error(result.message || '开始检测失败') - } - - } catch (error) { - console.error('💥 开始检测失败:', error) - ElMessage.error(`开始检测失败: ${error.message}`) - throw error - } -} -// 停止检测 -async function stopDetection() { - try { - // 计算检测持续时间 - let duration = 0 - // 调用后端API停止检测 - const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - duration: duration - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - } catch (error) { - console.error('❌ 停止检测失败:', error) - ElMessage.error(`停止检测失败: ${error.message}`) +const calculateAge = (birthDate) => { + if (!birthDate) return '—' + const today = new Date() + const birth = new Date(birthDate) + let age = today.getFullYear() - birth.getFullYear() + const monthDiff = today.getMonth() - birth.getMonth() + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) { + age-- } + return age } +const tempInfo = ref({}) // WebSocket连接函数 function connectWebSocket() { try { @@ -654,7 +899,7 @@ function connectWebSocket() { console.log('✅ 主WebSocket连接成功!Socket ID:', socket.id) isConnected.value = true //绘制头部仪表盘 - // initchart() + initchart() }) socket.on('connect_error', (error) => { @@ -800,7 +1045,671 @@ function connectWebSocket() { isConnected.value = false } } -const creatorId = ref('') + +// 启动设备数据推送 +function startDeviceDataPush() { + if (devicesSocket && devicesSocket.connected) { + console.log('🚀 发送启动设备数据推送请求...') + devicesSocket.emit('start_push_data') + } else { + console.warn('⚠️ 设备Socket未连接,无法启动设备数据推送') + } +} + +// 断开WebSocket连接函数 +function disconnectWebSocket() { + try { + if (socket) { + if (socket.connected) { + console.log('正在主动断开WebSocket连接...') + + // 移除所有事件监听器 + socket.removeAllListeners() + + // 断开主连接 + socket.disconnect() + + console.log('✅ 主WebSocket连接已断开') + } + socket = null + isConnected.value = false + } + + // 断开统一设备命名空间连接 + if (devicesSocket) { + if (devicesSocket.connected) { + // 取消订阅所有设备 + try { + devicesSocket.emit('unsubscribe_device', { device_type: 'camera' }) + devicesSocket.emit('unsubscribe_device', { device_type: 'femtobolt' }) + devicesSocket.emit('unsubscribe_device', { device_type: 'imu' }) + devicesSocket.emit('unsubscribe_device', { device_type: 'pressure' }) + } catch (e) { + console.warn('取消设备订阅时出错:', e) + } + + // 移除所有事件监听器 + devicesSocket.removeAllListeners() + + // 断开连接 + devicesSocket.disconnect() + + console.log('🔗 统一设备命名空间连接已断开') + } + + devicesSocket = null + cameraSocket = null + femtoboltSocket = null + imuSocket = null + pressureSocket = null + restartSocket = null + } + + // 重置所有设备状态 + cameraStatus.value = '未连接' + femtoboltStatus.value = '未连接' + imuStatus.value = '未连接' + pressureStatus.value = '未连接' + + } catch (error) { + console.warn('断开WebSocket连接时出错:', error) + } +} + +// WebSocket重连函数 +function reconnectWebSocket() { + console.log('开始重新连接WebSocket...') + + // 先断开现有连接 + disconnectWebSocket() + + // 延迟一段时间后重新连接 + setTimeout(() => { + connectWebSocket() + console.log('WebSocket重连完成') + }, 1000) +} + + + + +// 简单的帧显示函数 +function displayFrame(base64Image) { + // 兼容旧调用:默认作为 camera1 更新 + displayCameraFrameById('camera1', base64Image) +} + +function displayCameraFrameById(deviceId, base64Image) { + if (base64Image && base64Image.length > 0) { + const url = 'data:image/jpeg;base64,' + base64Image + if (String(deviceId).toLowerCase() === 'camera2') { + camera2ImgSrc.value = url + } else { + camera1ImgSrc.value = url + // 旧变量保留(避免其它位置引用出错) + rtspImgSrc.value = url + } + } else { + console.warn('⚠️ 收到空的视频帧数据') + } +} + +// 深度相机帧显示函数 +function displayDepthCameraFrame(base64Image) { + if (base64Image && base64Image.length > 0) { + depthCameraImgSrc.value = 'data:image/jpeg;base64,' + base64Image + } else { + console.warn('⚠️ 收到空的深度相机帧数据') + } +} + + +// 头部姿态最值跟踪数据 +const headPoseMaxValues = ref({ + rotationLeftMax: 0, // 旋转-左旋最大值 + rotationRightMax: 0, // 旋转-右旋最大值 + tiltLeftMax: 0, // 倾斜-左倾最大值 + tiltRightMax: 0, // 倾斜-右倾最大值 + pitchUpMax: 0, // 俯仰-上仰最大值 + pitchDownMax: 0 // 俯仰-下俯最大值 +}) + +// 头部姿态历史最值记录数组 +const headPoseHistory = 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°的变化忽略 + + +// 处理IMU头部姿态数据 +function handleIMUData(data) { + try { + if (!data) return + + // 兼容两种载荷结构: + // 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 + } + headlist.value.rotation = rotation // 旋转角度 + headlist.value.tilt = tilt // 倾斜角度 + headlist.value.pitch = pitch // 俯仰角度 + 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.isDisposed()) { + try { + rotationCharts.setOption({ + series: [{ data: [{ value: rVal }] }] + }) + } catch (e) { + console.warn('rotationCharts setOption error:', e); + } + } + if (pitchCharts && !pitchCharts.isDisposed()) { + try { + pitchCharts.setOption({ + series: [{ data: [{ value: pVal }] }] + }) + } catch (e) { + console.warn('pitchCharts setOption error:', e); + } + } + if (tiltCharts && !tiltCharts.isDisposed()) { + try { + tiltCharts.setOption({ + series: [{ data: [{ value: tVal }] }] + }) + } catch (e) { + console.warn('tiltCharts setOption error:', e); + } + } + + // 更新最值跟踪逻辑使用原始数值(不做四舍五入) + updateHeadPoseMaxValues({ rotation, tilt, pitch }) + } catch (error) { + console.error('❌ 处理IMU数据失败:', error) + } +} + +// 更新头部姿态最值 +function updateHeadPoseMaxValues(headPose) { + try { + // 更新旋转角最值 + if (headPose.rotation < 0) { + // 左旋(负值),取绝对值的最大值 + headPoseMaxValues.value.rotationLeftMax = Math.max( + headPoseMaxValues.value.rotationLeftMax, + Math.abs(headPose.rotation) + ) + } else if (headPose.rotation > 0) { + // 右旋(正值) + headPoseMaxValues.value.rotationRightMax = Math.max( + headPoseMaxValues.value.rotationRightMax, + headPose.rotation + ) + } + + // 更新倾斜角最值 + if (headPose.tilt < 0) { + // 左倾(负值),取绝对值的最大值 + headPoseMaxValues.value.tiltLeftMax = Math.max( + headPoseMaxValues.value.tiltLeftMax, + Math.abs(headPose.tilt) + ) + } else if (headPose.tilt > 0) { + // 右倾(正值) + headPoseMaxValues.value.tiltRightMax = Math.max( + headPoseMaxValues.value.tiltRightMax, + headPose.tilt + ) + } + + // 更新俯仰角最值 + if (headPose.pitch < 0) { + // 下俯(负值),取绝对值的最大值 + headPoseMaxValues.value.pitchDownMax = Math.max( + headPoseMaxValues.value.pitchDownMax, + Math.abs(headPose.pitch) + ) + } else if (headPose.pitch > 0) { + // 上仰(正值) + headPoseMaxValues.value.pitchUpMax = Math.max( + headPoseMaxValues.value.pitchUpMax, + headPose.pitch + ) + } + + } catch (error) { + console.error('❌ 更新头部姿态最值失败:', error) + } +} + +// 清零最值并开始跟踪 +function clearAndStartTracking() { + try { + // 重置所有最值为0 + headPoseMaxValues.value = { + rotationLeftMax: 0, + rotationRightMax: 0, + tiltLeftMax: 0, + tiltRightMax: 0, + pitchUpMax: 0, + pitchDownMax: 0 + } + } catch (error) { + ElMessage.error('清零失败') + } +} + +// 保存当前最值到历史记录 +function saveMaxValuesToHistory() { + try { + // 创建当前最值的副本 + const currentMaxValues = { + id: headPoseHistory.value.length + 1, + rotationLeftMax: Number(headPoseMaxValues.value.rotationLeftMax.toFixed(1)), + rotationRightMax: Number(headPoseMaxValues.value.rotationRightMax.toFixed(1)), + tiltLeftMax: Number(headPoseMaxValues.value.tiltLeftMax.toFixed(1)), + tiltRightMax: Number(headPoseMaxValues.value.tiltRightMax.toFixed(1)), + pitchUpMax: Number(headPoseMaxValues.value.pitchUpMax.toFixed(1)), + pitchDownMax: Number(headPoseMaxValues.value.pitchDownMax.toFixed(1)), + timestamp: new Date().toLocaleString() + } + + // 添加到历史记录 + headPoseHistory.value.push(currentMaxValues) + + + // console.log('💾 头部姿态最值已保存:', currentMaxValues) + // ElMessage.success(`头部姿态最值已保存(第${currentMaxValues.id}组)`) + + // 更新历史数据表格(如果存在的话) + updateHistoryTable() + } catch (error) { + // console.error('❌ 保存最值失败:', error) + ElMessage.error('保存最值失败') + } +} + +// 更新历史数据表格 +function updateHistoryTable() { + try { + // 将头部姿态最值数据合并到现有的历史数据中 + if (headPoseHistory.value.length > 0) { + const latestData = headPoseHistory.value[headPoseHistory.value.length - 1] + + // 更新historyData数组,添加头部姿态最值数据 + const newHistoryItem = { + id: latestData.id, + rotLeft: latestData.rotationLeftMax, + rotRight: latestData.rotationRightMax, + tiltLeft: latestData.tiltLeftMax, + tiltRight: latestData.tiltRightMax, + pitchDown: latestData.pitchDownMax, + pitchUp: latestData.pitchUpMax, + timestamp: latestData.timestamp + } + // 如果historyData数组存在,则添加新数据 + if (historyData.value) { + historyData.value.push(newHistoryItem) + } + historyData.value.sort((a, b) => b.id - a.id); + 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 + + // 更新足部压力数据 + // 显示分区压力值 + if (pressureData.pressure_zones) { + footPressure.value = pressureData.pressure_zones + } + + + // 处理压力图片 + if (pressureData.pressure_image) { + // console.log(' 📊 接收到压力分布图片 (base64格式)') + // 这里可以将图片显示在界面上 + if (pressureData.pressure_image && pressureData.pressure_image.length > 0) { + footImgSrc.value = pressureData.pressure_image + } else { + console.warn('⚠️ 收到空的压力传感器数据图') + } + } + } + } catch (error) { + console.error('❌ 处理压力传感器数据失败:', error) + } +} + + +async function handleDiagnosticInfo(status) { + try { + // 检查是否有活跃的会话ID + if (!patientInfo.value.sessionId) { + throw new Error('缺少会话Id') + } + // 调用后端API采集检测数据 + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/save-info`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + diagnosis_info: diagnosticForm.diagnosis_info, + treatment_info: diagnosticForm.treatment_info, + suggestion_info: diagnosticForm.suggestion_info, + status: status, + session_id: patientInfo.value.sessionId, + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const result = await response.json() + if (result.success) { + // 显示成功消息 + ElMessage.success({ + message: status + '诊断信息成功', + duration: 5000 + }) + + } else { + throw new Error(result.message || '诊断信息失败') + } + } catch (error) { + ElMessage.error({ + message: errorMessage, + duration: 5000 + }) + + } finally { + + } +} + +// 保存检测数据 +async function saveDetectionData() { + console.log(tempInfo.value) + if (screenshotLoading.value) return + + try { + screenshotLoading.value = true + // 显示保存进度 + ElMessage.info('正在保存检测截图数据...') + + // 检查是否有活跃的会话ID + if (!patientInfo.value.sessionId) { + throw new Error('请先开始检测再进行数据保存') + } + const base64 = 'data:image/jpeg;base64,' + + let body_image = "" + if(tempInfo.value.femtobolt_frame != null + && tempInfo.value.femtobolt_frame.depth_image != null){ + body_image = base64 + tempInfo.value.femtobolt_frame.depth_image + } + + let pressure_image = "" + let foot_data = "" + if(tempInfo.value.pressure_data != null + && tempInfo.value.pressure_data.foot_pressure != null + && tempInfo.value.pressure_data.foot_pressure.pressure_image != null){ + pressure_image = tempInfo.value.pressure_data.foot_pressure.pressure_image + foot_data = tempInfo.value.pressure_data.foot_pressure.pressure_zones + } + let foot_image="" + if(tempInfo.value.camera_frame != null + && tempInfo.value.camera_frame.image != null ){ + foot_image=base64 + tempInfo.value.camera_frame.image + } + + let head_pose={} + if(tempInfo.value.imu_data != null ){ + head_pose=tempInfo.value.imu_data + } + if(headPoseMaxValues !=null){ + head_pose.headPoseMaxValues = headPoseMaxValues.value + } + let screen_location = contenGridRef.value.getBoundingClientRect() + // 调用后端API保存截图 + const result = await sendDetectionData({ + + session_id: patientInfo.value.sessionId, + patient_id: patientInfo.value.id, + + head_pose:head_pose, + body_pose:null, + body_image: body_image, + + foot_data:foot_data, + foot_image:foot_image, + foot_data_image:pressure_image, + screen_image:null + + }) + isoperation.value = true + // 显示成功消息和文件路径 + ElMessage.success({ + message: `检测数据保存成功!`, + duration: 5000 + }) + + + } catch (error) { + console.error('❌ 检测数据保存失败:', error) + + // 根据错误类型显示不同的错误消息 + let errorMessage = '检测数据保存失败' + if (error.message.includes('网络连接失败')) { + errorMessage = '网络连接失败,请检查后端服务是否正常运行' + } else if (error.message.includes('服务器错误')) { + errorMessage = error.message + } else if (error.message.includes('未找到检测数据区域')) { + errorMessage = '检测数据区域不存在,请刷新页面重试' + } else if (error.message.includes('未找到检测数据')) { + errorMessage = '检测数据不存在,请刷新页面重试' + } else { + errorMessage = `检测数据保存失败: ${error.message}` + } + + ElMessage.error({ + message: errorMessage, + duration: 5000 + }) + + } finally { + screenshotLoading.value = false + } +} + +// 调用后端API保存检测数据 +async function sendDetectionData(data) { + try { + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/collect` + , { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const result = await response.json() + + if (result.success) { + console.log('📸 截图保存成功:', result.filepath) + return result + } else { + throw new Error(result.message || '保存失败') + } + + } catch (error) { + console.error('💥 保存截图API调用失败:', error) + + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('网络连接失败,请检查后端服务是否正常运行') + } else if (error.message.includes('HTTP')) { + throw new Error(`服务器错误: ${error.message}`) + } else { + throw new Error(error.message) + } + } +} + +// 处理开始/停止按钮点击 +async function handleStartStop() { + if (isRecording.value) { + // 停止录制视频 + await stopRecord() + } else { + // 开始录制视频 + await startRecord() + } +} +// 开始检测 +async function startDetection() { + try { + console.log('🚀 正在开始检测...') + + // 调用后端API开始检测 + const response = await fetch(`${BACKEND_URL}/api/detection/start`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + patient_id: patientId.value, + // 可以添加其他检测参数 + creator_id: creatorId.value, + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + const result = await response.json() + if (result.success) { + console.log('✅ 检测开始成功') + // 保存会话ID和检测开始时间 + patientInfo.value.sessionId = result.session_id + } else { + throw new Error(result.message || '开始检测失败') + } + + } catch (error) { + console.error('💥 开始检测失败:', error) + ElMessage.error(`开始检测失败: ${error.message}`) + throw error + } +} + +// 停止检测 +async function stopDetection() { + try { + // 计算检测持续时间 + let duration = 0 + // 调用后端API停止检测 + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + duration: duration + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + } catch (error) { + console.error('❌ 停止检测失败:', error) + ElMessage.error(`停止检测失败: ${error.message}`) + } +} +// 格式化日期方法 +const formatDate = (dateString) => { + if (!dateString) return '-' + const date = new Date(dateString) + return date.toLocaleDateString('zh-CN') +} +const patientId = ref("") + +// 加载患者信息 +const loadPatientInfo = async () => { + try { + // 从路由参数获取患者ID + + if (patientId.value == '' || patientId.value == null) { + console.warn('未找到患者ID参数') + return + } + // 调用API获取患者信息 + const response = await fetch(`${BACKEND_URL}/api/patients/${patientId.value}`) + if (response.ok) { + const result = await response.json() + if (result.success) { + patientInfo.value = { ...result.data, sessionId: null } + + console.log('患者信息加载成功:', patientInfo.value) + } else { + throw new Error(result.message) + } + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + } catch (error) { + console.error('加载患者信息失败:', error) + ElMessage.warning('加载患者信息失败,请检查网络连接') + } +} + // 处理页面关闭或刷新事件 const handleBeforeUnload = (event) => { console.log('页面即将关闭,正在清理资源...') @@ -813,29 +1722,88 @@ const handleBeforeUnload = (event) => { console.log('✅ 资源清理完成') } - - - -// 深度相机帧显示函数 -function displayDepthCameraFrame(base64Image) { - if (base64Image && base64Image.length > 0) { - depthCameraImgSrc.value = 'data:image/jpeg;base64,' + base64Image - } else { - console.warn('⚠️ 收到空的深度相机帧数据') - } +const creatorId = ref('') +let rotationCharts = null; +let pitchCharts = null; +let tiltCharts = null; +const initchart = () => { + // 确保 DOM 元素已经渲染 + nextTick(() => { + const chartDom = document.getElementById('rotationChartId'); + if (chartDom) { + // 如果图表已经存在,先销毁 + if (rotationCharts) { + try { + rotationCharts.dispose(); + } catch (e) { + console.warn('rotationCharts dispose error:', e); + } + rotationCharts = null; + } + rotationCharts = echarts.init(chartDom); + rotationCharts.setOption(chartoption.value); + } else { + console.warn('找不到 ID 为 rotationChartId 的 DOM 元素'); + } + const chartDom2 = document.getElementById('pitchChartId'); + if (chartDom2) { + // 如果图表已经存在,先销毁 + if (pitchCharts) { + try { + pitchCharts.dispose(); + } catch (e) { + console.warn('pitchCharts dispose error:', e); + } + pitchCharts = null; + } + pitchCharts = echarts.init(chartDom2); + pitchCharts.setOption(chartoption.value); + } else { + console.warn('找不到 ID 为 pitchChartId 的 DOM 元素'); + } + const chartDom3 = document.getElementById('tiltChartId'); + if (chartDom3) { + // 如果图表已经存在,先销毁 + if (tiltCharts) { + try { + tiltCharts.dispose(); + } catch (e) { + console.warn('tiltCharts dispose error:', e); + } + tiltCharts = null; + } + tiltCharts = echarts.init(chartDom3); + tiltCharts.setOption(chartoption.value); + } else { + console.warn('找不到 ID 为 tiltChartId 的 DOM 元素'); + } + // 添加窗口大小调整监听器 + window.addEventListener('resize', () => { + if (rotationCharts && !rotationCharts.isDisposed()) { + try { + rotationCharts.resize(); + } catch (e) { + console.warn('rotationCharts resize error:', e); + } + } + if (pitchCharts && !pitchCharts.isDisposed()) { + try { + pitchCharts.resize(); + } catch (e) { + console.warn('pitchCharts resize error:', e); + } + } + if (tiltCharts && !tiltCharts.isDisposed()) { + try { + tiltCharts.resize(); + } catch (e) { + console.warn('tiltCharts resize error:', e); + } + } + }); + }); } -// 头部姿态最值跟踪数据 -const headPoseMaxValues = ref({ - rotationLeftMax: 0, // 旋转-左旋最大值 - rotationRightMax: 0, // 旋转-右旋最大值 - tiltLeftMax: 0, // 倾斜-左倾最大值 - tiltRightMax: 0, // 倾斜-右倾最大值 - pitchUpMax: 0, // 俯仰-上仰最大值 - pitchDownMax: 0 // 俯仰-下俯最大值 -}) - - -const calibrationClick = async () => { // 校准按钮点击事件 +const calibrationClick = async () => { const response = await fetch(`${BACKEND_URL}/api/devices/calibrate/imu`, { method: 'POST', headers: { @@ -854,23 +1822,222 @@ const calibrationClick = async () => { // 校准按钮点击事件 } } -// 清零最值并开始跟踪 -function clearAndStartTracking() { - try { - // 重置所有最值为0 - headPoseMaxValues.value = { - rotationLeftMax: 0, - rotationRightMax: 0, - tiltLeftMax: 0, - tiltRightMax: 0, - pitchUpMax: 0, - pitchDownMax: 0 + +const cameraSubmit = async () => { + const response = await fetch(`${BACKEND_URL}/api/config/devices/all`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(cameraForm.value) + }) + if (response.ok) { + const result = await response.json() + if (result.success) { + ElMessage.success(result.message) + cameraDialogVisible.value = false + } else { + ElMessage.error(result.message) } - } catch (error) { - ElMessage.error('清零失败') } } +// 加载相机参数信息 +const getDevicesInit = async () => { + try { + // 调用API获取患者信息 + const response = await fetch(`${BACKEND_URL}/api/config/devices`) + if (response.ok) { + const result = await response.json() + if (result.success) { + console.log('相机参数加载成功:', result.data) + cameraForm.value = result.data + cameraDialogVisible.value = true + // console.log('相机参数加载成功:', patientInfo.value) + } else { + throw new Error(result.message) + } + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + } catch (error) { + console.error('加载相机参数失败:', error) + ElMessage.warning('加载相机参数失败,请检查网络连接') + } +} + +onMounted(() => { + + if (authStore.currentUser) { + console.log(authStore.currentUser) + creatorId.value = authStore.currentUser.id + } + // patientId.value = props.selectedPatient.id + patientId.value = '202511150005' + // 加载患者信息 + loadPatientInfo() + // 启动检测 + startDetection() + // 页面加载时自动连接WebSocket + connectWebSocket() + + // 监听页面关闭或刷新事件 + window.addEventListener('beforeunload', handleBeforeUnload) + +}) + +onUnmounted(() => { + console.log('🔄 Detection组件正在卸载,开始清理资源...') + + try { + // 清理定时器 + if (timerId.value) { + clearInterval(timerId.value) + timerId.value = null + console.log('✅ 定时器已清理') + } + + // // 停止录制 + // if (isRecording.value === true) { + // stopRecord() + // console.log('✅ 录制已停止') + // } + + // // 停止检测 + // stopDetection() + + // 断开WebSocket连接 + disconnectWebSocket() + + // 清理图表资源 + if (tiltCharts) { + try { + tiltCharts.dispose() + console.log('✅ tiltCharts已清理') + } catch (e) { + console.warn('tiltCharts dispose error in onUnmounted:', e) + } + tiltCharts = null + } + + if (rotationCharts) { + try { + rotationCharts.dispose() + console.log('✅ rotationCharts已清理') + } catch (e) { + console.warn('rotationCharts dispose error in onUnmounted:', e) + } + rotationCharts = null + } + + if (pitchCharts) { + try { + pitchCharts.dispose() + } catch (e) { + + } + pitchCharts = null + } + + // 移除页面关闭事件监听器 + window.removeEventListener('beforeunload', handleBeforeUnload) + } catch (error) { + + } +}) + +const startRecord = async () => { // 开始录屏 + try { + console.log('🚀 正在开始录屏...') + startTimer() + // 验证患者信息 + if (!patientInfo.value || !patientInfo.value.sessionId) { + throw new Error('缺少患者信息,无法开始录屏') + } + let screen_location = contenGridRef.value.getBoundingClientRect() + let femtobolt_location = wholeBodyRef.value.getBoundingClientRect() + let camera1_location = camera1Ref.value?.getBoundingClientRect() + let camera2_location = camera2Ref.value?.getBoundingClientRect() + let titile_height = 24 + // 调用后端API开始录屏 + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/start_record`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + patient_id: patientId.value, + // 可以添加其他录屏参数 + creator_id: creatorId.value, + screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)], + camera1_location:[ + Math.round(camera1_location.x), Math.round(camera1_location.y)+ titile_height, + Math.round(camera1_location.width), Math.round(camera1_location.height) + ], + camera2_location:[ + Math.round(camera2_location.x), Math.round(camera2_location.y)+ titile_height, + Math.round(camera2_location.width), Math.round(camera2_location.height) + ], + femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height)], + + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const result = await response.json() + + if (result.success) { + // 保存会话ID和检测开始时间 + patientInfo.value.detectionStartTime = Date.now() + console.log('✅ 录屏会话创建成功,会话ID:', patientInfo.value.sessionId) + isRecording.value = true + ElMessage.success('录屏已开始') + } else { + throw new Error(result.message || '开始录屏失败') + } + } catch (error) { + ElMessage.error(`开始录屏失败: ${error.message}`) + throw error + } +} + +const stopRecord = async () => { // 停止录屏 + try { + resetTimer() + // 计算检测持续时间 + let duration = 0 + if (patientInfo.value.detectionStartTime) { + duration = Math.floor((Date.now() - patientInfo.value.detectionStartTime) / 1000) + } + + // 调用后端API停止检测 + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop_record`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + duration: duration + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + isRecording.value = false + + } catch (error) { + console.error('❌ 停止检测失败:', error) + ElMessage.error(`停止检测失败: ${error.message}`) + } +} +function routerClick(){ + historyDialogVisible.value = true +} + +const isRestart = ref(false) // 防止连击 + // 单个刷新数据 function refreshClick(type) { // 检查是否在冷却期内 @@ -900,47 +2067,50 @@ function refreshClick(type) { } } -// 处理压力传感器足部压力数据 -function handlePressureData(data) { - try { - if (data && data.foot_pressure) { - const pressureData = data.foot_pressure +function closeDiagnosticMessage(e){ + isDiagnosticMessage.value = false + if(e== true){ + try { + // 清理定时器 + if (timerId.value) { + clearInterval(timerId.value) + timerId.value = null + console.log('✅ 定时器已清理') + } + + // 停止录制 + if (isRecording.value === true) { + stopRecord() + console.log('✅ 录制已停止') + } - // 更新足部压力数据 - // 显示分区压力值 - if (pressureData.pressure_zones) { - footPressure.value = pressureData.pressure_zones - } + // 停止检测 + stopDetection() + // 断开WebSocket连接 + disconnectWebSocket() + // 移除页面关闭事件监听器 + window.removeEventListener('beforeunload', handleBeforeUnload) + emit('endChange',true) + } catch (error) { + console.error('❌ Detection组件卸载时出错:', error) + emit('endChange',true) + } - // 处理压力图片 - if (pressureData.pressure_image) { - // console.log(' 📊 接收到压力分布图片 (base64格式)') - // 这里可以将图片显示在界面上 - if (pressureData.pressure_image && pressureData.pressure_image.length > 0) { - footImgSrc.value = pressureData.pressure_image - } else { - console.warn('⚠️ 收到空的压力传感器数据图') - } - } - } - } catch (error) { - console.error('❌ 处理压力传感器数据失败:', error) } + } -function displayCameraFrameById(deviceId, base64Image) { // 显示相机帧 - if (base64Image && base64Image.length > 0) { - const url = 'data:image/jpeg;base64,' + base64Image - if (String(deviceId).toLowerCase() === 'camera2') { - camera2ImgSrc.value = url - } else { - camera1ImgSrc.value = url - // 旧变量保留(避免其它位置引用出错) - rtspImgSrc.value = url - } - } else { - console.warn('⚠️ 收到空的视频帧数据') + + +function closecreatbox(e){ + if(e == true){ + loadPatientInfo() } + isCloseCreat.value = false +} + +function handleEditUserInfo(){ + isCloseCreat.value = true } @@ -955,7 +2125,8 @@ function displayCameraFrameById(deviceId, base64Image) { // 显示相机帧 } .detection-container-box .el-row { - height: calc(100% - 125px); + margin: 0!important; + height: calc(100%); } .username-line { @@ -1292,4 +2463,257 @@ function displayCameraFrameById(deviceId, base64Image) { // 显示相机帧 width: 100%; height: calc(50% - 8px); } +.patientInfotop1{ + font-weight: 700; + font-style: normal; + font-size: 22px; + color: #FFFFFF; + margin:0 20px; + margin-left:40px +} +.patientInfotop2{ + font-weight: 400; + font-style: normal; + font-size: 16px; + color: #FFFFFF; +} +.endbutton{ + width: 100px; + height: 36px; + background-color: rgba(255, 102, 0, 1); + border:1px solid rgba(255, 102, 0, 1); + font-size: 16px; + color: #FFFFFF; + margin-left:20px +} + +.pop-up-mask{ + position: fixed; + z-index: 99; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.7); +} + +.pop-up-tip-container { + width: 400px; + height:220px; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + margin: auto; + background: #1b1b1b; +} +.pop-up-camera-container{ + width: 608px; + height:495px; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + margin: auto; + background: #1b1b1b; + border-radius: 10px; + box-shadow: 0px 0px 10px rgba(80, 80, 80, 1); + border-width: 1px; + border-style: solid; + border-color: rgba(148, 148, 148, 1); +} +.pop-up-camera-header{ + width: 100%; + height: 50px; + display: flex; + align-items: center; + justify-content: space-between; + background-color: rgba(46, 52, 59, 1); + box-sizing: border-box; + padding: 0 20px; + font-family: 'Noto Sans SC'; + font-weight: 700; + font-style: normal; + font-size: 16px; + color: #FFFFFF; + text-align: left; + border-radius:10px 10px 0 0; +} + +.pop-up-tip-header{ + width: 100%; + height: 50px; + display: flex; + align-items: center; + justify-content: space-between; + background-color: rgba(46, 52, 59, 1); + box-sizing: border-box; + padding: 0 20px; + font-family: 'Noto Sans SC'; + font-weight: 700; + font-style: normal; + font-size: 16px; + color: #FFFFFF; + text-align: left; +} +.tipconfirmbutton-box{ + width:100%; + display:flex; + align-items: center; + justify-content: center; +} +.tipconfirmbutton{ + font-weight: 400; + font-style: normal; + font-size: 14px; + color: rgb(255, 255, 255); + background:#266fff; + border:1px solid #266fff; + width: 80px; + height: 40px; +} +.pop-up-tip-text{ + width:100%; + font-weight: 400; + font-style: normal; + font-size: 16px; + color: #FFFFFF; + text-align: center; + padding:40px 10px; +} +.pop-up-camera-body{ + padding: 20px 40px; +} +.pop-up-camera-display{ + display: flex; + align-items: center; +} +.pop-up-camera-line{ + width: 4px; + height: 13px; + background-color: rgba(38, 111, 255, 1); + margin-right: 3px; +} +.pop-up-camera-title{ + font-weight: 700; + font-style: normal; + font-size: 15px; + color: #FFFFFF; +} +.pop-up-camera-body :deep(.el-input__wrapper) { + background-color: rgba(40, 40, 40, 1); + border-width: 1px; + border-style: solid; + border-color: rgba(54, 54, 54, 1); + border-radius: 4px; + box-shadow: none; + height: 40px; + +} +.pop-up-camera-body :deep(.el-input__wrapper:hover) { + border-color: rgba(38, 111, 255, 1); + + +} +.pop-up-camera-body :deep( .el-input__inner) { + font-weight: 400; + font-style: normal; + font-size: 14px; + letter-spacing: normal; + color: #FFFFFF; + +} +.pop-up-camera-name{ + font-weight: 400; + font-style: normal; + font-size: 14px; + color: #787878; + width: 60px; + text-align: right; + margin-right: 20px; +} +.pop-up-camera-radio{ + width: 70px; + height: 40px; + background: inherit; + background-color: rgba(40, 40, 40, 1); + box-sizing: border-box; + border-width: 1px; + border-style: solid; + border-color: rgba(54, 54, 54, 1); + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; +} +.pop-up-camera-display :deep(.el-radio__label){ + font-weight: 400; + font-style: normal; + color: #FFFFFF; + font-size: 14px; +} +.pop-up-camera-display :deep(.el-radio){ + width: 70px; + height: 40px; + background: inherit; + background-color: rgba(40, 40, 40, 1); + box-sizing: border-box; + border-width: 1px; + border-style: solid; + border-color: rgba(54, 54, 54, 1); + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + margin: 0; +} +.pop-up-camera-display :deep(.el-radio.is-checked){ + width: 70px; + height: 40px; + background: inherit; + background-color: rgba(40, 40, 40, 1); + box-sizing: border-box; + border-width: 1px; + border-style: solid; + border-color: rgba(38, 111, 255, 1); + +} +.pop-up-camera-display :deep(.is-checked .el-radio__label){ + color: rgb(38, 111, 255); +} + +.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner){ + background: rgb(38, 111, 255); + border-color: rgb(38, 111, 255); +} +.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner){ + background: rgb(38, 111, 255); + border-color: rgb(38, 111, 255); +} + +.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner:after){ + background-color: #282828; +} +.pop-up-camera-display :deep(.el-radio__inner){ + background-color: #282828; + border-color: rgb(124, 124, 124); +} + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/views/DiagnosticMessage.vue b/frontend/src/renderer/src/views/DiagnosticMessage.vue new file mode 100644 index 00000000..c004f442 --- /dev/null +++ b/frontend/src/renderer/src/views/DiagnosticMessage.vue @@ -0,0 +1,203 @@ + + + + + + diff --git a/frontend/src/renderer/src/views/PatientCreate.vue b/frontend/src/renderer/src/views/PatientCreate.vue index 554c6018..74d118ef 100644 --- a/frontend/src/renderer/src/views/PatientCreate.vue +++ b/frontend/src/renderer/src/views/PatientCreate.vue @@ -3,7 +3,7 @@
-
新建患者信息
+
{{ patienttitle }}
@@ -154,10 +154,12 @@ const patientForm = reactive({ // 加载状态 const saveLoading = ref(false) const saveAndDetectLoading = ref(false) +const patienttitle = ref("新建患者信息") // 生命周期 onMounted(() => { // 从认证状态管理中加载用户信息 if (props.patienttype == 'edit') { + patienttitle.value = '编辑患者信息' Object.assign(patientForm, props.selectedPatient) } @@ -314,11 +316,6 @@ const handleSaveAndDetect = async () => { bottom: 0; margin: auto; background: #1b1b1b; - - /* height: 100vh; - display: flex; - flex-direction: column; - background: #000000; */ } .nav-container { diff --git a/frontend/src/renderer/src/views/PatientProfile.vue b/frontend/src/renderer/src/views/PatientProfile.vue index f5210dd7..1e83e478 100644 --- a/frontend/src/renderer/src/views/PatientProfile.vue +++ b/frontend/src/renderer/src/views/PatientProfile.vue @@ -1,401 +1,24 @@ @@ -406,146 +29,16 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { patientAPI, detectionAPI,historyAPI,getBackendUrl } from '@/services/api.js' import Header from '@/views/Header.vue' import { useAuthStore } from '@/stores/index.js' +const emit = defineEmits([ 'endChange']); const props = defineProps({ - patientId: { + selectedPatient: { required: false, - type: String, + type: Object, default: null } }) -const authStore = useAuthStore() -const BACKEND_URL = getBackendUrl() -const router = useRouter() -const route = useRoute() -const isHeader = ref(false) -const patientId = ref("") -// 响应式数据 -const patient = ref(null) -const detectionRecords = ref([]) -const searchKeyword = ref('') -const videoDialogVisible = ref(false) -const screenshotDialogVisible = ref(false) -const previewDialogVisible = ref(false) -const currentVideo = ref(null) -const currentScreenshots = ref([]) -const previewScreenshot = ref(null) -const videoPlayerRef = ref() -const dialogVideoVisible = ref(false) -const headPoseMaxValues = ref({ - rotationLeftMax: 0, // 旋转-左旋最大值 - rotationRightMax: 0, // 旋转-右旋最大值 - tiltLeftMax: 0, // 倾斜-左倾最大值 - tiltRightMax: 0, // 倾斜-右倾最大值 - pitchUpMax: 0, // 俯仰-上仰最大值 - pitchDownMax: 0 }) -const userInfo = reactive({ - username: '', - avatar: '' - }) - -// 计算属性 -const filteredRecords = computed(() => { - if (!searchKeyword.value) { - return detectionRecords.value - } - return detectionRecords.value.filter(record => - record.id.toString().includes(searchKeyword.value) || - record.doctor?.includes(searchKeyword.value) || - formatDate(record.createdAt).includes(searchKeyword.value) - ) -}) -const footPressure = ref({}) -const profileInfo = ref([]) -const diagnosticForm = ref({ - diagnosis_info: '', - treatment_info: '', - suggestion_info: '' -}) -const srcList = ref([]) -const imageRef = ref() -function showImage(row){ // 显示大屏图片 - - srcList.value = [row] - setTimeout(() => { - if(imageRef.value){ - imageRef.value.showPreview() - } - }, 300) -} -function bigImgClick(row) { - // 将Windows路径的反斜杠转换为Web URL的正斜杠 - const webPath = row.normal_video_path.replace(/\\/g, '/') - videoUrl.value = BACKEND_URL + '/' + webPath - dialogVideoVisible.value = true -} - -function getDayNum(date2,index){ - // 解析日期字符串 - const parseDate = (dateStr) => { - if (!dateStr) return new Date(); - const parts = dateStr.split(' '); - const datePart = parts[0].split('-'); - const timePart = parts[1].split(':'); - - return new Date( - parseInt(datePart[0]), // 年 - parseInt(datePart[1]) - 1, // 月 (0-indexed) - parseInt(datePart[2]), // 日 - parseInt(timePart[0]), // 小时 - parseInt(timePart[1]), // 分钟 - parseInt(timePart[2]) // 秒 - ); - }; - - const d1 = new Date(); - const d2 = parseDate(date2); - - // 计算时间差(毫秒) - const timeDiff = Math.abs(d1.getTime() - d2.getTime()); - - // 转换为天数 - const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24)); - - if(daysDiff == 0){ - return "当天"; - } - return daysDiff + "天"; -} - -const sessionsInit = async () => { - try { - let params ={ - patient_id: route.params.id - } - // 导出报告逻辑 - const response = await historyAPI.sessionsPage(params) - if (response.success) { - response.data.sessions.forEach(element => { - element.list = [{}] - }); - profileInfo.value = response.data.sessions - } - } catch (error) { - ElMessage.error('获取失败') - } -} - - -const videoUrl = ref("") -const dialogVisible = ref(false) -const handleClose = () => { - dialogVisible.value = false - dialogVideoVisible.value = false - videoUrl.value = "" -} -const detailsDialogVisible = ref(false) -const selectedPatient = ref({}) -const detailshandleClose = () => { - detailsDialogVisible.value = false -} -// 添加计算年龄的方法 -const calculateAge = (birthDate) => { - if (!birthDate) return '-' +const calculateAge = (birthDate) => { // 获取年龄 + if (!birthDate) return '—' const today = new Date() const birth = new Date(birthDate) let age = today.getFullYear() - birth.getFullYear() @@ -555,1047 +48,72 @@ const calculateAge = (birthDate) => { } return age } - -// 方法 -const goBack = () => { - router.go(-1) -} - -const editPatient = () => { - router.push(`/patient/edit/${patient.value.id}`) -} - -const startDetection = () => { - router.push(`/detection/${patient.value.id}`) -} - -const viewRecord = (record) => { - router.push(`/detection/record/${record.id}`) -} - -const playVideo = (record) => { - currentVideo.value = { - url: record.videoUrl, - createdAt: record.createdAt, - duration: record.duration, - fileSize: record.videoFileSize - } - videoDialogVisible.value = true -} - -const viewScreenshots = (record) => { - currentScreenshots.value = record.screenshots || [] - screenshotDialogVisible.value = true -} - -const previewScreenshotHandler = (screenshot, index) => { - previewScreenshot.value = screenshot - previewDialogVisible.value = true -} - -const downloadScreenshot = (screenshot) => { - // 下载截图逻辑 - const link = document.createElement('a') - link.href = screenshot.url - link.download = `screenshot_${screenshot.id}.png` - link.click() -} - -const deleteScreenshot = async (screenshot) => { - try { - await ElMessageBox.confirm('确定要删除这张截图吗?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }) - - // 删除截图逻辑 - ElMessage.success('截图已删除') - // 重新加载截图列表 - loadDetectionRecords() - } catch { - // 用户取消 - } -} - - -const exportReport = async (record) => { - try { - ElMessage.info('正在生成报告...') - // 导出报告逻辑 - const response = await detectionAPI.exportReport(record.id) - if (response.success) { - ElMessage.success('报告导出成功') - } - } catch (error) { - ElMessage.error('报告导出失败') - } -} - -const closeVideoDialog = () => { - if (videoPlayerRef.value) { - videoPlayerRef.value.pause() - } - videoDialogVisible.value = false - currentVideo.value = null -} - -const closeScreenshotDialog = () => { - screenshotDialogVisible.value = false - currentScreenshots.value = [] -} - -const closePreviewDialog = () => { - previewDialogVisible.value = false - previewScreenshot.value = null -} - -const getRecordStatusType = (status) => { - switch (status) { - case '已完成': return 'success' - case '进行中': return 'warning' - case '已中断': return 'danger' - default: return 'info' - } -} - -const formatDate = (date) => { - return new Date(date).toLocaleString('zh-CN') -} - -const formatTime = (timestamp) => { - return new Date(timestamp).toLocaleTimeString('zh-CN') -} - -const formatDuration = (seconds) => { - const minutes = Math.floor(seconds / 60) - const remainingSeconds = seconds % 60 - return `${minutes}分${remainingSeconds}秒` -} - -const formatFileSize = (bytes) => { - if (bytes === 0) return '0 B' - const k = 1024 - const sizes = ['B', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] -} - -const loadPatientInfo = async () => { - try { - const response = await patientAPI.getById(patientId.value) - if (response.success) { - patient.value = response.data - selectedPatient.value = response.data - console.log('加载患者信息成功:', response.data) - } else { - throw new Error(response.message) - } - } catch (error) { - console.error('加载患者信息失败:', error) - // 模拟数据 - patient.value = { - id: route.params.id, - name: '张三', - gender: '男', - age: 45, - height: 175, - weight: 70, - phone: '13800138001', - doctor: '李医生', - createdAt: '2024-01-15T10:30:00Z' - } - } -} - -const loadDetectionRecords = async () => { - try { - const response = await detectionAPI.getByPatientId(patientId.value) - if (response.success) { - detectionRecords.value = response.data - } else { - throw new Error(response.message) - } - } catch (error) { - console.error('加载检测记录失败:', error) - // 模拟数据 - detectionRecords.value = [ - { - id: 1, - patientId: patientId.value, - status: '已完成', - duration: 300, - doctor: '李医生', - createdAt: '2024-01-20T14:20:00Z', - videoUrl: '/videos/detection_1.mp4', - videoFileSize: 52428800, - screenshots: [ - { - id: 1, - timestamp: '2024-01-20T14:22:30Z', - thumbnail: '/data/patients/thumb_1.jpg', - url: '/data/patients/screenshot_1.png', - data: { - pitch: 2.5, - yaw: -1.2, - roll: 0.8, - leftPressure: 45, - rightPressure: 55 - } - }, - { - id: 2, - timestamp: '2024-01-20T14:24:15Z', - thumbnail: '/data/patients/thumb_2.jpg', - url: '/data/patients/screenshot_2.png', - data: { - pitch: 3.1, - yaw: -0.8, - roll: 1.2, - leftPressure: 48, - rightPressure: 52 - } - } - ] - }, - { - id: 2, - patientId: patientId.value, - status: '已完成', - duration: 240, - doctor: '王医生', - createdAt: '2024-01-18T11:30:00Z', - videoUrl: '/videos/detection_2.mp4', - videoFileSize: 41943040, - screenshots: [] - } - ] - } -} -//修改诊断信息 -function editClick(row,index) { - diagnosticForm.value = { - id: row.id, - diagnosis_info: row.diagnosis_info, - treatment_info: row.treatment_info, - suggestion_info: row.suggestion_info, - status: row.status, - index: index - } - dialogVisible.value = true -} -//数据详情 -function patientdetails(row) { - detectionById(row) - detailsDialogVisible.value = true -} -async function handleDiagnosticInfo(status) { - try { - // 检查是否有活跃的会话ID - if (!diagnosticForm.value.id) { - throw new Error('缺少会话Id') - } - // 调用后端API采集检测数据 - const response = await fetch(`${BACKEND_URL}/api/detection/${diagnosticForm.value.id}/save-info`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - diagnosis_info:diagnosticForm.value.diagnosis_info, - treatment_info:diagnosticForm.value.treatment_info, - suggestion_info:diagnosticForm.value.suggestion_info, - status:status, - id:diagnosticForm.value.id, - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - - const result = await response.json() - if (result.success) { - profileInfo.value[diagnosticForm.value.index].diagnosis_info = diagnosticForm.value.diagnosis_info - profileInfo.value[diagnosticForm.value.index].treatment_info = diagnosticForm.value.treatment_info - profileInfo.value[diagnosticForm.value.index].suggestion_info = diagnosticForm.value.suggestion_info - profileInfo.value[diagnosticForm.value.index].status = status - ElMessage.success({ - message: '诊断信息成功', - duration: 5000 - }) - dialogVisible.value =false - } else { - throw new Error(result.message || '诊断信息失败') - } - } catch (error) { - ElMessage.error({ - message: errorMessage, - duration: 5000 - }) - } finally { - } -} - -// 暂停视频 -const pauseVideo = (index) => { - if (videoPlayerRef.value) { - videoPlayerRef.value[index].pause() - } -} - - // 播放视频 -const playNewVideo = () => { - if (videoPlayerRef.value) { - videoPlayerRef.value[index].play() - } -} -const profileForm = ref({}) -function detectionById(row) { - footPressure.value = {} - headPoseMaxValues.value = { - rotationLeftMax: 0, // 旋转-左旋最大值 - rotationRightMax: 0, // 旋转-右旋最大值 - tiltLeftMax: 0, // 倾斜-左倾最大值 - tiltRightMax: 0, // 倾斜-右倾最大值 - pitchUpMax: 0, // 俯仰-上仰最大值 - pitchDownMax: 0 - } - - historyAPI.detectionById(row.id).then((response)=>{ - if(response.success){ - profileForm.value = response.data - if(response.data.foot_data !=null){ - footPressure.value = response.data.foot_data - } - if(response.data.head_pose !=null){ - headPoseMaxValues.value = response.data.head_pose.headPoseMaxValues - } - - - - } - }).catch(()=>{ - - }) -} -const deleteClick = async (row,row2) => { - detectionLatestList(row.id) - ElMessageBox.confirm( - '确定义删除此条数据?', - '提示', - { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning', - } - ).then(() => { - historyAPI.detectionDelById(row2.id).then((response)=>{ - if(response.success){ - ElMessage.success({ - message: response.message, - duration: 5000 - }); - sessionsInit() - // detectionLatestList(row.id) - } - }).catch(()=>{ - }) - }) -} - - -const sessionsDelById = async (row) => { - // 检查状态是否为 checking - if (row.status === 'checking') { - ElMessage.warning({ - message: '平衡体态检查中,不能删除!', - duration: 3000 - }); - return; - } - - ElMessageBox.confirm( - '确定义删除此条数据?', - '提示', - { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning', - } - ).then(() => { - historyAPI.sessionsDelById(row.id).then((response)=>{ - if(response.success){ - ElMessage.success({ - message: response.message, - duration: 5000 - }); - sessionsInit() - } - }).catch(()=>{ - }) - }) -} - -const detectionLatestList = async(id)=>{ - const response = await historyAPI.detectionLatestList(id) - console.log(response) - // historyAPI.detectionLatestList(id).then((response)=>{ - // if(response.success){ - // console.log(response) - // } - // }).catch(()=>{ - // }) -} -// // 其他控制方法 -// const togglePlayPause = () => { -// if (videoPlayerRef.value) { -// if (videoPlayerRef.value.paused) { -// videoPlayerRef.value.play() -// } else { -// videoPlayerRef.value.pause() -// } -// } -// } -// 生命周期 -onMounted(() => { - if(props.patientId != "" && props.patientId != null ){ - patientId.value = props.patientId - isHeader.value = true - }else{ - isHeader.value = false - patientId.value = route.params.id - } - loadPatientInfo() - sessionsInit() - if (authStore.currentUser) { - Object.assign(userInfo, { - username: authStore.currentUser.name, - avatar: authStore.currentUser.avatar || '' - }) - } - // loadDetectionRecords() -}) \ No newline at end of file diff --git a/frontend/src/renderer/src/views/PatientProfile2.vue b/frontend/src/renderer/src/views/PatientProfile2.vue new file mode 100644 index 00000000..f5210dd7 --- /dev/null +++ b/frontend/src/renderer/src/views/PatientProfile2.vue @@ -0,0 +1,1601 @@ + + + + + \ No newline at end of file