diff --git a/backend/database.py b/backend/database.py index e1ac4191..bd43c49b 100644 --- a/backend/database.py +++ b/backend/database.py @@ -641,10 +641,10 @@ class DatabaseManager: creator_id, settings.get('duration', 60), json.dumps(settings), - 'running', - settings.get('diagnosis_info', ''), - settings.get('treatment_info', ''), - settings.get('suggestion_info', ''), + 'checking', + '', + '', + '', china_time, china_time )) diff --git a/backend/devices/base_device.py b/backend/devices/base_device.py index 9d96beb6..ee85f8a6 100644 --- a/backend/devices/base_device.py +++ b/backend/devices/base_device.py @@ -44,6 +44,12 @@ class BaseDevice(ABC): # 状态变化回调 self._status_change_callbacks = [] + # 设备连接监控 + self._connection_monitor_thread = None + self._monitor_stop_event = threading.Event() + self._connection_check_interval = config.get('connection_check_interval', 5.0) # 默认5秒检查一次 + self._connection_timeout = config.get('connection_timeout', 30.0) # 默认30秒超时 + # 设备状态信息 self._device_info = { 'name': device_name, @@ -55,7 +61,7 @@ class BaseDevice(ABC): # 性能统计 self._stats = { - 'frames_processed': 0, + 'frames_processed': 0, 'errors_count': 0, 'start_time': None, 'last_frame_time': None @@ -137,6 +143,16 @@ class BaseDevice(ABC): bool: 重新加载是否成功 """ pass + + @abstractmethod + def check_hardware_connection(self) -> bool: + """ + 检查设备硬件连接状态 + + Returns: + bool: 设备是否物理连接 + """ + pass def set_socketio(self, socketio): """ @@ -193,6 +209,12 @@ class BaseDevice(ABC): # 只有状态真正改变时才触发回调 if old_status != is_connected: self._notify_status_change(is_connected) + + # 启动或停止连接监控 + if is_connected and not self._connection_monitor_thread: + self._start_connection_monitor() + elif not is_connected and self._connection_monitor_thread: + self._stop_connection_monitor() def emit_data(self, event: str, data: Any, namespace: Optional[str] = None): """ @@ -305,6 +327,64 @@ class BaseDevice(ABC): """ with self._lock: self._stats['start_time'] = None + + def _start_connection_monitor(self): + """ + 启动连接监控线程 + """ + if self._connection_monitor_thread and self._connection_monitor_thread.is_alive(): + return + + self._monitor_stop_event.clear() + self._connection_monitor_thread = threading.Thread( + target=self._connection_monitor_worker, + name=f"{self.device_name}_connection_monitor", + daemon=True + ) + self._connection_monitor_thread.start() + self.logger.info(f"设备 {self.device_name} 连接监控线程已启动") + + def _stop_connection_monitor(self): + """ + 停止连接监控线程 + """ + if self._connection_monitor_thread: + self._monitor_stop_event.set() + if self._connection_monitor_thread.is_alive(): + self._connection_monitor_thread.join(timeout=2.0) + self._connection_monitor_thread = None + self.logger.info(f"设备 {self.device_name} 连接监控线程已停止") + + def _connection_monitor_worker(self): + """ + 连接监控工作线程 + """ + self.logger.info(f"设备 {self.device_name} 连接监控开始") + + while not self._monitor_stop_event.is_set(): + try: + # 检查硬件连接状态 + hardware_connected = self.check_hardware_connection() + + # 如果硬件断开但软件状态仍为连接,则更新状态 + if not hardware_connected and self.is_connected: + self.logger.warning(f"检测到设备 {self.device_name} 硬件连接断开") + self.set_connected(False) + break # 硬件断开后停止监控 + + # 检查心跳超时 + if self.is_connected and time.time() - self._last_heartbeat > self._connection_timeout: + self.logger.warning(f"设备 {self.device_name} 心跳超时,判定为断开连接") + self.set_connected(False) + break # 超时后停止监控 + + except Exception as e: + self.logger.error(f"设备 {self.device_name} 连接监控异常: {e}") + + # 等待下次检查 + self._monitor_stop_event.wait(self._connection_check_interval) + + self.logger.info(f"设备 {self.device_name} 连接监控结束") def __enter__(self): """ @@ -318,5 +398,11 @@ class BaseDevice(ABC): """ self.cleanup() + def _cleanup_monitoring(self): + """ + 清理监控线程 + """ + self._stop_connection_monitor() + def __repr__(self): return f"<{self.__class__.__name__}(name='{self.device_name}', connected={self.is_connected}, streaming={self.is_streaming})>" \ No newline at end of file diff --git a/backend/devices/camera_manager.py b/backend/devices/camera_manager.py index b1d9997d..4dc9711c 100644 --- a/backend/devices/camera_manager.py +++ b/backend/devices/camera_manager.py @@ -612,40 +612,61 @@ class CameraManager(BaseDevice): self.logger.error(f"重新加载相机配置失败: {e}") return False + def check_hardware_connection(self) -> bool: + """ + 检查相机硬件连接状态 + + Returns: + bool: 相机是否物理连接 + """ + try: + if self.cap and self.cap.isOpened(): + # 尝试读取一帧来验证连接 + ret, _ = self.cap.read() + return ret + return False + except Exception as e: + self.logger.debug(f"检查相机硬件连接时发生异常: {e}") + return False + def cleanup(self): """ 清理资源 """ try: - self.stop_streaming() + self.logger.info("开始清理相机资源") + + # 清理监控线程 + self._cleanup_monitoring() + + # 停止流 + if self.is_streaming: + self.stop_streaming() + + # 断开连接 + self.disconnect() - if self.cap: - try: - self.cap.release() - except Exception: - pass - self.cap = None - # 清理帧缓存 while not self.frame_cache.empty(): try: self.frame_cache.get_nowait() except queue.Empty: break - self.last_frame = None - # 清理帧队列 + # 清理全局帧队列 while not self.frame_queue.empty(): try: self.frame_queue.get_nowait() except queue.Empty: break + self.last_frame = None + super().cleanup() self.logger.info("相机资源清理完成") except Exception as e: - self.logger.error(f"清理相机资源失败: {e}") + self.logger.error(f"清理相机资源时发生错误: {e}") def _save_frame_to_cache(self, frame, frame_type='camera'): """保存帧到全局缓存""" diff --git a/backend/devices/femtobolt_manager.py b/backend/devices/femtobolt_manager.py index 4ff854ca..538995e2 100644 --- a/backend/devices/femtobolt_manager.py +++ b/backend/devices/femtobolt_manager.py @@ -830,11 +830,40 @@ class FemtoBoltManager(BaseDevice): self.logger.error(f"重新加载FemtoBolt配置失败: {e}") return False + def check_hardware_connection(self) -> bool: + """ + 检查FemtoBolt设备硬件连接状态 + + Returns: + bool: 设备是否物理连接 + """ + try: + if not self.sdk_initialized or not self.device_handle: + return False + + # 尝试获取设备状态来验证连接 + if hasattr(self.femtobolt, 'device_get_capture'): + try: + # 尝试获取一帧数据来验证设备连接 + capture = self.femtobolt.device_get_capture(self.device_handle, timeout_in_ms=1000) + if capture: + self.femtobolt.capture_release(capture) + return True + except Exception: + pass + return False + except Exception as e: + self.logger.debug(f"检查FemtoBolt硬件连接时发生异常: {e}") + return False + def cleanup(self): """ 清理资源 """ try: + # 清理监控线程 + self._cleanup_monitoring() + self.stop_streaming() self._cleanup_device() diff --git a/backend/devices/imu_manager.py b/backend/devices/imu_manager.py index 042a063d..5a3e44e4 100644 --- a/backend/devices/imu_manager.py +++ b/backend/devices/imu_manager.py @@ -892,11 +892,51 @@ class IMUManager(BaseDevice): self.logger.error(f"重新加载IMU配置失败: {e}") return False + def check_hardware_connection(self) -> bool: + """ + 检查IMU硬件连接状态 + """ + try: + if not self.imu_device: + return False + + # 对于真实设备,检查串口连接状态 + if hasattr(self.imu_device, 'ser') and self.imu_device.ser: + # 检查串口是否仍然打开 + if not self.imu_device.ser.is_open: + return False + + # 尝试读取数据来验证连接 + try: + # 保存当前超时设置 + original_timeout = self.imu_device.ser.timeout + self.imu_device.ser.timeout = 0.1 # 设置短超时 + + # 尝试读取少量数据 + test_data = self.imu_device.ser.read(1) + + # 恢复原始超时设置 + self.imu_device.ser.timeout = original_timeout + + return True # 如果没有异常,认为连接正常 + except Exception: + return False + + # 对于模拟设备,总是返回True + return True + + except Exception as e: + self.logger.debug(f"检查IMU硬件连接时出错: {e}") + return False + def cleanup(self): """ 清理资源 """ try: + # 停止连接监控 + self._cleanup_monitoring() + self.disconnect() # 清理缓冲区 diff --git a/backend/devices/pressure_manager.py b/backend/devices/pressure_manager.py index bc9e0fc7..ecb744e9 100644 --- a/backend/devices/pressure_manager.py +++ b/backend/devices/pressure_manager.py @@ -998,9 +998,46 @@ class PressureManager(BaseDevice): self.logger.error(f"重新加载压力板配置失败: {e}") return False + def check_hardware_connection(self) -> bool: + """ + 检查压力板硬件连接状态 + + Returns: + bool: 硬件连接是否正常 + """ + try: + if not self.device: + return False + + # 对于真实设备,检查DLL和设备句柄状态 + if hasattr(self.device, 'dll') and hasattr(self.device, 'device_handle'): + if not self.device.dll or not self.device.device_handle: + return False + + # 检查设备连接状态 + if not self.device.is_connected: + return False + + # 尝试读取一次数据来验证连接 + try: + test_data = self.device.read_data() + return test_data is not None and 'foot_pressure' in test_data + except Exception: + return False + + # 对于模拟设备,总是返回True + return True + + except Exception as e: + self.logger.debug(f"检查压力板硬件连接时出错: {e}") + return False + def cleanup(self) -> None: """清理资源""" try: + # 停止连接监控 + self._cleanup_monitoring() + self.stop_streaming() self.disconnect() self.logger.info("压力板设备资源清理完成") diff --git a/backend/devices/screen_recorder.py b/backend/devices/screen_recorder.py index e1eaeb8e..d67cf5bc 100644 --- a/backend/devices/screen_recorder.py +++ b/backend/devices/screen_recorder.py @@ -547,7 +547,7 @@ class RecordingManager: if self.current_session_id: result['database_updates'] = { 'session_id': self.current_session_id, - 'status': 'checked' + 'status': 'recorded' } self.logger.info(f'数据库更新信息已准备 - 会话ID: {self.current_session_id}') @@ -647,12 +647,7 @@ class RecordingManager: # 写入视频帧 if frame is not None: video_writer.write(frame) - frame_count += 1 - - # # 每100帧记录一次进度 - # if frame_count % 100 == 0: - # elapsed_recording_time = current_time - recording_start_time - # self.logger.debug(f'{recording_type}录制进度: {frame_count}帧, 已录制{elapsed_recording_time:.1f}秒, 目标帧率{target_fps}fps') + frame_count += 1 else: self.logger.warning(f'{recording_type}获取帧失败,跳过此帧') @@ -664,17 +659,8 @@ class RecordingManager: # 计算录制统计信息 if self.global_recording_start_time: - total_recording_time = time.time() - self.global_recording_start_time - actual_fps = frame_count / total_recording_time if total_recording_time > 0 else 0 - expected_frames = int(total_recording_time * target_fps) - - self.logger.info(f'{recording_type}录制线程结束统计:') - self.logger.info(f' 实际录制帧数: {frame_count}帧') - self.logger.info(f' 预期录制帧数: {expected_frames}帧') - self.logger.info(f' 目标帧率: {target_fps}fps') - self.logger.info(f' 实际平均帧率: {actual_fps:.2f}fps') - self.logger.info(f' 录制时长: {total_recording_time:.3f}秒') - + total_recording_time = time.time() - self.global_recording_start_time + expected_frames = int(total_recording_time * target_fps) if abs(frame_count - expected_frames) > target_fps * 0.1: # 如果帧数差异超过0.1秒的帧数 self.logger.warning(f'{recording_type}帧数异常: 实际{frame_count}帧 vs 预期{expected_frames}帧,差异{frame_count - expected_frames}帧') else: diff --git a/backend/main.py b/backend/main.py index d8f7a669..cf65c454 100644 --- a/backend/main.py +++ b/backend/main.py @@ -977,47 +977,15 @@ class AppServer: data = flask_request.get_json() patient_id = data.get('patient_id') - creator_id = data.get('creator_id') - screen_location = data.get('screen_location') # [0,0,1920,1080] - camera_location = data.get('camera_location') # [0,0,640,480] - femtobolt_location = data.get('femtobolt_location') # [0,0,640,480] + creator_id = data.get('creator_id') if not patient_id or not creator_id: return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400 # 调用create_detection_session方法,settings传空字典 - session_id = self.db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id) - - # 开始同步录制 - recording_response = None - try: - recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera_location,femtobolt_location) - - # 处理录制管理器返回的数据库更新信息 - if recording_response and recording_response.get('success') and 'database_updates' in recording_response: - db_updates = recording_response['database_updates'] - try: - # 更新会话状态 - if not self.db_manager.update_session_status(db_updates['session_id'], db_updates['status']): - self.logger.error(f'更新会话状态失败 - 会话ID: {db_updates["session_id"]}, 状态: {db_updates["status"]}') - - # 更新视频文件路径 - video_paths = db_updates['video_paths'] - self.db_manager.update_session_normal_video_path(db_updates['session_id'], video_paths['normal_video_path']) - self.db_manager.update_session_screen_video_path(db_updates['session_id'], video_paths['screen_video_path']) - self.db_manager.update_session_femtobolt_video_path(db_updates['session_id'], video_paths['femtobolt_video_path']) - - self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}') - except Exception as db_error: - self.logger.error(f'处理数据库更新失败: {db_error}') - - except Exception as rec_e: - self.logger.error(f'开始同步录制失败: {rec_e}') - - start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response}) + session_id = self.db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id) + return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time}) except Exception as e: self.logger.error(f'开始检测失败: {e}') return jsonify({'success': False, 'error': str(e)}), 500 @@ -1049,6 +1017,89 @@ class AppServer: except Exception as duration_error: self.logger.error(f'更新会话持续时间失败: {duration_error}') + success = self.db_manager.update_session_status(session_id, 'completed') + if success: + self.logger.info(f'检测会话已停止 - 会话ID: {session_id}') + return jsonify({ + 'success': True, + 'message': '检测已停止' + }) + else: + self.logger.error('停止检测失败,更新会话状态失败') + return jsonify({ + 'success': False, + 'error': '停止检测失败' + }), 500 + + except Exception as e: + self.logger.error(f'停止检测失败: {e}', exc_info=True) + return jsonify({'success': False, 'error': str(e)}), 500 + + @self.app.route('/api/detection//start_record', methods=['POST']) + def start_record(session_id): + """开始视频录制""" + try: + if not self.db_manager or not self.device_coordinator: + return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500 + + data = flask_request.get_json() + patient_id = data.get('patient_id') + screen_location = data.get('screen_location') # [0,0,1920,1080] + camera_location = data.get('camera_location') # [0,0,640,480] + femtobolt_location = data.get('femtobolt_location') # [0,0,640,480] + + + if not patient_id: + return jsonify({'success': False, 'error': '缺少患者ID'}), 400 + + # 开始视频录制 + recording_response = None + try: + recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera_location,femtobolt_location) + + # 处理录制管理器返回的数据库更新信息 + if recording_response and recording_response.get('success') and 'database_updates' in recording_response: + db_updates = recording_response['database_updates'] + try: + # 更新会话状态 + if not self.db_manager.update_session_status(db_updates['session_id'], db_updates['status']): + self.logger.error(f'更新会话状态失败 - 会话ID: {db_updates["session_id"]}, 状态: {db_updates["status"]}') + + # 更新视频文件路径 + video_paths = db_updates['video_paths'] + self.db_manager.update_session_normal_video_path(db_updates['session_id'], video_paths['normal_video_path']) + self.db_manager.update_session_screen_video_path(db_updates['session_id'], video_paths['screen_video_path']) + self.db_manager.update_session_femtobolt_video_path(db_updates['session_id'], video_paths['femtobolt_video_path']) + + self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}') + except Exception as db_error: + self.logger.error(f'处理数据库更新失败: {db_error}') + + except Exception as rec_e: + self.logger.error(f'开始同步录制失败: {rec_e}') + + start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response}) + except Exception as e: + self.logger.error(f'开始录制失败: {e}') + return jsonify({'success': False, 'error': str(e)}), 500 + + @self.app.route('/api/detection//stop_record', methods=['POST']) + def stop_record(session_id): + """停止视频录制""" + try: + if not self.db_manager or not self.device_coordinator: + self.logger.error('数据库管理器或设备管理器未初始化') + return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500 + + if not session_id: + self.logger.error('缺少会话ID') + return jsonify({ + 'success': False, + 'error': '缺少会话ID' + }), 400 + # 停止同步录制,传递视频数据 try: restrt = self.recording_manager.stop_recording(session_id) @@ -1066,12 +1117,12 @@ class AppServer: success = False else: # 如果录制管理器没有返回数据库更新信息,则手动更新 - success = self.db_manager.update_session_status(session_id, 'completed') + success = self.db_manager.update_session_status(session_id, 'recorded') except Exception as rec_e: self.logger.error(f'停止同步录制失败: {rec_e}', exc_info=True) # 即使录制停止失败,也尝试更新数据库状态 - success = self.db_manager.update_session_status(session_id, 'completed') + success = self.db_manager.update_session_status(session_id, 'recorded') raise if success: @@ -1090,7 +1141,7 @@ class AppServer: except Exception as e: self.logger.error(f'停止检测失败: {e}', exc_info=True) return jsonify({'success': False, 'error': str(e)}), 500 - + @self.app.route('/api/detection//status', methods=['GET']) def get_detection_status(session_id): """获取检测状态""" diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index 7a12bc8c..b7db3264 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -11,6 +11,7 @@ 实时检测 +
检测中
@@ -41,12 +42,12 @@
- + - +
@@ -61,10 +62,11 @@ 身体姿态 -
+
{{ femtoboltStatus }}
-
+
深度相机视频流 @@ -75,7 +77,7 @@ align-content: space-between; "> -
+
@@ -108,66 +110,54 @@
-
+
旋转角
-
{{ headlist.rotation }}
-
-
左:{{ +
{{ headlist.rotation }}°
+
+
左最大:{{ headPoseMaxValues.rotationLeftMax.toFixed(1) }}°
-
右:{{ - headPoseMaxValues.rotationRightMax.toFixed(1) }}°
+ +
+
+
左最大:{{ + headPoseMaxValues.rotationLeftMax.toFixed(1) }}°
+
倾斜角
-
{{ headlist.tilt }}
-
-
左:{{ +
{{ headlist.tilt }}°
+
+
左最大:{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°
-
右:{{ + +
+
+
右最大:{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°
俯仰角
-
{{ headlist.pitch }}
-
-
俯:{{ +
{{ headlist.pitch }}°
+
+
俯最大:{{ + headPoseMaxValues.pitchDownMax.toFixed(1) }}°
+
+
+
俯最大:{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°
-
仰:{{ - headPoseMaxValues.pitchUpMax.toFixed(1) }}°
-
-
-
历史数据
-
- -
- - - - - - - - - - - - - - - -
+
-
+
@@ -180,68 +170,75 @@ pressureStatus }}
-
-
-
- 左前足 - {{ footPressure.left_front - }}% -
-
- 左后足 - {{ footPressure.left_rear }}% -
-
-
-
-
- 左足 +
+
+
+
+ 左前足 + {{ footPressure.left_front + }}%
-
- 右足 +
+ 左后足 + {{ footPressure.left_rear }}%
-
- -
-
-
-
-
-
左足总压力
-
{{ footPressure.left_total - }}%
+
+
+
+ 左足 +
+
+ 右足 +
-
-
右足总压力
-
{{ footPressure.right_total - }}%
+
+ +
+
+
+
+
+
左足总压力
+
{{ footPressure.left_total + }}%
+
+
+
右足总压力
+
{{ footPressure.right_total + }}%
+
-
-
-
- 右前足 - {{ footPressure.right_front - }}% -
-
- 右后足 - {{ footPressure.right_rear - }}% +
+
+ 右前足 + {{ footPressure.right_front + }}% +
+
+ 右后足 + {{ footPressure.right_rear + }}% +
+
-
-
+
@@ -322,7 +319,7 @@
-
+
@@ -334,7 +331,7 @@
{{ cameraStatus }}
-
+
@@ -586,19 +583,13 @@ const screenshotLoading = ref(false) const dataCollectionLoading = ref(false) const isRecording = ref(false) const cameraDialogVisible =ref(false) // 设置相机参数弹框 - - const contenGridRef =ref(null) // 实时检查整体box - const wholeBodyRef = ref(null) // 身体姿态ref const videoImgRef =ref(null) // 视频流图片ref - // 录像相关变量 let mediaRecorder = null let recordedChunks = [] let recordingStream = null - - // 患者信息(从页面获取或通过API获取) const patientInfo = ref({ id: '', @@ -693,11 +684,7 @@ const calculatedAge = ref(null) //修改 // 模拟历史数据 -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°' } -]) +const historyData = ref([]) const chartoption = ref({ backgroundColor: '#242424', grid: { top: 0, right: 0, bottom: 0, left: 0 }, @@ -825,7 +812,7 @@ const startTimer = () => { if (seconds.value >= 60) { console.log('⏰ 检测时长超过10分钟,自动停止检测'); ElMessage.warning('检测时长已达到10分钟,自动停止检测'); - stopDetection(); + stopRecord() return; } @@ -1683,28 +1670,23 @@ async function handleStartStop() { } if (isStart.value) { - // 停止检测 - await stopDetection() + // 停止录制视频 + await stopRecord() } else { patientInfo.value.sessionId = null - // 开始检测 - await startDetection() + // 开始录制视频 + await startRecord() } } // 开始检测 async function startDetection() { try { console.log('🚀 正在开始检测...') - isRecording.value = true startTimer() // 验证患者信息 if (!patientInfo.value || !patientInfo.value.id) { throw new Error('缺少患者信息,无法开始检测') } - let screen_location = contenGridRef.value.getBoundingClientRect() - let femtobolt_location = wholeBodyRef.value.getBoundingClientRect() - let camera_location = videoImgRef.value.getBoundingClientRect() - let titile_height = 24 // 调用后端API开始检测 const response = await fetch(`${BACKEND_URL}/api/detection/start`, { method: 'POST', @@ -1715,10 +1697,6 @@ async function startDetection() { patient_id: patientInfo.value.id, // 可以添加其他检测参数 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-titile_height)], - camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height-titile_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-titile_height)], - }) }) if (!response.ok) { @@ -1729,14 +1707,8 @@ async function startDetection() { if (result.success) { console.log('✅ 检测开始成功') - // 保存会话ID和检测开始时间 patientInfo.value.sessionId = result.session_id - patientInfo.value.detectionStartTime = Date.now() - console.log('✅ 检测会话创建成功,会话ID:', patientInfo.value.sessionId) - - isStart.value = true - ElMessage.success('检测已开始') } else { throw new Error(result.message || '开始检测失败') } @@ -1751,14 +1723,8 @@ async function startDetection() { // 停止检测 async function stopDetection() { try { - console.log('🛑 停止检测,会话ID:', patientInfo.value.sessionId) - 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`, { method: 'POST', @@ -1773,8 +1739,6 @@ async function stopDetection() { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } isRecording.value = false - isStart.value = false - } catch (error) { console.error('❌ 停止检测失败:', error) ElMessage.error(`停止检测失败: ${error.message}`) @@ -1979,15 +1943,11 @@ const getDevicesInit = async () => { } onMounted(() => { - - console.log(wholeBodyRef.value.getBoundingClientRect()) - console.log(videoImgRef.value.getBoundingClientRect()) // 加载患者信息 loadPatientInfo() - // 页面加载时自动连接WebSocket connectWebSocket() - + startDetection() // 监听页面关闭或刷新事件 window.addEventListener('beforeunload', handleBeforeUnload) if (authStore.currentUser) { @@ -2005,11 +1965,12 @@ onUnmounted(() => { if (isRecording.value) { stopRecording() } - // 停止检测(如果正在检测) - if (isStart.value) { - stopDetection() + if(isStart.value == true){ + + stopRecord() } + stopDetection() // 页面关闭时断开WebSocket连接 disconnectWebSocket() @@ -2042,6 +2003,90 @@ onUnmounted(() => { // 移除页面关闭事件监听器 window.removeEventListener('beforeunload', handleBeforeUnload) }) + +const startRecord = async () => { // 开始录屏 + try { + console.log('🚀 正在开始录屏...') + + // 验证患者信息 + if (!patientInfo.value || !patientInfo.value.sessionId) { + throw new Error('缺少患者信息,无法开始录屏') + } + isRecording.value = true + let screen_location = contenGridRef.value.getBoundingClientRect() + let femtobolt_location = wholeBodyRef.value.getBoundingClientRect() + let camera_location = videoImgRef.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: patientInfo.value.sessionId, + // 可以添加其他录屏参数 + 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-titile_height)], + camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height-titile_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-titile_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) + isStart.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 + isStart.value = false + + } catch (error) { + console.error('❌ 停止检测失败:', error) + ElMessage.error(`停止检测失败: ${error.message}`) + } +} +function routerClick(){ + router.push(`/patient/${patientInfo.value.id}`) +} \ No newline at end of file diff --git a/frontend/src/renderer/src/views/PatientProfile.vue b/frontend/src/renderer/src/views/PatientProfile.vue index a44a592e..0901d5af 100644 --- a/frontend/src/renderer/src/views/PatientProfile.vue +++ b/frontend/src/renderer/src/views/PatientProfile.vue @@ -1174,6 +1174,7 @@ onMounted(() => { .main-content { padding: 10px; display: flex; + flex-direction: column; overflow: auto; width: 100%; } diff --git a/frontend/src/renderer/src/views/Recording.vue b/frontend/src/renderer/src/views/Recording.vue index a28ee8e0..8fa27c9b 100644 --- a/frontend/src/renderer/src/views/Recording.vue +++ b/frontend/src/renderer/src/views/Recording.vue @@ -983,6 +983,7 @@ onUnmounted(() => { .main-content { flex: 1; display: flex; + flex-direction: column; overflow: hidden; }