Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94d52e4237 | |||
| da2dc38a51 | |||
| 79c6b5859b | |||
| 77d35fc4cc | |||
| fbe332a8a6 | |||
| fe79fe828a | |||
| 96ba7c098a |
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"product": "BodyBalanceEvaluation",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"machine_id": "W10-D13710C7BD317C29",
|
|
||||||
"platform": "Windows",
|
|
||||||
"request_time": "2025-11-04T05:35:19.472181+00:00",
|
|
||||||
"hardware_info": {
|
|
||||||
"system": "Windows",
|
|
||||||
"machine": "AMD64",
|
|
||||||
"processor": "Intel64 Family 6 Model 165 Stepping 2, GenuineIntel",
|
|
||||||
"node": "MSI"
|
|
||||||
},
|
|
||||||
"company_name": "北京天宏博科技有限公司",
|
|
||||||
"contact_info": "thb@163.com"
|
|
||||||
}
|
|
||||||
@ -17,7 +17,7 @@ max_backups = 7
|
|||||||
[FILEPATH]
|
[FILEPATH]
|
||||||
path = D:/BodyCheck/file/
|
path = D:/BodyCheck/file/
|
||||||
|
|
||||||
[CAMERA]
|
[CAMERA1]
|
||||||
enabled = True
|
enabled = True
|
||||||
device_index = 0
|
device_index = 0
|
||||||
width = 1280
|
width = 1280
|
||||||
@ -27,6 +27,16 @@ buffer_size = 1
|
|||||||
fourcc = MJPG
|
fourcc = MJPG
|
||||||
backend = directshow
|
backend = directshow
|
||||||
|
|
||||||
|
[CAMERA2]
|
||||||
|
enabled = True
|
||||||
|
device_index = 1
|
||||||
|
width = 1280
|
||||||
|
height = 720
|
||||||
|
fps = 30
|
||||||
|
buffer_size = 1
|
||||||
|
fourcc = MJPG
|
||||||
|
backend = directshow
|
||||||
|
|
||||||
[FEMTOBOLT]
|
[FEMTOBOLT]
|
||||||
enabled = True
|
enabled = True
|
||||||
algorithm_type = plt
|
algorithm_type = plt
|
||||||
|
|||||||
@ -26,34 +26,49 @@ except ImportError:
|
|||||||
class CameraManager(BaseDevice):
|
class CameraManager(BaseDevice):
|
||||||
"""普通相机管理器"""
|
"""普通相机管理器"""
|
||||||
|
|
||||||
def __init__(self, socketio, config_manager: Optional[ConfigManager] = None):
|
def __init__(self, socketio, config_manager: Optional[ConfigManager] = None,
|
||||||
|
device_name: str = "camera1",
|
||||||
|
instance_config: Optional[Dict[str, Any]] = None):
|
||||||
"""
|
"""
|
||||||
初始化相机管理器
|
初始化相机管理器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
socketio: SocketIO实例
|
socketio: SocketIO实例
|
||||||
config_manager: 配置管理器实例
|
config_manager: 配置管理器实例
|
||||||
|
device_name: 设备名称(仅支持 'camera1' | 'camera2')
|
||||||
|
instance_config: 覆盖默认配置的实例级配置(如 device_index、分辨率、fps 等)
|
||||||
"""
|
"""
|
||||||
# 配置管理
|
# 配置管理
|
||||||
self.config_manager = config_manager or ConfigManager()
|
self.config_manager = config_manager or ConfigManager()
|
||||||
config = self.config_manager.get_device_config('camera')
|
# 校验设备名,仅允许 camera1/camera2
|
||||||
|
if device_name not in ('camera1', 'camera2'):
|
||||||
|
raise ValueError(f"不支持的设备名: {device_name},仅支持 'camera1'/'camera2'")
|
||||||
|
# 根据设备名选择配置源:'camera1' 使用 [CAMERA1];'camera2' 使用 [CAMERA2]
|
||||||
|
base_key = 'camera1' if device_name == 'camera1' else 'camera2'
|
||||||
|
base_config = self.config_manager.get_device_config(base_key)
|
||||||
|
# 合并实例覆盖配置
|
||||||
|
if instance_config:
|
||||||
|
try:
|
||||||
|
base_config = {**base_config, **instance_config}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
super().__init__("camera", config)
|
super().__init__(device_name, base_config)
|
||||||
|
|
||||||
# 保存socketio实例
|
# 保存socketio实例
|
||||||
self._socketio = socketio
|
self._socketio = socketio
|
||||||
|
|
||||||
# 相机相关属性
|
# 相机相关属性
|
||||||
self.cap = None
|
self.cap = None
|
||||||
self.device_index = config.get('device_index', 0)
|
self.device_index = base_config.get('device_index', 0)
|
||||||
self.width = config.get('width', 1280)
|
self.width = base_config.get('width', 1280)
|
||||||
self.height = config.get('height', 720)
|
self.height = base_config.get('height', 720)
|
||||||
self.fps = config.get('fps', 30)
|
self.fps = base_config.get('fps', 30)
|
||||||
self.buffer_size = config.get('buffer_size', 1)
|
self.buffer_size = base_config.get('buffer_size', 1)
|
||||||
self.fourcc = config.get('fourcc', 'MJPG')
|
self.fourcc = base_config.get('fourcc', 'MJPG')
|
||||||
|
|
||||||
# OpenCV后端配置 (DirectShow性能最佳)
|
# OpenCV后端配置 (DirectShow性能最佳)
|
||||||
backend_name = config.get('backend', 'directshow').lower()
|
backend_name = base_config.get('backend', 'directshow').lower()
|
||||||
self.backend_map = {
|
self.backend_map = {
|
||||||
'directshow': cv2.CAP_DSHOW,
|
'directshow': cv2.CAP_DSHOW,
|
||||||
'dshow': cv2.CAP_DSHOW,
|
'dshow': cv2.CAP_DSHOW,
|
||||||
@ -64,12 +79,12 @@ class CameraManager(BaseDevice):
|
|||||||
self.backend_name = backend_name
|
self.backend_name = backend_name
|
||||||
|
|
||||||
# 额外可调的降采样宽度(不改变外部配置语义,仅内部优化传输)
|
# 额外可调的降采样宽度(不改变外部配置语义,仅内部优化传输)
|
||||||
self._tx_max_width = int(config.get('tx_max_width', 1920))
|
self._tx_max_width = int(base_config.get('tx_max_width', 1920))
|
||||||
|
|
||||||
# 流控制
|
# 流控制
|
||||||
self.streaming_thread = None
|
self.streaming_thread = None
|
||||||
# 减小缓存长度,保留最近2帧即可,避免累计占用
|
# 减小缓存长度,保留最近2帧即可,避免累计占用
|
||||||
self.frame_cache = queue.Queue(maxsize=int(config.get('frame_cache_len', 2)))
|
self.frame_cache = queue.Queue(maxsize=int(base_config.get('frame_cache_len', 2)))
|
||||||
self.last_frame = None
|
self.last_frame = None
|
||||||
self.frame_count = 0
|
self.frame_count = 0
|
||||||
self.dropped_frames = 0
|
self.dropped_frames = 0
|
||||||
@ -80,13 +95,14 @@ class CameraManager(BaseDevice):
|
|||||||
self.actual_fps = 0
|
self.actual_fps = 0
|
||||||
|
|
||||||
# 重连与断连检测机制(-1 表示无限重连)
|
# 重连与断连检测机制(-1 表示无限重连)
|
||||||
self.max_reconnect_attempts = int(config.get('max_reconnect_attempts', -1))
|
self.max_reconnect_attempts = int(base_config.get('max_reconnect_attempts', -1))
|
||||||
self.reconnect_delay = float(config.get('reconnect_delay', 2.0))
|
self.reconnect_delay = float(base_config.get('reconnect_delay', 2.0))
|
||||||
self.read_fail_threshold = int(config.get('read_fail_threshold', 30))
|
self.read_fail_threshold = int(base_config.get('read_fail_threshold', 30))
|
||||||
self._last_connected_state = None
|
self._last_connected_state = None
|
||||||
|
|
||||||
# 设备标识和性能统计
|
# 设备标识和性能统计
|
||||||
self.device_id = f"camera_{self.device_index}"
|
# 使用设备名作为ID,便于前端区分
|
||||||
|
self.device_id = device_name
|
||||||
self.performance_stats = {
|
self.performance_stats = {
|
||||||
'frames_processed': 0,
|
'frames_processed': 0,
|
||||||
'actual_fps': 0,
|
'actual_fps': 0,
|
||||||
@ -373,6 +389,9 @@ class CameraManager(BaseDevice):
|
|||||||
|
|
||||||
total_config_time = (time.time() - config_start) * 1000
|
total_config_time = (time.time() - config_start) * 1000
|
||||||
|
|
||||||
|
# 若未进行性能优化,确保变量存在
|
||||||
|
optimization_time = locals().get('optimization_time', 0.0)
|
||||||
|
|
||||||
self.logger.info(f"相机配置完成 - 分辨率: {actual_width}x{actual_height}, FPS: {actual_fps}")
|
self.logger.info(f"相机配置完成 - 分辨率: {actual_width}x{actual_height}, FPS: {actual_fps}")
|
||||||
self.logger.info(f"配置耗时统计 - 缓冲区: {buffer_time:.1f}ms, 优化设置: {optimization_time:.1f}ms, 分辨率: {resolution_time:.1f}ms, 帧率: {fps_time:.1f}ms, 验证: {verification_time:.1f}ms, 总计: {total_config_time:.1f}ms")
|
self.logger.info(f"配置耗时统计 - 缓冲区: {buffer_time:.1f}ms, 优化设置: {optimization_time:.1f}ms, 分辨率: {resolution_time:.1f}ms, 帧率: {fps_time:.1f}ms, 验证: {verification_time:.1f}ms, 总计: {total_config_time:.1f}ms")
|
||||||
self.logger.debug(f"配置详情 - 分辨率设置: {resolution_time:.1f}ms, FPS设置: {fps_time:.1f}ms, 验证: {verification_time:.1f}ms, 总计: {total_config_time:.1f}ms")
|
self.logger.debug(f"配置详情 - 分辨率设置: {resolution_time:.1f}ms, FPS设置: {fps_time:.1f}ms, 验证: {verification_time:.1f}ms, 总计: {total_config_time:.1f}ms")
|
||||||
@ -852,8 +871,9 @@ class CameraManager(BaseDevice):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 获取最新配置
|
# 获取最新配置(按设备名映射,已限制为 camera1/camera2)
|
||||||
config = self.config_manager.get_device_config('camera')
|
key = self.device_name
|
||||||
|
config = self.config_manager.get_device_config(key)
|
||||||
|
|
||||||
# 更新配置属性
|
# 更新配置属性
|
||||||
self.device_index = config.get('device_index', 0)
|
self.device_index = config.get('device_index', 0)
|
||||||
@ -880,8 +900,8 @@ class CameraManager(BaseDevice):
|
|||||||
# 创建新队列
|
# 创建新队列
|
||||||
self.frame_cache = queue.Queue(maxsize=frame_cache_len)
|
self.frame_cache = queue.Queue(maxsize=frame_cache_len)
|
||||||
|
|
||||||
# 更新设备信息
|
# 更新设备信息(设备ID直接使用设备名)
|
||||||
self.device_id = f"camera_{self.device_index}"
|
self.device_id = self.device_name
|
||||||
|
|
||||||
self.logger.info(f"相机配置重新加载成功 - 设备索引: {self.device_index}, 分辨率: {self.width}x{self.height}, FPS: {self.fps}")
|
self.logger.info(f"相机配置重新加载成功 - 设备索引: {self.device_index}, 分辨率: {self.width}x{self.height}, FPS: {self.fps}")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -113,10 +113,10 @@ class DeviceCoordinator:
|
|||||||
# 注册Socket.IO命名空间
|
# 注册Socket.IO命名空间
|
||||||
self._register_namespaces()
|
self._register_namespaces()
|
||||||
|
|
||||||
# 初始化设备
|
# 初始化设备(失败则降级继续)
|
||||||
if not self._initialize_devices():
|
if not self._initialize_devices():
|
||||||
self.logger.warning("设备初始化失败,将以降级模式继续运行")
|
self.logger.warning("设备初始化失败,将以降级模式继续运行")
|
||||||
|
|
||||||
# 启动监控线程
|
# 启动监控线程
|
||||||
self._start_monitor()
|
self._start_monitor()
|
||||||
|
|
||||||
@ -163,10 +163,12 @@ class DeviceCoordinator:
|
|||||||
future = self.executor.submit(self._init_femtobolt)
|
future = self.executor.submit(self._init_femtobolt)
|
||||||
futures.append(('femtobolt', future))
|
futures.append(('femtobolt', future))
|
||||||
|
|
||||||
# 普通相机
|
# 普通相机:初始化两个实例(camera1 与 camera2)
|
||||||
if self.device_configs.get('camera', {}).get('enabled', False):
|
# camera1 使用 [CAMERA1] 配置;camera2 使用 [CAMERA2](若不存在则回退为 device_index+1)
|
||||||
future = self.executor.submit(self._init_camera)
|
if self.device_configs.get('camera1', {}).get('enabled', True):
|
||||||
futures.append(('camera', future))
|
futures.append(('camera1', self.executor.submit(self._init_camera_by_name, 'camera1', 'CAMERA1')))
|
||||||
|
if self.device_configs.get('camera2', {}).get('enabled', True):
|
||||||
|
futures.append(('camera2', self.executor.submit(self._init_camera_by_name, 'camera2', 'CAMERA2')))
|
||||||
|
|
||||||
# IMU传感器
|
# IMU传感器
|
||||||
if self.device_configs.get('imu', {}).get('enabled', False):
|
if self.device_configs.get('imu', {}).get('enabled', False):
|
||||||
@ -205,21 +207,72 @@ class DeviceCoordinator:
|
|||||||
self.logger.error(f"设备初始化失败: {e}")
|
self.logger.error(f"设备初始化失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _init_camera(self) -> bool:
|
def _init_camera_by_name(self, device_name: str, section: str = 'CAMERA1') -> bool:
|
||||||
"""
|
"""
|
||||||
初始化普通相机
|
按名称初始化相机,支持 camera1/camera2 并覆盖配置段
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_name: 设备名称(如 'camera1'、'camera2')
|
||||||
|
section: 配置段名称('CAMERA1' 或 'CAMERA2')
|
||||||
Returns:
|
Returns:
|
||||||
bool: 初始化是否成功
|
bool: 初始化是否成功
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
camera = CameraManager(self.socketio, self.config_manager)
|
# 构造实例覆盖配置:优先读取目标配置段,否则回退到 [CAMERA1]
|
||||||
self.devices['camera'] = camera
|
cfg = {}
|
||||||
if camera.initialize():
|
parser = getattr(self.config_manager, 'config', None)
|
||||||
|
base_cam = self.config_manager.get_device_config('camera1')
|
||||||
|
if parser and parser.has_section(section):
|
||||||
|
# 读取所有相关键
|
||||||
|
def get_opt(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.get(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
def get_int(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.getint(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
def get_bool(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.getboolean(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
enabled = get_bool(section, 'enabled', True)
|
||||||
|
if not enabled:
|
||||||
|
self.logger.info(f"{device_name} 未启用,跳过初始化")
|
||||||
|
return False
|
||||||
|
# 填充覆盖项
|
||||||
|
idx2 = get_int(section, 'device_index', None)
|
||||||
|
if idx2 is not None:
|
||||||
|
cfg['device_index'] = idx2
|
||||||
|
w = get_int(section, 'width', None)
|
||||||
|
h = get_int(section, 'height', None)
|
||||||
|
f = get_int(section, 'fps', None)
|
||||||
|
buf = get_int(section, 'buffer_size', None)
|
||||||
|
fourcc = get_opt(section, 'fourcc', None)
|
||||||
|
backend = get_opt(section, 'backend', None)
|
||||||
|
if w is not None: cfg['width'] = w
|
||||||
|
if h is not None: cfg['height'] = h
|
||||||
|
if f is not None: cfg['fps'] = f
|
||||||
|
if buf is not None: cfg['buffer_size'] = buf
|
||||||
|
if fourcc is not None: cfg['fourcc'] = fourcc
|
||||||
|
if backend is not None: cfg['backend'] = backend
|
||||||
|
else:
|
||||||
|
# section 不存在时:camera2 默认使用 device_index+1
|
||||||
|
if device_name.lower() == 'camera2':
|
||||||
|
cfg['device_index'] = int(base_cam.get('device_index', 0)) + 1
|
||||||
|
else:
|
||||||
|
cfg['device_index'] = int(base_cam.get('device_index', 0))
|
||||||
|
|
||||||
|
camera = CameraManager(self.socketio, self.config_manager, device_name=device_name, instance_config=cfg)
|
||||||
|
self.devices[device_name] = camera
|
||||||
|
if camera.initialize():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"初始化相机失败: {e}")
|
self.logger.error(f"初始化{device_name}失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _init_imu(self) -> bool:
|
def _init_imu(self) -> bool:
|
||||||
@ -441,8 +494,8 @@ class DeviceCoordinator:
|
|||||||
success_count = 0
|
success_count = 0
|
||||||
for device_name, device in self.devices.items():
|
for device_name, device in self.devices.items():
|
||||||
try:
|
try:
|
||||||
# 对深度相机(femtobolt)和普通相机(camera)直接调用初始化和启动推流
|
# 对深度相机(femtobolt)和普通相机(camera1/camera2)直接跳过连接监控
|
||||||
if device_name in ['femtobolt', 'camera',"imu"]:
|
if device_name in ['femtobolt', 'camera1', 'camera2', "imu"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if hasattr(device, '_start_connection_monitor'):
|
if hasattr(device, '_start_connection_monitor'):
|
||||||
@ -475,19 +528,9 @@ class DeviceCoordinator:
|
|||||||
success_count = 0
|
success_count = 0
|
||||||
for device_name, device in self.devices.items():
|
for device_name, device in self.devices.items():
|
||||||
try:
|
try:
|
||||||
# 对深度相机(femtobolt)和普通相机(camera)直接调用停止推流
|
# 对深度相机(femtobolt)和普通相机(camera1/camera2)直接跳过连接监控停止
|
||||||
if device_name in ['femtobolt', 'camera',"imu"]:
|
if device_name in ['femtobolt', 'camera1', 'camera2', "imu"]:
|
||||||
self.logger.info(f"停止{device_name}设备推流")
|
self.logger.info(f"停止{device_name}设备推流")
|
||||||
|
|
||||||
# # 调用设备的cleanup方法清理资源,停止推流
|
|
||||||
# if hasattr(device, 'cleanup'):
|
|
||||||
# if device.cleanup():
|
|
||||||
# success_count += 1
|
|
||||||
# self.logger.info(f"{device_name}设备推流已停止")
|
|
||||||
# else:
|
|
||||||
# self.logger.warning(f"{device_name}设备推流停止失败")
|
|
||||||
# else:
|
|
||||||
# self.logger.warning(f"{device_name}设备不支持推流停止")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if hasattr(device, '_stop_connection_monitor'):
|
if hasattr(device, '_stop_connection_monitor'):
|
||||||
@ -592,13 +635,58 @@ class DeviceCoordinator:
|
|||||||
|
|
||||||
new_device = None
|
new_device = None
|
||||||
try:
|
try:
|
||||||
# 根据设备类型重新创建实例
|
# 根据设备类型重新创建实例(仅支持 camera1/camera2)
|
||||||
if device_name == 'camera':
|
if device_name in ('camera1', 'camera2'):
|
||||||
try:
|
try:
|
||||||
from .camera_manager import CameraManager
|
from .camera_manager import CameraManager
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from camera_manager import CameraManager
|
from camera_manager import CameraManager
|
||||||
new_device = CameraManager(self.socketio, self.config_manager)
|
# 为 camera1/camera2 构造实例配置
|
||||||
|
section = 'CAMERA1' if device_name == 'camera1' else 'CAMERA2'
|
||||||
|
cfg = {}
|
||||||
|
parser = getattr(self.config_manager, 'config', None)
|
||||||
|
base_cam = self.config_manager.get_device_config('camera1')
|
||||||
|
if parser and parser.has_section(section):
|
||||||
|
def get_opt(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.get(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
def get_int(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.getint(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
def get_bool(sec, key, fallback=None):
|
||||||
|
try:
|
||||||
|
return parser.getboolean(sec, key)
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
enabled = get_bool(section, 'enabled', True)
|
||||||
|
if not enabled:
|
||||||
|
raise Exception(f"{device_name} 未启用")
|
||||||
|
idx2 = get_int(section, 'device_index', None)
|
||||||
|
if idx2 is not None:
|
||||||
|
cfg['device_index'] = idx2
|
||||||
|
w = get_int(section, 'width', None)
|
||||||
|
h = get_int(section, 'height', None)
|
||||||
|
f = get_int(section, 'fps', None)
|
||||||
|
buf = get_int(section, 'buffer_size', None)
|
||||||
|
fourcc = get_opt(section, 'fourcc', None)
|
||||||
|
backend = get_opt(section, 'backend', None)
|
||||||
|
if w is not None: cfg['width'] = w
|
||||||
|
if h is not None: cfg['height'] = h
|
||||||
|
if f is not None: cfg['fps'] = f
|
||||||
|
if buf is not None: cfg['buffer_size'] = buf
|
||||||
|
if fourcc is not None: cfg['fourcc'] = fourcc
|
||||||
|
if backend is not None: cfg['backend'] = backend
|
||||||
|
else:
|
||||||
|
# section 不存在时:camera2 默认使用 [CAMERA1] 的 device_index + 1
|
||||||
|
if device_name == 'camera2':
|
||||||
|
cfg['device_index'] = int(base_cam.get('device_index', 0)) + 1
|
||||||
|
else:
|
||||||
|
cfg['device_index'] = int(base_cam.get('device_index', 0))
|
||||||
|
new_device = CameraManager(self.socketio, self.config_manager, device_name=device_name, instance_config=cfg)
|
||||||
elif device_name == 'imu':
|
elif device_name == 'imu':
|
||||||
try:
|
try:
|
||||||
from .imu_manager import IMUManager
|
from .imu_manager import IMUManager
|
||||||
@ -812,7 +900,12 @@ class DeviceCoordinator:
|
|||||||
self.executor.shutdown(wait=True)
|
self.executor.shutdown(wait=True)
|
||||||
|
|
||||||
# 清理Socket管理器
|
# 清理Socket管理器
|
||||||
self.socket_manager.cleanup()
|
try:
|
||||||
|
self.socket_manager.cleanup_all()
|
||||||
|
except Exception:
|
||||||
|
# 兼容旧接口
|
||||||
|
if hasattr(self.socket_manager, 'cleanup'):
|
||||||
|
self.socket_manager.cleanup()
|
||||||
|
|
||||||
self.logger.info("设备协调器已关闭")
|
self.logger.info("设备协调器已关闭")
|
||||||
|
|
||||||
@ -837,7 +930,7 @@ def test_restart_device(device_name=None):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
device_name (str, optional): 指定要测试的设备名称。如果为None,则自动选择第一个可用设备。
|
device_name (str, optional): 指定要测试的设备名称。如果为None,则自动选择第一个可用设备。
|
||||||
可选值: 'camera', 'imu', 'pressure', 'femtobolt'
|
可选值: 'camera1', 'camera2', 'imu', 'pressure', 'femtobolt'
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
@ -847,22 +940,13 @@ def test_restart_device(device_name=None):
|
|||||||
print("设备协调器重启功能测试")
|
print("设备协调器重启功能测试")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
# 创建模拟的SocketIO和配置管理器
|
# 创建模拟的SocketIO(使用真实配置文件)
|
||||||
mock_socketio = Mock()
|
mock_socketio = Mock()
|
||||||
mock_config_manager = Mock()
|
|
||||||
|
|
||||||
# 模拟配置数据
|
|
||||||
mock_config_manager.get_device_config.return_value = {
|
|
||||||
'camera': {'enabled': True, 'device_id': 0, 'fps': 30},
|
|
||||||
'imu': {'enabled': True, 'device_type': 'mock'},
|
|
||||||
'pressure': {'enabled': True, 'device_type': 'mock'},
|
|
||||||
'femtobolt': {'enabled': False}
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 创建设备协调器实例
|
# 创建设备协调器实例
|
||||||
print("1. 创建设备协调器...")
|
print("1. 创建设备协调器...")
|
||||||
coordinator = DeviceCoordinator(mock_socketio, mock_config_manager)
|
coordinator = DeviceCoordinator(mock_socketio)
|
||||||
|
|
||||||
# 初始化设备协调器
|
# 初始化设备协调器
|
||||||
print("2. 初始化设备协调器...")
|
print("2. 初始化设备协调器...")
|
||||||
@ -882,13 +966,14 @@ def test_restart_device(device_name=None):
|
|||||||
print("❌ 没有可用的设备进行测试")
|
print("❌ 没有可用的设备进行测试")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 根据参数选择测试设备
|
# 根据参数选择测试设备(仅支持 camera1/camera2/imu/pressure/femtobolt)
|
||||||
if device_name:
|
if device_name:
|
||||||
if device_name in available_devices:
|
allowed = {'camera1', 'camera2', 'imu', 'pressure', 'femtobolt'}
|
||||||
|
if device_name in available_devices and device_name in allowed:
|
||||||
test_device = device_name
|
test_device = device_name
|
||||||
print(f"3. 使用指定的测试设备: {test_device}")
|
print(f"3. 使用指定的测试设备: {test_device}")
|
||||||
else:
|
else:
|
||||||
print(f"❌ 指定的设备 '{device_name}' 不存在")
|
print(f"❌ 指定的设备 '{device_name}' 不存在或不受支持")
|
||||||
print(f" 可用设备: {available_devices}")
|
print(f" 可用设备: {available_devices}")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -992,7 +1077,7 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 执行测试
|
# 执行测试
|
||||||
# 可选值: 'camera', 'imu', 'pressure', 'femtobolt'
|
# 可选值: 'camera1', 'camera2', 'imu', 'pressure', 'femtobolt'
|
||||||
success = test_restart_device('pressure')
|
success = test_restart_device('pressure')
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@ -305,10 +305,10 @@ class BleIMUDevice:
|
|||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
logger.info(f"扫描并连接蓝牙IMU: {self.mac_address} ...")
|
# logger.info(f"扫描并连接蓝牙IMU: {self.mac_address} ...")
|
||||||
device = await BleakScanner.find_device_by_address(self.mac_address, cb=dict(use_bdaddr=False))
|
device = await BleakScanner.find_device_by_address(self.mac_address, cb=dict(use_bdaddr=False))
|
||||||
if device is None:
|
if device is None:
|
||||||
logger.warning(f"未找到设备: {self.mac_address}")
|
# logger.warning(f"未找到设备: {self.mac_address}")
|
||||||
await asyncio.sleep(2.0)
|
await asyncio.sleep(2.0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@ -242,7 +242,7 @@ class RecordingManager:
|
|||||||
|
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
def start_recording(self, session_id: str, patient_id: str, screen_location: List[int], camera_location: List[int], femtobolt_location: List[int], recording_types: List[str] = None) -> Dict[str, Any]:
|
def start_recording(self, session_id: str, patient_id: str, screen_location: List[int], camera1_location: List[int], camera2_location: List[int], femtobolt_location: List[int], recording_types: List[str] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
启动同步录制
|
启动同步录制
|
||||||
|
|
||||||
@ -250,9 +250,10 @@ class RecordingManager:
|
|||||||
session_id: 检测会话ID
|
session_id: 检测会话ID
|
||||||
patient_id: 患者ID
|
patient_id: 患者ID
|
||||||
screen_location: 屏幕录制区域 [x, y, w, h]
|
screen_location: 屏幕录制区域 [x, y, w, h]
|
||||||
camera_location: 相机录制区域 [x, y, w, h]
|
camera1_location: 相机1录制区域 [x, y, w, h]
|
||||||
|
camera2_location: 相机2录制区域 [x, y, w, h]
|
||||||
femtobolt_location: FemtoBolt录制区域 [x, y, w, h]
|
femtobolt_location: FemtoBolt录制区域 [x, y, w, h]
|
||||||
recording_types: 录制类型列表 ['screen', 'feet', 'femtobolt'],默认全部录制
|
recording_types: 录制类型列表 ['screen', 'camera1', 'camera2', 'feet', 'femtobolt'],默认全部录制
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict: 录制启动状态和信息
|
Dict: 录制启动状态和信息
|
||||||
@ -263,6 +264,8 @@ class RecordingManager:
|
|||||||
'patient_id': patient_id,
|
'patient_id': patient_id,
|
||||||
'recording_start_time': None,
|
'recording_start_time': None,
|
||||||
'video_paths': {
|
'video_paths': {
|
||||||
|
'camera1_video': None,
|
||||||
|
'camera2_video': None,
|
||||||
'feet_video': None,
|
'feet_video': None,
|
||||||
'screen_video': None,
|
'screen_video': None,
|
||||||
'femtobolt_video': None
|
'femtobolt_video': None
|
||||||
@ -277,7 +280,7 @@ class RecordingManager:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
# 设置默认录制类型
|
# 设置默认录制类型
|
||||||
recording_types = ['screen', 'feet']
|
recording_types = ['screen', 'camera1' ,'camera2']
|
||||||
|
|
||||||
# 验证录制区域参数(仅对启用的录制类型进行验证)
|
# 验证录制区域参数(仅对启用的录制类型进行验证)
|
||||||
if 'screen' in recording_types:
|
if 'screen' in recording_types:
|
||||||
@ -286,10 +289,16 @@ class RecordingManager:
|
|||||||
result['message'] = '屏幕录制区域参数无效或缺失,必须是包含4个元素的数组[x, y, w, h]'
|
result['message'] = '屏幕录制区域参数无效或缺失,必须是包含4个元素的数组[x, y, w, h]'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if 'feet' in recording_types:
|
if 'camera1' in recording_types:
|
||||||
if not camera_location or not isinstance(camera_location, list) or len(camera_location) != 4:
|
if not camera1_location or not isinstance(camera1_location, list) or len(camera1_location) != 4:
|
||||||
result['success'] = False
|
result['success'] = False
|
||||||
result['message'] = '相机录制区域参数无效或缺失,必须是包含4个元素的数组[x, y, w, h]'
|
result['message'] = '相机1录制区域参数无效或缺失,必须是包含4个元素的数组[x, y, w, h]'
|
||||||
|
return result
|
||||||
|
|
||||||
|
if 'camera2' in recording_types:
|
||||||
|
if not camera2_location or not isinstance(camera2_location, list) or len(camera2_location) != 4:
|
||||||
|
result['success'] = False
|
||||||
|
result['message'] = '相机2录制区域参数无效或缺失,必须是包含4个元素的数组[x, y, w, h]'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if 'femtobolt' in recording_types:
|
if 'femtobolt' in recording_types:
|
||||||
@ -303,19 +312,22 @@ class RecordingManager:
|
|||||||
# self.logger.info(f'检测sessionID................: {self.current_session_id}')
|
# self.logger.info(f'检测sessionID................: {self.current_session_id}')
|
||||||
self.current_patient_id = patient_id
|
self.current_patient_id = patient_id
|
||||||
self.screen_region = tuple(screen_location) # [x, y, w, h] -> (x, y, w, h)
|
self.screen_region = tuple(screen_location) # [x, y, w, h] -> (x, y, w, h)
|
||||||
self.camera_region = tuple(camera_location) # [x, y, w, h] -> (x, y, w, h)
|
self.camera1_region = tuple(camera1_location) # [x, y, w, h] -> (x, y, w, h)
|
||||||
|
self.camera2_region = tuple(camera2_location) # [x, y, w, h] -> (x, y, w, h)
|
||||||
self.femtobolt_region = tuple(femtobolt_location) # [x, y, w, h] -> (x, y, w, h)
|
self.femtobolt_region = tuple(femtobolt_location) # [x, y, w, h] -> (x, y, w, h)
|
||||||
|
|
||||||
# 根据录制区域大小设置自适应帧率
|
# 根据录制区域大小设置自适应帧率
|
||||||
if 'screen' in recording_types:
|
if 'screen' in recording_types:
|
||||||
self._set_adaptive_fps_by_region('screen', self.screen_region)
|
self._set_adaptive_fps_by_region('screen', self.screen_region)
|
||||||
if 'feet' in recording_types:
|
if 'camera1' in recording_types:
|
||||||
self._set_adaptive_fps_by_region('camera', self.camera_region)
|
self._set_adaptive_fps_by_region('camera1', self.camera1_region)
|
||||||
|
if 'camera2' in recording_types:
|
||||||
|
self._set_adaptive_fps_by_region('camera2', self.camera2_region)
|
||||||
if 'femtobolt' in recording_types:
|
if 'femtobolt' in recording_types:
|
||||||
self._set_adaptive_fps_by_region('femtobolt', self.femtobolt_region)
|
self._set_adaptive_fps_by_region('femtobolt', self.femtobolt_region)
|
||||||
|
|
||||||
# 设置录制同步
|
# 设置录制同步
|
||||||
active_recording_count = len([t for t in recording_types if t in ['screen', 'feet', 'femtobolt']])
|
active_recording_count = len([t for t in recording_types if t in ['screen', 'camera1', 'camera2', 'femtobolt']])
|
||||||
self.recording_sync_barrier = threading.Barrier(active_recording_count)
|
self.recording_sync_barrier = threading.Barrier(active_recording_count)
|
||||||
self.recording_start_sync.clear()
|
self.recording_start_sync.clear()
|
||||||
self.global_recording_start_time = None
|
self.global_recording_start_time = None
|
||||||
@ -340,7 +352,8 @@ class RecordingManager:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
feet_video_path = os.path.join(base_path, 'feet.mp4')
|
camera1_video_path = os.path.join(base_path, 'camera1.mp4')
|
||||||
|
camera2_video_path = os.path.join(base_path, 'camera2.mp4')
|
||||||
screen_video_path = os.path.join(base_path, 'screen.mp4')
|
screen_video_path = os.path.join(base_path, 'screen.mp4')
|
||||||
femtobolt_video_path = os.path.join(base_path, 'femtobolt.mp4')
|
femtobolt_video_path = os.path.join(base_path, 'femtobolt.mp4')
|
||||||
|
|
||||||
@ -350,7 +363,8 @@ class RecordingManager:
|
|||||||
'session_id': session_id,
|
'session_id': session_id,
|
||||||
'status': 'recording',
|
'status': 'recording',
|
||||||
'video_paths': {
|
'video_paths': {
|
||||||
'normal_video_path': os.path.join(db_base_path, 'feet.mp4'),
|
'camera1_video_path': os.path.join(db_base_path, 'camera1.mp4'),
|
||||||
|
'camera2_video_path': os.path.join(db_base_path, 'camera2.mp4'),
|
||||||
'screen_video_path': os.path.join(db_base_path, 'screen.mp4'),
|
'screen_video_path': os.path.join(db_base_path, 'screen.mp4'),
|
||||||
'femtobolt_video_path': os.path.join(db_base_path, 'femtobolt.mp4')
|
'femtobolt_video_path': os.path.join(db_base_path, 'femtobolt.mp4')
|
||||||
}
|
}
|
||||||
@ -373,7 +387,20 @@ class RecordingManager:
|
|||||||
# 根据录制类型选择性地初始化视频写入器,使用各自的自适应帧率
|
# 根据录制类型选择性地初始化视频写入器,使用各自的自适应帧率
|
||||||
self.screen_video_writer = None
|
self.screen_video_writer = None
|
||||||
self.femtobolt_video_writer = None
|
self.femtobolt_video_writer = None
|
||||||
self.feet_video_writer = None
|
self.camera1_video_writer = None
|
||||||
|
self.camera2_video_writer = None
|
||||||
|
|
||||||
|
if 'camera1' in recording_types:
|
||||||
|
self.camera1_video_writer = cv2.VideoWriter(
|
||||||
|
camera1_video_path, fourcc, self.camera1_current_fps, (self.camera1_region[2], self.camera1_region[3])
|
||||||
|
)
|
||||||
|
self.logger.info(f'相机1视频写入器使用帧率: {self.camera1_current_fps}fps')
|
||||||
|
|
||||||
|
if 'camera2' in recording_types:
|
||||||
|
self.camera2_video_writer = cv2.VideoWriter(
|
||||||
|
camera2_video_path, fourcc, self.camera2_current_fps, (self.camera2_region[2], self.camera2_region[3])
|
||||||
|
)
|
||||||
|
self.logger.info(f'相机2视频写入器使用帧率: {self.camera2_current_fps}fps')
|
||||||
|
|
||||||
if 'screen' in recording_types:
|
if 'screen' in recording_types:
|
||||||
self.screen_video_writer = cv2.VideoWriter(
|
self.screen_video_writer = cv2.VideoWriter(
|
||||||
@ -386,23 +413,24 @@ class RecordingManager:
|
|||||||
femtobolt_video_path, fourcc, self.femtobolt_current_fps, (self.femtobolt_region[2], self.femtobolt_region[3])
|
femtobolt_video_path, fourcc, self.femtobolt_current_fps, (self.femtobolt_region[2], self.femtobolt_region[3])
|
||||||
)
|
)
|
||||||
self.logger.info(f'FemtoBolt视频写入器使用帧率: {self.femtobolt_current_fps}fps')
|
self.logger.info(f'FemtoBolt视频写入器使用帧率: {self.femtobolt_current_fps}fps')
|
||||||
|
|
||||||
if 'feet' in recording_types:
|
|
||||||
self.feet_video_writer = cv2.VideoWriter(
|
# 检查相机1视频写入器
|
||||||
feet_video_path, fourcc, self.camera_current_fps, (self.camera_region[2], self.camera_region[3])
|
if 'camera1' in recording_types:
|
||||||
)
|
if self.camera1_video_writer and self.camera1_video_writer.isOpened():
|
||||||
self.logger.info(f'足部视频写入器使用帧率: {self.camera_current_fps}fps')
|
self.logger.info(f'相机1视频写入器初始化成功: {camera1_video_path}')
|
||||||
|
|
||||||
# 检查视频写入器状态(仅检查启用的录制类型)
|
|
||||||
# 检查足部视频写入器
|
|
||||||
if 'feet' in recording_types:
|
|
||||||
if self.feet_video_writer and self.feet_video_writer.isOpened():
|
|
||||||
self.logger.info(f'足部视频写入器初始化成功: {feet_video_path}')
|
|
||||||
else:
|
else:
|
||||||
self.logger.error(f'足部视频写入器初始化失败: {feet_video_path}')
|
self.logger.error(f'相机1视频写入器初始化失败: {camera1_video_path}')
|
||||||
|
else:
|
||||||
|
self.logger.info('相机1录制功能已禁用')
|
||||||
|
# 检查相机2视频写入器
|
||||||
|
if 'camera2' in recording_types:
|
||||||
|
if self.camera2_video_writer and self.camera2_video_writer.isOpened():
|
||||||
|
self.logger.info(f'相机2视频写入器初始化成功: {camera2_video_path}')
|
||||||
|
else:
|
||||||
|
self.logger.error(f'相机2视频写入器初始化失败: {camera2_video_path}')
|
||||||
else:
|
else:
|
||||||
self.logger.info('足部录制功能已禁用')
|
self.logger.info('足部录制功能已禁用')
|
||||||
|
|
||||||
# 检查屏幕视频写入器
|
# 检查屏幕视频写入器
|
||||||
if 'screen' in recording_types:
|
if 'screen' in recording_types:
|
||||||
if self.screen_video_writer and self.screen_video_writer.isOpened():
|
if self.screen_video_writer and self.screen_video_writer.isOpened():
|
||||||
@ -426,15 +454,25 @@ class RecordingManager:
|
|||||||
self.sync_recording = True
|
self.sync_recording = True
|
||||||
|
|
||||||
# 根据录制类型启动对应的录制线程
|
# 根据录制类型启动对应的录制线程
|
||||||
if 'feet' in recording_types and self.feet_video_writer and self.feet_video_writer.isOpened():
|
if 'camera1' in recording_types and self.camera1_video_writer and self.camera1_video_writer.isOpened():
|
||||||
self.feet_recording_thread = threading.Thread(
|
self.camera1_recording_thread = threading.Thread(
|
||||||
target=self._generic_recording_thread,
|
target=self._generic_recording_thread,
|
||||||
args=('camera', self.camera_region, feet_video_path, self.feet_video_writer),
|
args=('camera1', self.camera1_region, camera1_video_path, self.camera1_video_writer),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
name='FeetRecordingThread'
|
name='Camera1RecordingThread'
|
||||||
)
|
)
|
||||||
self.feet_recording_thread.start()
|
self.camera1_recording_thread.start()
|
||||||
# self.logger.info(f'足部录制线程已启动 - 区域: {self.camera_region}, 输出文件: {feet_video_path}')
|
# self.logger.info(f'相机1录制线程已启动 - 区域: {self.camera1_region}, 输出文件: {camera1_video_path}')
|
||||||
|
|
||||||
|
if 'camera2' in recording_types and self.camera2_video_writer and self.camera2_video_writer.isOpened():
|
||||||
|
self.camera2_recording_thread = threading.Thread(
|
||||||
|
target=self._generic_recording_thread,
|
||||||
|
args=('camera2', self.camera2_region, camera2_video_path, self.camera2_video_writer),
|
||||||
|
daemon=True,
|
||||||
|
name='Camera2RecordingThread'
|
||||||
|
)
|
||||||
|
self.camera2_recording_thread.start()
|
||||||
|
# self.logger.info(f'相机2录制线程已启动 - 区域: {self.camera2_region}, 输出文件: {camera2_video_path}')
|
||||||
|
|
||||||
if 'screen' in recording_types and self.screen_video_writer and self.screen_video_writer.isOpened():
|
if 'screen' in recording_types and self.screen_video_writer and self.screen_video_writer.isOpened():
|
||||||
self.screen_recording_thread = threading.Thread(
|
self.screen_recording_thread = threading.Thread(
|
||||||
@ -506,8 +544,10 @@ class RecordingManager:
|
|||||||
|
|
||||||
# 收集活跃的录制线程
|
# 收集活跃的录制线程
|
||||||
active_threads = []
|
active_threads = []
|
||||||
if hasattr(self, 'feet_recording_thread') and self.feet_recording_thread and self.feet_recording_thread.is_alive():
|
if hasattr(self, 'camera1_recording_thread') and self.camera1_recording_thread and self.camera1_recording_thread.is_alive():
|
||||||
active_threads.append(('feet', self.feet_recording_thread))
|
active_threads.append(('camera1', self.camera1_recording_thread))
|
||||||
|
if hasattr(self, 'camera2_recording_thread') and self.camera2_recording_thread and self.camera2_recording_thread.is_alive():
|
||||||
|
active_threads.append(('camera2', self.camera2_recording_thread))
|
||||||
if hasattr(self, 'screen_recording_thread') and self.screen_recording_thread and self.screen_recording_thread.is_alive():
|
if hasattr(self, 'screen_recording_thread') and self.screen_recording_thread and self.screen_recording_thread.is_alive():
|
||||||
active_threads.append(('screen', self.screen_recording_thread))
|
active_threads.append(('screen', self.screen_recording_thread))
|
||||||
if hasattr(self, 'femtobolt_recording_thread') and self.femtobolt_recording_thread and self.femtobolt_recording_thread.is_alive():
|
if hasattr(self, 'femtobolt_recording_thread') and self.femtobolt_recording_thread and self.femtobolt_recording_thread.is_alive():
|
||||||
@ -535,9 +575,12 @@ class RecordingManager:
|
|||||||
if thread_name == 'screen':
|
if thread_name == 'screen':
|
||||||
expected_frames = int(actual_recording_duration * self.screen_current_fps)
|
expected_frames = int(actual_recording_duration * self.screen_current_fps)
|
||||||
self.logger.info(f' 屏幕录制预期帧数: {expected_frames}帧 (帧率{self.screen_current_fps}fps)')
|
self.logger.info(f' 屏幕录制预期帧数: {expected_frames}帧 (帧率{self.screen_current_fps}fps)')
|
||||||
elif thread_name == 'feet':
|
elif thread_name == 'camera1':
|
||||||
expected_frames = int(actual_recording_duration * self.camera_current_fps)
|
expected_frames = int(actual_recording_duration * self.camera_current_fps)
|
||||||
self.logger.info(f' 足部录制预期帧数: {expected_frames}帧 (帧率{self.camera_current_fps}fps)')
|
self.logger.info(f' 相机1录制预期帧数: {expected_frames}帧 (帧率{self.camera_current_fps}fps)')
|
||||||
|
elif thread_name == 'camera2':
|
||||||
|
expected_frames = int(actual_recording_duration * self.camera_current_fps)
|
||||||
|
self.logger.info(f' 相机2录制预期帧数: {expected_frames}帧 (帧率{self.camera_current_fps}fps)')
|
||||||
elif thread_name == 'femtobolt':
|
elif thread_name == 'femtobolt':
|
||||||
expected_frames = int(actual_recording_duration * self.femtobolt_current_fps)
|
expected_frames = int(actual_recording_duration * self.femtobolt_current_fps)
|
||||||
self.logger.info(f' FemtoBolt录制预期帧数: {expected_frames}帧 (帧率{self.femtobolt_current_fps}fps)')
|
self.logger.info(f' FemtoBolt录制预期帧数: {expected_frames}帧 (帧率{self.femtobolt_current_fps}fps)')
|
||||||
@ -575,7 +618,7 @@ class RecordingManager:
|
|||||||
通用录制线程,支持屏幕、相机和FemtoBolt录制
|
通用录制线程,支持屏幕、相机和FemtoBolt录制
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
recording_type: 录制类型 ('screen', 'camera', 'femtobolt')
|
recording_type: 录制类型 ('screen', 'camera1', 'camera2', 'femtobolt')
|
||||||
region: 录制区域 (x, y, width, height)
|
region: 录制区域 (x, y, width, height)
|
||||||
output_file_name: 输出文件名
|
output_file_name: 输出文件名
|
||||||
video_writer: 视频写入器对象
|
video_writer: 视频写入器对象
|
||||||
@ -587,8 +630,10 @@ class RecordingManager:
|
|||||||
# 根据录制类型获取对应的自适应帧率
|
# 根据录制类型获取对应的自适应帧率
|
||||||
if recording_type == 'screen':
|
if recording_type == 'screen':
|
||||||
target_fps = self.screen_current_fps
|
target_fps = self.screen_current_fps
|
||||||
elif recording_type == 'camera':
|
elif recording_type == 'camera1':
|
||||||
target_fps = self.camera_current_fps
|
target_fps = self.camera1_current_fps
|
||||||
|
elif recording_type == 'camera2':
|
||||||
|
target_fps = self.camera2_current_fps
|
||||||
elif recording_type == 'femtobolt':
|
elif recording_type == 'femtobolt':
|
||||||
target_fps = self.femtobolt_current_fps
|
target_fps = self.femtobolt_current_fps
|
||||||
else:
|
else:
|
||||||
@ -789,9 +834,9 @@ class RecordingManager:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def collect_detection_data(self, session_id: str, patient_id: str, detection_data: Dict[str, Any]) -> Dict[str, Any]:
|
def save_detection_images(self, session_id: str, patient_id: str, detection_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
保存前端传入的检测数据和图片
|
保存前端传入的检测图片到指定目录
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session_id: 检测会话ID
|
session_id: 检测会话ID
|
||||||
@ -824,7 +869,8 @@ class RecordingManager:
|
|||||||
'body_image': None,
|
'body_image': None,
|
||||||
'foot_data': detection_data.get('foot_data'),
|
'foot_data': detection_data.get('foot_data'),
|
||||||
'foot_data_image': None,
|
'foot_data_image': None,
|
||||||
'foot_image': None,
|
'foot1_image': None,
|
||||||
|
'foot2_image': None,
|
||||||
'screen_image': None,
|
'screen_image': None,
|
||||||
'timestamp': timestamp
|
'timestamp': timestamp
|
||||||
}
|
}
|
||||||
@ -833,7 +879,8 @@ class RecordingManager:
|
|||||||
# 保存图片数据
|
# 保存图片数据
|
||||||
image_fields = [
|
image_fields = [
|
||||||
('body_image', 'body'),
|
('body_image', 'body'),
|
||||||
('foot_image', 'foot'),
|
('foot1_image', 'foot1'),
|
||||||
|
('foot2_image', 'foot2'),
|
||||||
('foot_data_image', 'foot_data')
|
('foot_data_image', 'foot_data')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -84,7 +84,8 @@ class DeviceTestServer:
|
|||||||
|
|
||||||
# 设备管理器和模拟数据生成器
|
# 设备管理器和模拟数据生成器
|
||||||
self.device_managers = {
|
self.device_managers = {
|
||||||
'camera': CameraManager(self.socketio, self.config_manager),
|
'camera1': CameraManager(self.socketio, self.config_manager, device_name='camera1'),
|
||||||
|
'camera2': CameraManager(self.socketio, self.config_manager, device_name='camera2'),
|
||||||
'femtobolt': FemtoBoltManager(self.socketio, self.config_manager),
|
'femtobolt': FemtoBoltManager(self.socketio, self.config_manager),
|
||||||
'imu': IMUManager(self.socketio, self.config_manager),
|
'imu': IMUManager(self.socketio, self.config_manager),
|
||||||
'pressure': PressureManager(self.socketio, self.config_manager)
|
'pressure': PressureManager(self.socketio, self.config_manager)
|
||||||
@ -340,7 +341,8 @@ class DeviceTestServer:
|
|||||||
def _get_event_name(self, device_name: str) -> str:
|
def _get_event_name(self, device_name: str) -> str:
|
||||||
"""获取设备对应的事件名称"""
|
"""获取设备对应的事件名称"""
|
||||||
event_map = {
|
event_map = {
|
||||||
'camera': 'camera_frame',
|
'camera1': 'camera_frame',
|
||||||
|
'camera2': 'camera_frame',
|
||||||
'femtobolt': 'femtobolt_frame',
|
'femtobolt': 'femtobolt_frame',
|
||||||
'imu': 'imu_data',
|
'imu': 'imu_data',
|
||||||
'pressure': 'pressure_data'
|
'pressure': 'pressure_data'
|
||||||
|
|||||||
@ -1,227 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
配置API测试脚本
|
|
||||||
用于测试设备配置HTTP API的功能
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigAPITester:
|
|
||||||
"""配置API测试器"""
|
|
||||||
|
|
||||||
def __init__(self, base_url="http://localhost:5002"):
|
|
||||||
"""
|
|
||||||
初始化测试器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
base_url: API基础URL
|
|
||||||
"""
|
|
||||||
self.base_url = base_url
|
|
||||||
self.api_url = f"{base_url}/api/config"
|
|
||||||
|
|
||||||
def test_get_all_configs(self):
|
|
||||||
"""测试获取所有设备配置"""
|
|
||||||
print("\n=== 测试获取所有设备配置 ===")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{self.api_url}/devices")
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 获取所有设备配置成功")
|
|
||||||
print(json.dumps(result['data'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 获取失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_get_single_config(self, device_name):
|
|
||||||
"""测试获取单个设备配置"""
|
|
||||||
print(f"\n=== 测试获取{device_name}设备配置 ===")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{self.api_url}/devices/{device_name}")
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print(f"✓ 获取{device_name}配置成功")
|
|
||||||
print(json.dumps(result['data'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 获取失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_set_imu_config(self):
|
|
||||||
"""测试设置IMU配置"""
|
|
||||||
print("\n=== 测试设置IMU配置 ===")
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
"device_type": "real",
|
|
||||||
"port": "COM6",
|
|
||||||
"baudrate": 9600
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(f"{self.api_url}/devices/imu", json=data)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 设置IMU配置成功")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
print("更新后的配置:")
|
|
||||||
print(json.dumps(result['config'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 设置失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_set_pressure_config(self):
|
|
||||||
"""测试设置压力板配置"""
|
|
||||||
print("\n=== 测试设置压力板配置 ===")
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
"device_type": "real",
|
|
||||||
"use_mock": False,
|
|
||||||
"port": "COM5",
|
|
||||||
"baudrate": 115200
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(f"{self.api_url}/devices/pressure", json=data)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 设置压力板配置成功")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
print("更新后的配置:")
|
|
||||||
print(json.dumps(result['config'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 设置失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_set_camera_config(self):
|
|
||||||
"""测试设置相机配置"""
|
|
||||||
print("\n=== 测试设置相机配置 ===")
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
"device_index": 0,
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720,
|
|
||||||
"fps": 30
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(f"{self.api_url}/devices/camera", json=data)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 设置相机配置成功")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
print("更新后的配置:")
|
|
||||||
print(json.dumps(result['config'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 设置失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_set_femtobolt_config(self):
|
|
||||||
"""测试设置FemtoBolt配置"""
|
|
||||||
print("\n=== 测试设置FemtoBolt配置 ===")
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
"color_resolution": "1080P",
|
|
||||||
"depth_mode": "NFOV_UNBINNED",
|
|
||||||
"fps": 30,
|
|
||||||
"depth_range_min": 1200,
|
|
||||||
"depth_range_max": 1500
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(f"{self.api_url}/devices/femtobolt", json=data)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 设置FemtoBolt配置成功")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
print("更新后的配置:")
|
|
||||||
print(json.dumps(result['config'], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print(f"✗ 设置失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_validate_config(self):
|
|
||||||
"""测试验证配置"""
|
|
||||||
print("\n=== 测试验证配置 ===")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{self.api_url}/validate")
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 配置验证成功")
|
|
||||||
validation_result = result['data']
|
|
||||||
print(f"配置有效性: {validation_result['valid']}")
|
|
||||||
if validation_result['errors']:
|
|
||||||
print(f"错误: {validation_result['errors']}")
|
|
||||||
if validation_result['warnings']:
|
|
||||||
print(f"警告: {validation_result['warnings']}")
|
|
||||||
else:
|
|
||||||
print(f"✗ 验证失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def test_reload_config(self):
|
|
||||||
"""测试重新加载配置"""
|
|
||||||
print("\n=== 测试重新加载配置 ===")
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{self.api_url}/reload")
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
print("✓ 重新加载配置成功")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
else:
|
|
||||||
print(f"✗ 重新加载失败: {result['message']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ 请求异常: {e}")
|
|
||||||
|
|
||||||
def run_all_tests(self):
|
|
||||||
"""运行所有测试"""
|
|
||||||
print("开始配置API功能测试...")
|
|
||||||
print(f"API地址: {self.api_url}")
|
|
||||||
|
|
||||||
# 等待服务启动
|
|
||||||
print("\n等待API服务启动...")
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# 运行测试
|
|
||||||
self.test_get_all_configs()
|
|
||||||
|
|
||||||
# 测试获取单个设备配置
|
|
||||||
for device in ['imu', 'pressure', 'camera', 'femtobolt']:
|
|
||||||
self.test_get_single_config(device)
|
|
||||||
|
|
||||||
# 测试设置配置
|
|
||||||
self.test_set_imu_config()
|
|
||||||
self.test_set_pressure_config()
|
|
||||||
self.test_set_camera_config()
|
|
||||||
self.test_set_femtobolt_config()
|
|
||||||
|
|
||||||
# 测试其他功能
|
|
||||||
self.test_validate_config()
|
|
||||||
self.test_reload_config()
|
|
||||||
|
|
||||||
print("\n=== 测试完成 ===")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 创建测试器并运行测试
|
|
||||||
tester = ConfigAPITester()
|
|
||||||
tester.run_all_tests()
|
|
||||||
@ -102,8 +102,8 @@ class ConfigManager:
|
|||||||
'pressure_baudrate': '115200'
|
'pressure_baudrate': '115200'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 默认相机配置
|
# 默认相机1配置
|
||||||
self.config['CAMERA'] = {
|
self.config['CAMERA1'] = {
|
||||||
'device_index': '0',
|
'device_index': '0',
|
||||||
'width': '1280',
|
'width': '1280',
|
||||||
'height': '720',
|
'height': '720',
|
||||||
@ -111,6 +111,15 @@ class ConfigManager:
|
|||||||
'backend': 'directshow'
|
'backend': 'directshow'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 默认相机2配置
|
||||||
|
self.config['CAMERA2'] = {
|
||||||
|
'device_index': '1',
|
||||||
|
'width': '1280',
|
||||||
|
'height': '720',
|
||||||
|
'fps': '30',
|
||||||
|
'backend': 'directshow'
|
||||||
|
}
|
||||||
|
|
||||||
# 默认FemtoBolt配置
|
# 默认FemtoBolt配置
|
||||||
self.config['FEMTOBOLT'] = {
|
self.config['FEMTOBOLT'] = {
|
||||||
'color_resolution': '1080P',
|
'color_resolution': '1080P',
|
||||||
@ -134,7 +143,7 @@ class ConfigManager:
|
|||||||
获取设备配置
|
获取设备配置
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
device_name: 设备名称 (camera, femtobolt, imu, pressure)
|
device_name: 设备名称 (camera1, camera2, femtobolt, imu, pressure)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Any]: 设备配置字典
|
Dict[str, Any]: 设备配置字典
|
||||||
@ -144,8 +153,10 @@ class ConfigManager:
|
|||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
if device_name == 'camera':
|
if device_name == 'camera1':
|
||||||
config = self._get_camera_config()
|
config = self._get_camera1_config()
|
||||||
|
elif device_name == 'camera2':
|
||||||
|
config = self._get_camera2_config()
|
||||||
elif device_name == 'femtobolt':
|
elif device_name == 'femtobolt':
|
||||||
config = self._get_femtobolt_config()
|
config = self._get_femtobolt_config()
|
||||||
elif device_name == 'imu':
|
elif device_name == 'imu':
|
||||||
@ -159,7 +170,7 @@ class ConfigManager:
|
|||||||
self._device_configs[device_name] = config
|
self._device_configs[device_name] = config
|
||||||
return config.copy()
|
return config.copy()
|
||||||
|
|
||||||
def _get_camera_config(self) -> Dict[str, Any]:
|
def _get_camera1_config(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
获取相机配置
|
获取相机配置
|
||||||
|
|
||||||
@ -167,16 +178,32 @@ class ConfigManager:
|
|||||||
Dict[str, Any]: 相机配置
|
Dict[str, Any]: 相机配置
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'enabled': self.config.getboolean('CAMERA', 'enabled', fallback=True),
|
'enabled': self.config.getboolean('CAMERA1', 'enabled', fallback=True),
|
||||||
'device_index': self.config.getint('CAMERA', 'device_index', fallback=0),
|
'device_index': self.config.getint('CAMERA1', 'device_index', fallback=0),
|
||||||
'width': self.config.getint('CAMERA', 'width', fallback=1280),
|
'width': self.config.getint('CAMERA1', 'width', fallback=1280),
|
||||||
'height': self.config.getint('CAMERA', 'height', fallback=720),
|
'height': self.config.getint('CAMERA1', 'height', fallback=720),
|
||||||
'fps': self.config.getint('CAMERA', 'fps', fallback=30),
|
'fps': self.config.getint('CAMERA1', 'fps', fallback=30),
|
||||||
'buffer_size': self.config.getint('CAMERA', 'buffer_size', fallback=1),
|
'buffer_size': self.config.getint('CAMERA1', 'buffer_size', fallback=1),
|
||||||
'fourcc': self.config.get('CAMERA', 'fourcc', fallback='MJPG'),
|
'fourcc': self.config.get('CAMERA1', 'fourcc', fallback='MJPG'),
|
||||||
'backend': self.config.get('CAMERA', 'backend', fallback='directshow')
|
'backend': self.config.get('CAMERA1', 'backend', fallback='directshow')
|
||||||
}
|
}
|
||||||
|
def _get_camera2_config(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
获取相机配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'enabled': self.config.getboolean('CAMERA2', 'enabled', fallback=True),
|
||||||
|
'device_index': self.config.getint('CAMERA2', 'device_index', fallback=0),
|
||||||
|
'width': self.config.getint('CAMERA2', 'width', fallback=1280),
|
||||||
|
'height': self.config.getint('CAMERA2', 'height', fallback=720),
|
||||||
|
'fps': self.config.getint('CAMERA2', 'fps', fallback=30),
|
||||||
|
'buffer_size': self.config.getint('CAMERA2', 'buffer_size', fallback=1),
|
||||||
|
'fourcc': self.config.get('CAMERA2', 'fourcc', fallback='MJPG'),
|
||||||
|
'backend': self.config.get('CAMERA2', 'backend', fallback='directshow')
|
||||||
|
}
|
||||||
def _get_femtobolt_config(self) -> Dict[str, Any]:
|
def _get_femtobolt_config(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
获取FemtoBolt配置
|
获取FemtoBolt配置
|
||||||
@ -332,7 +359,7 @@ class ConfigManager:
|
|||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
# 验证必需的配置段
|
# 验证必需的配置段
|
||||||
required_sections = ['DEVICES', 'CAMERA', 'FEMTOBOLT', 'SYSTEM']
|
required_sections = ['DEVICES', 'CAMERA1', 'CAMERA2', 'FEMTOBOLT', 'SYSTEM']
|
||||||
for section in required_sections:
|
for section in required_sections:
|
||||||
if not self.config.has_section(section):
|
if not self.config.has_section(section):
|
||||||
errors.append(f"缺少必需的配置段: {section}")
|
errors.append(f"缺少必需的配置段: {section}")
|
||||||
@ -438,7 +465,7 @@ class ConfigManager:
|
|||||||
'message': f'设置压力板配置失败: {str(e)}'
|
'message': f'设置压力板配置失败: {str(e)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_camera_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
|
def set_camera1_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
设置相机设备配置
|
设置相机设备配置
|
||||||
|
|
||||||
@ -457,15 +484,15 @@ class ConfigManager:
|
|||||||
try:
|
try:
|
||||||
# 验证必需参数
|
# 验证必需参数
|
||||||
if 'device_index' in config_data:
|
if 'device_index' in config_data:
|
||||||
self.set_config_value('CAMERA', 'device_index', str(config_data['device_index']))
|
self.set_config_value('CAMERA1', 'device_index', str(config_data['device_index']))
|
||||||
if 'width' in config_data:
|
if 'width' in config_data:
|
||||||
self.set_config_value('CAMERA', 'width', str(config_data['width']))
|
self.set_config_value('CAMERA1', 'width', str(config_data['width']))
|
||||||
if 'height' in config_data:
|
if 'height' in config_data:
|
||||||
self.set_config_value('CAMERA', 'height', str(config_data['height']))
|
self.set_config_value('CAMERA1', 'height', str(config_data['height']))
|
||||||
if 'fps' in config_data:
|
if 'fps' in config_data:
|
||||||
self.set_config_value('CAMERA', 'fps', str(config_data['fps']))
|
self.set_config_value('CAMERA1', 'fps', str(config_data['fps']))
|
||||||
if 'backend' in config_data:
|
if 'backend' in config_data:
|
||||||
self.set_config_value('CAMERA', 'backend', str(config_data['backend']))
|
self.set_config_value('CAMERA1', 'backend', str(config_data['backend']))
|
||||||
|
|
||||||
# 保存配置
|
# 保存配置
|
||||||
self.save_config()
|
self.save_config()
|
||||||
@ -474,7 +501,7 @@ class ConfigManager:
|
|||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': '相机配置更新成功',
|
'message': '相机配置更新成功',
|
||||||
'config': self.get_device_config('camera')
|
'config': self.get_device_config('camera1')
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"设置相机配置失败: {e}")
|
self.logger.error(f"设置相机配置失败: {e}")
|
||||||
@ -482,7 +509,50 @@ class ConfigManager:
|
|||||||
'success': False,
|
'success': False,
|
||||||
'message': f'设置相机配置失败: {str(e)}'
|
'message': f'设置相机配置失败: {str(e)}'
|
||||||
}
|
}
|
||||||
|
def set_camera2_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
设置相机设备配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_data: 相机配置数据
|
||||||
|
{
|
||||||
|
'device_index': 1,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
'fps': 30
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 设置结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 验证必需参数
|
||||||
|
if 'device_index' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'device_index', str(config_data['device_index']))
|
||||||
|
if 'width' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'width', str(config_data['width']))
|
||||||
|
if 'height' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'height', str(config_data['height']))
|
||||||
|
if 'fps' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'fps', str(config_data['fps']))
|
||||||
|
if 'backend' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'backend', str(config_data['backend']))
|
||||||
|
|
||||||
|
# 保存配置
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
|
self.logger.info(f"相机配置已更新: {config_data}")
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': '相机配置更新成功',
|
||||||
|
'config': self.get_device_config('camera2')
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"设置相机配置失败: {e}")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'设置相机配置失败: {str(e)}'
|
||||||
|
}
|
||||||
def set_femtobolt_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
|
def set_femtobolt_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
设置FemtoBolt设备配置
|
设置FemtoBolt设备配置
|
||||||
@ -541,7 +611,8 @@ class ConfigManager:
|
|||||||
return {
|
return {
|
||||||
'imu': self.get_device_config('imu'),
|
'imu': self.get_device_config('imu'),
|
||||||
'pressure': self.get_device_config('pressure'),
|
'pressure': self.get_device_config('pressure'),
|
||||||
'camera': self.get_device_config('camera'),
|
'camera1': self.get_device_config('camera1'),
|
||||||
|
'camera2': self.get_device_config('camera2'),
|
||||||
'femtobolt': self.get_device_config('femtobolt')
|
'femtobolt': self.get_device_config('femtobolt')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,27 +681,27 @@ class ConfigManager:
|
|||||||
self.logger.error(error_msg)
|
self.logger.error(error_msg)
|
||||||
|
|
||||||
# 相机配置
|
# 相机配置
|
||||||
if 'camera' in configs:
|
if 'camera1' in configs:
|
||||||
try:
|
try:
|
||||||
config_data = configs['camera']
|
config_data = configs['camera1']
|
||||||
if 'device_index' in config_data:
|
if 'device_index' in config_data:
|
||||||
self.set_config_value('CAMERA', 'device_index', str(config_data['device_index']))
|
self.set_config_value('CAMERA1', 'device_index', str(config_data['device_index']))
|
||||||
if 'width' in config_data:
|
if 'width' in config_data:
|
||||||
self.set_config_value('CAMERA', 'width', str(config_data['width']))
|
self.set_config_value('CAMERA1', 'width', str(config_data['width']))
|
||||||
if 'height' in config_data:
|
if 'height' in config_data:
|
||||||
self.set_config_value('CAMERA', 'height', str(config_data['height']))
|
self.set_config_value('CAMERA1', 'height', str(config_data['height']))
|
||||||
if 'fps' in config_data:
|
if 'fps' in config_data:
|
||||||
self.set_config_value('CAMERA', 'fps', str(config_data['fps']))
|
self.set_config_value('CAMERA1', 'fps', str(config_data['fps']))
|
||||||
if 'buffer_size' in config_data:
|
if 'buffer_size' in config_data:
|
||||||
self.set_config_value('CAMERA', 'buffer_size', str(config_data['buffer_size']))
|
self.set_config_value('CAMERA1', 'buffer_size', str(config_data['buffer_size']))
|
||||||
if 'fourcc' in config_data:
|
if 'fourcc' in config_data:
|
||||||
self.set_config_value('CAMERA', 'fourcc', config_data['fourcc'])
|
self.set_config_value('CAMERA1', 'fourcc', config_data['fourcc'])
|
||||||
if 'tx_max_width' in config_data:
|
if 'tx_max_width' in config_data:
|
||||||
self.set_config_value('CAMERA', 'tx_max_width', str(config_data['tx_max_width']))
|
self.set_config_value('CAMERA1', 'tx_max_width', str(config_data['tx_max_width']))
|
||||||
if 'backend' in config_data:
|
if 'backend' in config_data:
|
||||||
self.set_config_value('CAMERA', 'backend', str(config_data['backend']))
|
self.set_config_value('CAMERA1', 'backend', str(config_data['backend']))
|
||||||
|
|
||||||
results['camera'] = {
|
results['camera1'] = {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': '相机配置更新成功',
|
'message': '相机配置更新成功',
|
||||||
'config': config_data
|
'config': config_data
|
||||||
@ -638,10 +709,42 @@ class ConfigManager:
|
|||||||
self.logger.info(f"相机配置已更新: {config_data}")
|
self.logger.info(f"相机配置已更新: {config_data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f'设置相机配置失败: {str(e)}'
|
error_msg = f'设置相机配置失败: {str(e)}'
|
||||||
results['camera'] = {'success': False, 'message': error_msg}
|
results['camera1'] = {'success': False, 'message': error_msg}
|
||||||
errors.append(f"相机: {error_msg}")
|
errors.append(f"相机: {error_msg}")
|
||||||
self.logger.error(error_msg)
|
self.logger.error(error_msg)
|
||||||
|
|
||||||
|
if 'camera2' in configs:
|
||||||
|
try:
|
||||||
|
config_data = configs['camera2']
|
||||||
|
if 'device_index' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'device_index', str(config_data['device_index']))
|
||||||
|
if 'width' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'width', str(config_data['width']))
|
||||||
|
if 'height' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'height', str(config_data['height']))
|
||||||
|
if 'fps' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'fps', str(config_data['fps']))
|
||||||
|
if 'buffer_size' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'buffer_size', str(config_data['buffer_size']))
|
||||||
|
if 'fourcc' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'fourcc', config_data['fourcc'])
|
||||||
|
if 'tx_max_width' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'tx_max_width', str(config_data['tx_max_width']))
|
||||||
|
if 'backend' in config_data:
|
||||||
|
self.set_config_value('CAMERA2', 'backend', str(config_data['backend']))
|
||||||
|
|
||||||
|
results['camera2'] = {
|
||||||
|
'success': True,
|
||||||
|
'message': '相机配置更新成功',
|
||||||
|
'config': config_data
|
||||||
|
}
|
||||||
|
self.logger.info(f"相机配置已更新: {config_data}")
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f'设置相机配置失败: {str(e)}'
|
||||||
|
results['camera2'] = {'success': False, 'message': error_msg}
|
||||||
|
errors.append(f"相机2: {error_msg}")
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
|
||||||
# FemtoBolt配置
|
# FemtoBolt配置
|
||||||
if 'femtobolt' in configs:
|
if 'femtobolt' in configs:
|
||||||
try:
|
try:
|
||||||
@ -703,7 +806,8 @@ class ConfigManager:
|
|||||||
{
|
{
|
||||||
'imu': {'device_type': 'real', 'port': 'COM7', 'baudrate': 9600},
|
'imu': {'device_type': 'real', 'port': 'COM7', 'baudrate': 9600},
|
||||||
'pressure': {'device_type': 'real', 'port': 'COM8', 'baudrate': 115200},
|
'pressure': {'device_type': 'real', 'port': 'COM8', 'baudrate': 115200},
|
||||||
'camera': {'device_index': 0, 'width': 1280, 'height': 720, 'fps': 30},
|
'camera1': {'device_index': 0, 'width': 1280, 'height': 720, 'fps': 30},
|
||||||
|
'camera2': {'device_index': 1, 'width': 1280, 'height': 720, 'fps': 30},
|
||||||
'femtobolt': {'color_resolution': '1080P', 'depth_mode': 'NFOV_UNBINNED', 'fps': 15}
|
'femtobolt': {'color_resolution': '1080P', 'depth_mode': 'NFOV_UNBINNED', 'fps': 15}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
382
backend/main.py
@ -25,6 +25,7 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
# 导入模块
|
# 导入模块
|
||||||
from database import DatabaseManager
|
from database import DatabaseManager
|
||||||
from utils import config as app_config
|
from utils import config as app_config
|
||||||
|
from utils import DataValidator # 添加数据验证器导入
|
||||||
from devices.camera_manager import CameraManager
|
from devices.camera_manager import CameraManager
|
||||||
from devices.imu_manager import IMUManager
|
from devices.imu_manager import IMUManager
|
||||||
from devices.pressure_manager import PressureManager
|
from devices.pressure_manager import PressureManager
|
||||||
@ -163,8 +164,9 @@ class AppServer:
|
|||||||
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
|
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
|
||||||
|
|
||||||
self.config.read(config_path, encoding='utf-8')
|
self.config.read(config_path, encoding='utf-8')
|
||||||
device_index = self.config.get('CAMERA', 'device_index', fallback=None)
|
camera1_index = self.config.get('CAMERA1', 'device_index', fallback=None)
|
||||||
print(f"设备号: {device_index}")
|
camera2_index = self.config.get('CAMERA2', 'device_index', fallback=None)
|
||||||
|
print(f"相机1设备号: {camera1_index}, 相机2设备号: {camera2_index}")
|
||||||
|
|
||||||
def init_app(self):
|
def init_app(self):
|
||||||
"""初始化应用组件"""
|
"""初始化应用组件"""
|
||||||
@ -243,16 +245,21 @@ class AppServer:
|
|||||||
|
|
||||||
# 初始化录制管理器
|
# 初始化录制管理器
|
||||||
self.logger.info('正在初始化录制管理器...')
|
self.logger.info('正在初始化录制管理器...')
|
||||||
camera_manager = self.device_managers.get('camera')
|
camera1_manager = self.device_managers.get('camera1')
|
||||||
if camera_manager:
|
camera2_manager = self.device_managers.get('camera2')
|
||||||
self.recording_manager = RecordingManager(
|
femtobolt_manager = self.device_managers.get('femtobolt')
|
||||||
camera_manager=camera_manager,
|
pressure_manager = self.device_managers.get('pressure')
|
||||||
db_manager=self.db_manager
|
|
||||||
)
|
# 录制管理器当前采用屏幕区域截取方式进行相机录制,不依赖 CameraManager
|
||||||
self.logger.info('录制管理器初始化完成')
|
# 但保留其他设备管理器以便后续扩展(如FemtoBolt、压力传感器)
|
||||||
else:
|
self.recording_manager = RecordingManager(
|
||||||
self.recording_manager = None
|
camera_manager=None,
|
||||||
self.logger.warning('相机设备未初始化,录制管理器将不可用')
|
db_manager=self.db_manager,
|
||||||
|
femtobolt_manager=femtobolt_manager,
|
||||||
|
pressure_manager=pressure_manager,
|
||||||
|
config_manager=self.config_manager
|
||||||
|
)
|
||||||
|
self.logger.info('录制管理器初始化完成')
|
||||||
|
|
||||||
# 启动Flask应用
|
# 启动Flask应用
|
||||||
host = self.host
|
host = self.host
|
||||||
@ -895,15 +902,16 @@ class AppServer:
|
|||||||
if status not in ['approved', 'rejected']:
|
if status not in ['approved', 'rejected']:
|
||||||
return jsonify({'success': False, 'error': '无效的审核状态'}), 400
|
return jsonify({'success': False, 'error': '无效的审核状态'}), 400
|
||||||
|
|
||||||
result = self.db_manager.update_user_status(user_id, status)
|
# 使用数据库层已有的审核方法
|
||||||
if result:
|
try:
|
||||||
|
self.db_manager.approve_user(user_id, approved=(status == 'approved'))
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': f'用户已{"通过" if status == "approved" else "拒绝"}审核'
|
'message': f'用户已{"通过" if status == "approved" else "拒绝"}审核'
|
||||||
})
|
})
|
||||||
else:
|
except Exception:
|
||||||
return jsonify({'success': False, 'error': '用户不存在'}), 404
|
return jsonify({'success': False, 'error': '用户不存在或审核失败'}), 404
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'审核用户失败: {e}')
|
self.logger.error(f'审核用户失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
@ -952,48 +960,52 @@ class AppServer:
|
|||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
elif flask_request.method == 'POST':
|
elif flask_request.method == 'POST':
|
||||||
# 创建患者
|
# 创建新患者
|
||||||
try:
|
try:
|
||||||
# 检查Content-Type
|
data = flask_request.get_json()
|
||||||
if not flask_request.is_json:
|
|
||||||
return jsonify({'success': False, 'message': '请求Content-Type必须为application/json'}), 415
|
|
||||||
|
|
||||||
data = flask_request.get_json(force=True)
|
# 验证患者数据
|
||||||
|
validation_result = DataValidator.validate_patient_data(data)
|
||||||
required_fields = ['name', 'gender', 'age']
|
if not validation_result['valid']:
|
||||||
for field in required_fields:
|
return jsonify({
|
||||||
if not data.get(field):
|
'success': False,
|
||||||
return jsonify({'success': False, 'error': f'{field}不能为空'}), 400
|
'error': '; '.join(validation_result['errors'])
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 准备患者数据
|
||||||
patient_data = {
|
patient_data = {
|
||||||
'name': data['name'],
|
'name': validation_result['data'].get('name'),
|
||||||
'gender': data['gender'],
|
'gender': validation_result['data'].get('gender'),
|
||||||
'age': data['age'],
|
'birth_date': validation_result['data'].get('birth_date'),
|
||||||
'birth_date': data.get('birth_date'),
|
'nationality': validation_result['data'].get('nationality'),
|
||||||
'nationality': data.get('nationality'),
|
'residence': validation_result['data'].get('residence'),
|
||||||
'residence': data.get('residence'),
|
'height': validation_result['data'].get('height'),
|
||||||
'height': data.get('height'),
|
'weight': validation_result['data'].get('weight'),
|
||||||
'weight': data.get('weight'),
|
'shoe_size': validation_result['data'].get('shoe_size'),
|
||||||
'shoe_size': data.get('shoe_size'),
|
'phone': validation_result['data'].get('phone'),
|
||||||
'phone': data.get('phone'),
|
'email': validation_result['data'].get('email'),
|
||||||
'email': data.get('email'),
|
'occupation': validation_result['data'].get('occupation'),
|
||||||
'occupation': data.get('occupation'),
|
'workplace': validation_result['data'].get('workplace'),
|
||||||
'workplace': data.get('workplace'),
|
'idcode': validation_result['data'].get('idcode'),
|
||||||
'medical_history': data.get('medical_history', ''),
|
'medical_history': validation_result['data'].get('medical_history'),
|
||||||
'notes': data.get('notes', '')
|
'notes': validation_result['data'].get('notes')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 创建患者
|
||||||
patient_id = self.db_manager.create_patient(patient_data)
|
patient_id = self.db_manager.create_patient(patient_data)
|
||||||
|
|
||||||
if patient_id:
|
# 获取创建的患者信息
|
||||||
return jsonify({
|
patient = self.db_manager.get_patient(patient_id)
|
||||||
'success': True,
|
|
||||||
'message': '患者创建成功',
|
return jsonify({
|
||||||
'data': {'patient_id': patient_id}
|
'success': True,
|
||||||
})
|
'message': '患者创建成功',
|
||||||
else:
|
'data': {
|
||||||
return jsonify({'success': False, 'error': '患者创建失败'}), 500
|
'patient_id': patient_id,
|
||||||
|
'patient': patient
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'创建患者失败: {e}')
|
self.logger.error(f'创建患者失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
@ -1005,10 +1017,7 @@ class AppServer:
|
|||||||
# 获取患者详情
|
# 获取患者详情
|
||||||
try:
|
try:
|
||||||
patient = self.db_manager.get_patient(patient_id)
|
patient = self.db_manager.get_patient(patient_id)
|
||||||
if patient:
|
return jsonify({'success': True, 'data': patient})
|
||||||
return jsonify({'success': True, 'data': patient})
|
|
||||||
else:
|
|
||||||
return jsonify({'success': False, 'error': '患者不存在'}), 404
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'获取患者详情失败: {e}')
|
self.logger.error(f'获取患者详情失败: {e}')
|
||||||
@ -1033,17 +1042,13 @@ class AppServer:
|
|||||||
'email': data.get('email'),
|
'email': data.get('email'),
|
||||||
'occupation': data.get('occupation'),
|
'occupation': data.get('occupation'),
|
||||||
'workplace': data.get('workplace'),
|
'workplace': data.get('workplace'),
|
||||||
|
'idcode': data.get('idcode'),
|
||||||
'medical_history': data.get('medical_history'),
|
'medical_history': data.get('medical_history'),
|
||||||
'notes': data.get('notes')
|
'notes': data.get('notes')
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db_manager.update_patient(patient_id, patient_data)
|
self.db_manager.update_patient(patient_id, patient_data)
|
||||||
result = True
|
return jsonify({'success': True, 'message': '患者信息更新成功'})
|
||||||
|
|
||||||
if result:
|
|
||||||
return jsonify({'success': True, 'message': '患者信息更新成功'})
|
|
||||||
else:
|
|
||||||
return jsonify({'success': False, 'error': '患者不存在'}), 404
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'更新患者信息失败: {e}')
|
self.logger.error(f'更新患者信息失败: {e}')
|
||||||
@ -1053,10 +1058,7 @@ class AppServer:
|
|||||||
# 删除患者
|
# 删除患者
|
||||||
try:
|
try:
|
||||||
result = self.db_manager.delete_patient(patient_id)
|
result = self.db_manager.delete_patient(patient_id)
|
||||||
if result:
|
return jsonify({'success': True, 'message': '患者已删除'})
|
||||||
return jsonify({'success': True, 'message': '患者已删除'})
|
|
||||||
else:
|
|
||||||
return jsonify({'success': False, 'error': '患者不存在'}), 404
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'删除患者失败: {e}')
|
self.logger.error(f'删除患者失败: {e}')
|
||||||
@ -1076,21 +1078,8 @@ class AppServer:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'获取设备状态失败: {e}')
|
self.logger.error(f'获取设备状态失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@self.app.route('/api/devices/refresh', methods=['POST'])
|
|
||||||
def refresh_devices():
|
|
||||||
"""刷新设备"""
|
|
||||||
try:
|
|
||||||
if self.device_coordinator:
|
|
||||||
result = self.device_coordinator.refresh_all_devices()
|
|
||||||
return jsonify({'success': True, 'data': result})
|
|
||||||
else:
|
|
||||||
return jsonify({'success': False, 'error': '设备协调器未初始化'}), 500
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f'刷新设备失败: {e}')
|
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
# ==================== 设备配置API ====================
|
# ==================== 设备配置API ====================
|
||||||
|
|
||||||
@ -1222,6 +1211,19 @@ class AppServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'开始检测失败: {e}')
|
self.logger.error(f'开始检测失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
@self.app.route('/api/detection/<session_id>/has_data', methods=['GET'])
|
||||||
|
def has_session_detection_data(session_id: str):
|
||||||
|
"""检查指定会话是否存在检测数据,用于判断单次检测是否有效"""
|
||||||
|
try:
|
||||||
|
if not self.db_manager:
|
||||||
|
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
||||||
|
|
||||||
|
exists = self.db_manager.has_session_detection_data(session_id)
|
||||||
|
return jsonify({'success': True, 'session_id': session_id, 'has_data': bool(exists)})
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'检查会话检测数据存在失败: {e}')
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@self.app.route('/api/detection/<session_id>/stop', methods=['POST'])
|
@self.app.route('/api/detection/<session_id>/stop', methods=['POST'])
|
||||||
def stop_detection(session_id):
|
def stop_detection(session_id):
|
||||||
@ -1246,24 +1248,22 @@ class AppServer:
|
|||||||
'message': '空白会话已删除'
|
'message': '空白会话已删除'
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# 正常会话的停止流程
|
# 正常会话的停止流程:调用数据库层结束检测,自动计算时长并写入结束信息
|
||||||
# 如果提供了duration,更新到数据库
|
|
||||||
data = flask_request.get_json() or {}
|
data = flask_request.get_json() or {}
|
||||||
duration = data.get('duration')
|
diagnosis_info = data.get('diagnosis_info')
|
||||||
if duration is not None and isinstance(duration, (int, float)):
|
treatment_info = data.get('treatment_info')
|
||||||
try:
|
suggestion_info = data.get('suggestion_info')
|
||||||
self.db_manager.update_session_duration(session_id, int(duration))
|
success = self.db_manager.update_session_endcheck(
|
||||||
self.logger.info(f'更新会话持续时间: {session_id} -> {duration}秒')
|
session_id,
|
||||||
except Exception as duration_error:
|
diagnosis_info=diagnosis_info,
|
||||||
self.logger.error(f'更新会话持续时间失败: {duration_error}')
|
treatment_info=treatment_info,
|
||||||
|
suggestion_info=suggestion_info
|
||||||
# 更新会话状态为已完成
|
)
|
||||||
success = self.db_manager.update_session_status(session_id, 'completed')
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info(f'检测会话已停止 - 会话ID: {session_id}')
|
self.logger.info(f'检测会话已结束检查 - 会话ID: {session_id}')
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': '检测已停止'
|
'message': '检测已结束并已写入总结信息'
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
self.logger.error('停止检测失败,更新会话状态失败')
|
self.logger.error('停止检测失败,更新会话状态失败')
|
||||||
@ -1284,32 +1284,35 @@ class AppServer:
|
|||||||
|
|
||||||
data = flask_request.get_json()
|
data = flask_request.get_json()
|
||||||
patient_id = data.get('patient_id')
|
patient_id = data.get('patient_id')
|
||||||
screen_location = data.get('screen_location') # [0,0,1920,1080]
|
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]
|
femtobolt_location = data.get('femtobolt_location') # [0,0,640,480]
|
||||||
|
camera1_location = data.get('camera1_location') # [0,0,640,480]
|
||||||
|
camera2_location = data.get('camera2_location') # [0,0,640,480]
|
||||||
if not patient_id:
|
if not patient_id:
|
||||||
return jsonify({'success': False, 'error': '缺少患者ID'}), 400
|
return jsonify({'success': False, 'error': '缺少患者ID'}), 400
|
||||||
|
|
||||||
# 开始视频录制
|
# 开始视频录制
|
||||||
recording_response = None
|
recording_response = None
|
||||||
try:
|
try:
|
||||||
recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera_location,femtobolt_location)
|
recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera1_location,camera2_location,femtobolt_location)
|
||||||
|
|
||||||
# 处理录制管理器返回的数据库更新信息
|
# 处理录制管理器返回的数据库更新信息
|
||||||
if recording_response and recording_response.get('success') and 'database_updates' in recording_response:
|
if recording_response and recording_response.get('success') and 'database_updates' in recording_response:
|
||||||
db_updates = recording_response['database_updates']
|
db_updates = recording_response['database_updates']
|
||||||
try:
|
try:
|
||||||
# 更新会话状态
|
# 保存检测视频记录(映射到 detection_video 表字段)
|
||||||
if not self.db_manager.update_session_status(db_updates['session_id'], db_updates['status']):
|
video_paths = db_updates.get('video_paths', {})
|
||||||
self.logger.error(f'更新会话状态失败 - 会话ID: {db_updates["session_id"]}, 状态: {db_updates["status"]}')
|
video_record = {
|
||||||
|
'screen_video_path': video_paths.get('screen_video_path'),
|
||||||
# 更新视频文件路径
|
'femtobolt_video_path': video_paths.get('femtobolt_video_path'),
|
||||||
video_paths = db_updates['video_paths']
|
'camera1_video_path': video_paths.get('camera1_video_path'),
|
||||||
self.db_manager.update_session_normal_video_path(db_updates['session_id'], video_paths['normal_video_path'])
|
'camera2_video_path': video_paths.get('camera2_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'])
|
|
||||||
|
try:
|
||||||
|
self.db_manager.save_detection_video(db_updates['session_id'], video_record)
|
||||||
|
except Exception as video_err:
|
||||||
|
self.logger.error(f'保存检测视频记录失败: {video_err}')
|
||||||
|
|
||||||
self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}')
|
self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}')
|
||||||
except Exception as db_error:
|
except Exception as db_error:
|
||||||
@ -1329,55 +1332,20 @@ class AppServer:
|
|||||||
def stop_record(session_id):
|
def stop_record(session_id):
|
||||||
"""停止视频录制"""
|
"""停止视频录制"""
|
||||||
try:
|
try:
|
||||||
if not self.db_manager or not self.device_coordinator:
|
|
||||||
self.logger.error('数据库管理器或设备管理器未初始化')
|
|
||||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
|
||||||
|
|
||||||
if not session_id:
|
if not session_id:
|
||||||
self.logger.error('缺少会话ID')
|
self.logger.error('缺少会话ID')
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': '缺少会话ID'
|
'error': '缺少会话ID'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 停止同步录制,传递视频数据
|
# 停止同步录制,传递视频数据
|
||||||
try:
|
try:
|
||||||
restrt = self.recording_manager.stop_recording(session_id)
|
restrt = self.recording_manager.stop_recording(session_id)
|
||||||
self.logger.info(f'停止录制结果: {restrt}')
|
self.logger.info(f'停止录制结果: {restrt}')
|
||||||
|
|
||||||
# 处理录制管理器返回的数据库更新信息
|
|
||||||
if restrt and restrt.get('success') and 'database_updates' in restrt:
|
|
||||||
db_updates = restrt['database_updates']
|
|
||||||
try:
|
|
||||||
# 更新会话状态
|
|
||||||
success = self.db_manager.update_session_status(db_updates['session_id'], db_updates['status'])
|
|
||||||
self.logger.info(f'会话状态已更新为: {db_updates["status"]} - 会话ID: {db_updates["session_id"]}')
|
|
||||||
except Exception as db_error:
|
|
||||||
self.logger.error(f'处理停止录制的数据库更新失败: {db_error}')
|
|
||||||
success = False
|
|
||||||
else:
|
|
||||||
# 如果录制管理器没有返回数据库更新信息,则手动更新
|
|
||||||
success = self.db_manager.update_session_status(session_id, 'recorded')
|
|
||||||
|
|
||||||
except Exception as rec_e:
|
except Exception as rec_e:
|
||||||
self.logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
|
self.logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
|
||||||
# 即使录制停止失败,也尝试更新数据库状态
|
raise
|
||||||
success = self.db_manager.update_session_status(session_id, 'recorded')
|
return jsonify({'success': True,'msg': '停止录制成功'})
|
||||||
raise
|
|
||||||
|
|
||||||
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:
|
except Exception as e:
|
||||||
self.logger.error(f'停止检测失败: {e}', exc_info=True)
|
self.logger.error(f'停止检测失败: {e}', exc_info=True)
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
@ -1475,8 +1443,8 @@ class AppServer:
|
|||||||
self.logger.error(f'保存会话信息失败: {e}')
|
self.logger.error(f'保存会话信息失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@self.app.route('/api/detection/<session_id>/collect', methods=['POST'])
|
@self.app.route('/api/detection/<session_id>/save-data', methods=['POST'])
|
||||||
def collect_detection_data(session_id):
|
def save_detection_data(session_id):
|
||||||
"""采集检测数据"""
|
"""采集检测数据"""
|
||||||
try:
|
try:
|
||||||
if not self.db_manager:
|
if not self.db_manager:
|
||||||
@ -1506,8 +1474,8 @@ class AppServer:
|
|||||||
'error': '无法获取患者ID'
|
'error': '无法获取患者ID'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 调用录制管理器采集数据
|
# 调用录制管理器保存检测截图到文件
|
||||||
collected_data = self.recording_manager.collect_detection_data(
|
collected_data = self.recording_manager.save_detection_images(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
patient_id=patient_id,
|
patient_id=patient_id,
|
||||||
detection_data=data
|
detection_data=data
|
||||||
@ -1542,16 +1510,7 @@ class AppServer:
|
|||||||
|
|
||||||
sessions = self.db_manager.get_detection_sessions(page, size, patient_id)
|
sessions = self.db_manager.get_detection_sessions(page, size, patient_id)
|
||||||
total = self.db_manager.get_sessions_count(patient_id)
|
total = self.db_manager.get_sessions_count(patient_id)
|
||||||
|
|
||||||
# 为每个会话补充最新的检测数据
|
|
||||||
for session in sessions:
|
|
||||||
session_id = session.get('id')
|
|
||||||
if session_id:
|
|
||||||
latest_data = self.db_manager.get_latest_detection_data(session_id, 5)
|
|
||||||
session['latest_detection_data'] = latest_data
|
|
||||||
else:
|
|
||||||
session['latest_detection_data'] = []
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'data': {
|
'data': {
|
||||||
@ -1583,57 +1542,74 @@ class AppServer:
|
|||||||
self.logger.error(f'获取会话数据失败: {e}')
|
self.logger.error(f'获取会话数据失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@self.app.route('/api/detection/data/details', methods=['GET'])
|
||||||
@self.app.route('/api/detection/data/<session_id>/latest', methods=['GET'])
|
def get_detection_data_by_ids():
|
||||||
def get_latest_detection_data(session_id):
|
"""根据多个主键ID查询检测数据详情,ids为逗号分隔"""
|
||||||
"""获取最新的检测数据"""
|
|
||||||
try:
|
try:
|
||||||
limit = int(flask_request.args.get('limit', 5))
|
ids_param = flask_request.args.get('ids')
|
||||||
data = self.db_manager.get_latest_detection_data(session_id, limit)
|
if not ids_param:
|
||||||
|
return jsonify({'success': False, 'error': '缺少ids参数'}), 400
|
||||||
return jsonify({
|
ids = [i.strip() for i in ids_param.split(',') if i.strip()]
|
||||||
'success': True,
|
data_list = self.db_manager.get_detection_data_by_ids(ids)
|
||||||
'data': data
|
return jsonify({'success': True, 'data': data_list})
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'获取最新检测数据失败: {e}')
|
self.logger.error(f'批量获取检测数据失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
@self.app.route('/api/detection/data/detail/<data_id>', methods=['GET'])
|
|
||||||
def get_detection_data_by_id(data_id):
|
|
||||||
"""根据主键ID查询检测数据详情"""
|
|
||||||
try:
|
|
||||||
data = self.db_manager.get_detection_data_by_id(data_id)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({'success': False, 'error': '检测数据不存在'}), 404
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f'获取检测数据详情失败: {e}')
|
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@self.app.route('/api/detection/data/<data_id>', methods=['DELETE'])
|
@self.app.route('/api/detection/data/<data_id>', methods=['DELETE'])
|
||||||
def delete_detection_data(data_id):
|
def delete_detection_data(data_id):
|
||||||
"""删除检测数据记录"""
|
"""删除检测数据记录(支持单个或多个ID,多个用逗号分隔)"""
|
||||||
try:
|
try:
|
||||||
self.db_manager.delete_detection_data(data_id)
|
if not data_id:
|
||||||
|
return jsonify({'success': False, 'error': '未提供检测数据ID'}), 400
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
# 支持批量:逗号分隔
|
||||||
'message': '检测数据删除成功'
|
ids = [i.strip() for i in str(data_id).split(',') if i.strip()]
|
||||||
})
|
payload = ids if len(ids) > 1 else (ids[0] if ids else data_id)
|
||||||
|
|
||||||
|
success = self.db_manager.delete_detection_data(payload)
|
||||||
|
if success:
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': '检测数据删除成功',
|
||||||
|
'deleted_ids': ids
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': '检测数据删除失败'}), 500
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 404
|
return jsonify({'success': False, 'error': str(e)}), 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'删除检测数据失败: {e}')
|
self.logger.error(f'删除检测数据失败: {e}')
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
@self.app.route('/api/detection/video/<video_id>', methods=['DELETE'])
|
||||||
|
def delete_detection_video(video_id):
|
||||||
|
"""删除检测视频记录(支持单个或多个ID,多个用逗号分隔)"""
|
||||||
|
try:
|
||||||
|
if not video_id:
|
||||||
|
return jsonify({'success': False, 'error': '未提供检测视频ID'}), 400
|
||||||
|
|
||||||
|
# 支持批量:逗号分隔
|
||||||
|
ids = [i.strip() for i in str(video_id).split(',') if i.strip()]
|
||||||
|
payload = ids if len(ids) > 1 else (ids[0] if ids else video_id)
|
||||||
|
|
||||||
|
success = self.db_manager.delete_detection_video(payload)
|
||||||
|
if success:
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': '检测视频删除成功',
|
||||||
|
'deleted_ids': ids
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': '检测视频删除失败'}), 500
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 404
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'删除检测视频失败: {e}')
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@self.app.route('/api/detection/sessions/<session_id>', methods=['DELETE'])
|
@self.app.route('/api/detection/sessions/<session_id>', methods=['DELETE'])
|
||||||
def delete_detection_session(session_id):
|
def delete_detection_session(session_id):
|
||||||
@ -1687,7 +1663,7 @@ class AppServer:
|
|||||||
def handle_subscribe_device(data):
|
def handle_subscribe_device(data):
|
||||||
"""订阅特定设备数据"""
|
"""订阅特定设备数据"""
|
||||||
device_type = data.get('device_type')
|
device_type = data.get('device_type')
|
||||||
if device_type in ['camera', 'femtobolt', 'imu', 'pressure']:
|
if device_type in ['camera1', 'camera2', 'femtobolt', 'imu', 'pressure']:
|
||||||
self.logger.info(f'客户端订阅{device_type}设备数据')
|
self.logger.info(f'客户端订阅{device_type}设备数据')
|
||||||
emit('subscription_status', {
|
emit('subscription_status', {
|
||||||
'device_type': device_type,
|
'device_type': device_type,
|
||||||
@ -2010,7 +1986,7 @@ def main():
|
|||||||
"""主函数"""
|
"""主函数"""
|
||||||
# 解析命令行参数
|
# 解析命令行参数
|
||||||
parser = argparse.ArgumentParser(description='Body Balance Evaluation System Backend')
|
parser = argparse.ArgumentParser(description='Body Balance Evaluation System Backend')
|
||||||
parser.add_argument('--host', default='localhost', help='Host address to bind to')
|
parser.add_argument('--host', default='0.0.0.0', help='Host address to bind to')
|
||||||
parser.add_argument('--port', type=int, default=5000, help='Port number to bind to')
|
parser.add_argument('--port', type=int, default=5000, help='Port number to bind to')
|
||||||
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -2021,4 +1997,4 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -222,16 +222,24 @@ class DataValidator:
|
|||||||
|
|
||||||
# 性别验证
|
# 性别验证
|
||||||
if data.get('gender'):
|
if data.get('gender'):
|
||||||
if data['gender'] not in ['male', 'female', 'other']:
|
# 支持中文和英文性别值
|
||||||
errors.append('性别值无效')
|
gender_map = {'男': 'male', '女': 'female', 'male': 'male', 'female': 'female'}
|
||||||
|
gender_value = data['gender'].strip()
|
||||||
|
if gender_value in gender_map:
|
||||||
|
data['gender'] = gender_map[gender_value]
|
||||||
|
else:
|
||||||
|
errors.append('性别值无效,应为:男、女、male、female')
|
||||||
|
|
||||||
# 出生日期验证
|
# 出生日期验证
|
||||||
if data.get('birth_date'):
|
if data.get('birth_date'):
|
||||||
try:
|
try:
|
||||||
birth_date = datetime.fromisoformat(data['birth_date'].replace('Z', '+00:00'))
|
birth_dt = datetime.fromisoformat(data['birth_date'].replace('Z', '+00:00'))
|
||||||
if birth_date > datetime.now():
|
birth = birth_dt.date()
|
||||||
|
today = datetime.now().date()
|
||||||
|
lower = datetime(1900, 1, 1).date()
|
||||||
|
if birth > today:
|
||||||
errors.append('出生日期不能是未来时间')
|
errors.append('出生日期不能是未来时间')
|
||||||
if birth_date < datetime(1900, 1, 1):
|
if birth < lower:
|
||||||
errors.append('出生日期过早')
|
errors.append('出生日期过早')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
errors.append('出生日期格式无效')
|
errors.append('出生日期格式无效')
|
||||||
@ -518,4 +526,4 @@ class ResponseFormatter:
|
|||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
# 性能监控实例
|
# 性能监控实例
|
||||||
performance_monitor = PerformanceMonitor()
|
performance_monitor = PerformanceMonitor()
|
||||||
|
|||||||
259
document/Web接口调用说明.md
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
# Web 接口调用说明
|
||||||
|
|
||||||
|
本文档基于 `backend/main.py` 中注册的路由,整理对外提供的 Web API 调用方式、参数与返回示例,便于前端或第三方系统集成。
|
||||||
|
|
||||||
|
## 总览
|
||||||
|
|
||||||
|
- 基础与健康检查
|
||||||
|
- `GET /health`
|
||||||
|
- `GET /api/health`
|
||||||
|
- 授权相关
|
||||||
|
- `GET /api/license/info`
|
||||||
|
- `POST /api/license/activation-request`
|
||||||
|
- `POST /api/license/verify`
|
||||||
|
- `POST /api/license/activate-package`
|
||||||
|
- 认证与用户
|
||||||
|
- `POST /api/auth/login`
|
||||||
|
- `POST /api/auth/register`
|
||||||
|
- `POST /api/auth/logout`
|
||||||
|
- `GET /api/auth/verify`
|
||||||
|
- `POST /api/auth/forgot-password`
|
||||||
|
- `GET /api/users`
|
||||||
|
- `POST /api/users/<user_id>/approve`
|
||||||
|
- `DELETE /api/users/<user_id>`
|
||||||
|
- 设备与配置
|
||||||
|
- `GET /api/devices/status`
|
||||||
|
- `POST /api/devices/refresh`
|
||||||
|
- `GET /api/config/devices`
|
||||||
|
- `POST /api/config/devices/all`
|
||||||
|
- `POST /api/devices/calibrate`
|
||||||
|
- `POST /api/devices/calibrate/imu`
|
||||||
|
- 患者管理
|
||||||
|
- `GET /api/patients`
|
||||||
|
- `GET|PUT|DELETE /api/patients/<patient_id>`
|
||||||
|
- 检测流程
|
||||||
|
- `POST /api/detection/start`
|
||||||
|
- `GET /api/detection/<session_id>/has_data`
|
||||||
|
- `POST /api/detection/<session_id>/start_record`
|
||||||
|
- `POST /api/detection/<session_id>/stop_record`
|
||||||
|
- `POST /api/detection/<session_id>/save-data`
|
||||||
|
- `POST /api/detection/<session_id>/save-info`
|
||||||
|
- `GET /api/detection/<session_id>/status`
|
||||||
|
- `POST /api/detection/<session_id>/stop`
|
||||||
|
- 历史与数据查询
|
||||||
|
- `GET /api/history/sessions`
|
||||||
|
- `GET /api/history/sessions/<session_id>`
|
||||||
|
- `GET /api/detection/data/details?ids=<id1,id2,...>`
|
||||||
|
- 删除操作
|
||||||
|
- `DELETE /api/detection/data/<data_id[,data_id2,...]>`
|
||||||
|
- `DELETE /api/detection/video/<video_id[,video_id2,...]>`
|
||||||
|
- `DELETE /api/detection/sessions/<session_id>`
|
||||||
|
|
||||||
|
## 统一响应约定
|
||||||
|
|
||||||
|
- 成功:`{ "success": true, ... }`
|
||||||
|
- 失败:`{ "success": false, "error": "错误信息" }`
|
||||||
|
- 时间字段统一使用 ISO 文本或 `YYYY-MM-DD HH:mm:ss` 字符串。
|
||||||
|
|
||||||
|
## 基础与授权
|
||||||
|
|
||||||
|
### GET /health | GET /api/health
|
||||||
|
- 功能:健康检查与服务存活状态。
|
||||||
|
- 示例响应:
|
||||||
|
```json
|
||||||
|
{ "status": "healthy", "timestamp": "2024-01-01T12:00:00", "version": "1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/license/info
|
||||||
|
- 功能:获取授权状态与基础信息。
|
||||||
|
- 响应字段:`valid`, `message`, `license_type`, `license_id`, `expires_at`, `features`, `machine_id`。
|
||||||
|
|
||||||
|
### POST /api/license/activation-request
|
||||||
|
- 功能:生成离线激活请求文件。
|
||||||
|
- Body:`{ "company_name": "公司名", "contact_info": "联系方式" }`
|
||||||
|
- 返回:`request_file` 路径与 `content` 文本。
|
||||||
|
|
||||||
|
### POST /api/license/verify
|
||||||
|
- 功能:上传并验证授权文件。
|
||||||
|
- Form-Data:`license_file`(文件)。
|
||||||
|
- 返回:授权验证结果与解析出的授权信息。
|
||||||
|
|
||||||
|
### POST /api/license/activate-package
|
||||||
|
- 功能:上传激活包进行离线激活。
|
||||||
|
- 细节见服务端实现,返回激活状态与信息。
|
||||||
|
|
||||||
|
## 认证与用户
|
||||||
|
|
||||||
|
### POST /api/auth/login
|
||||||
|
- 功能:用户登录。
|
||||||
|
- Body:`{ "username": "string", "password": "string" }`
|
||||||
|
|
||||||
|
### POST /api/auth/register
|
||||||
|
- 功能:用户注册。
|
||||||
|
- Body:包含用户名、密码、手机号等注册信息。
|
||||||
|
|
||||||
|
### POST /api/auth/logout
|
||||||
|
- 功能:登出当前会话。
|
||||||
|
|
||||||
|
### GET /api/auth/verify
|
||||||
|
- 功能:登录状态校验。
|
||||||
|
|
||||||
|
### POST /api/auth/forgot-password
|
||||||
|
- 功能:忘记密码,根据用户名和手机号找回。
|
||||||
|
- Body:`{ "username": "string", "phone": "string" }`
|
||||||
|
|
||||||
|
### GET /api/users
|
||||||
|
- 功能:获取用户列表。
|
||||||
|
- Query 可选:分页参数。
|
||||||
|
|
||||||
|
### POST /api/users/<user_id>/approve
|
||||||
|
- 功能:批准用户,使其 `is_active = 1`。
|
||||||
|
- Body 可选:`{ "approved_by": 123 }`
|
||||||
|
|
||||||
|
### DELETE /api/users/<user_id>
|
||||||
|
- 功能:删除用户。
|
||||||
|
|
||||||
|
## 患者管理
|
||||||
|
|
||||||
|
### GET /api/patients
|
||||||
|
- 功能:获取患者列表。
|
||||||
|
- Query 可选:分页筛选。
|
||||||
|
|
||||||
|
### POST /api/patients
|
||||||
|
- 功能:创建新患者。
|
||||||
|
- Body:患者基本信息字段(姓名、性别、出生日期等)。
|
||||||
|
- 必填字段:`name`(姓名)、`gender`(性别)、`birth_date`(出生日期)
|
||||||
|
- 可选字段:`phone`(电话)、`email`(邮箱)、`height`(身高)、`weight`(体重)、`nationality`(民族)、`residence`(居住地)、`occupation`(职业)、`workplace`(工作单位)、`idcode`(身份证号)、`medical_history`(病史)、`notes`(备注)
|
||||||
|
|
||||||
|
### GET|PUT|DELETE /api/patients/<patient_id>
|
||||||
|
- 功能:获取/更新/删除单个患者。
|
||||||
|
- PUT Body:患者基本信息字段(姓名、性别、出生日期、联系方式等)。
|
||||||
|
|
||||||
|
## 设备与配置
|
||||||
|
|
||||||
|
### GET /api/devices/status
|
||||||
|
- 功能:获取各设备连接与工作状态。
|
||||||
|
|
||||||
|
### POST /api/devices/refresh
|
||||||
|
- 功能:刷新设备状态(重扫/重连)。
|
||||||
|
|
||||||
|
### GET /api/config/devices
|
||||||
|
- 功能:获取当前设备配置。
|
||||||
|
|
||||||
|
### POST /api/config/devices/all
|
||||||
|
- 功能:批量设置设备配置。
|
||||||
|
- Body:设备配置对象集合。
|
||||||
|
|
||||||
|
### POST /api/devices/calibrate
|
||||||
|
- 功能:触发设备标定(通用)。
|
||||||
|
|
||||||
|
### POST /api/devices/calibrate/imu
|
||||||
|
- 功能:仅触发 IMU 设备标定。
|
||||||
|
|
||||||
|
## 检测流程
|
||||||
|
|
||||||
|
### POST /api/detection/start
|
||||||
|
- 功能:创建检测会话并启动设备连接监控。
|
||||||
|
- Body:`{ "patient_id": "string", "creator_id": "string" }`
|
||||||
|
- 返回:`session_id`。
|
||||||
|
|
||||||
|
### GET /api/detection/<session_id>/has_data
|
||||||
|
- 功能:会话是否存在检测数据,用于判断单次检测是否有效。
|
||||||
|
- 返回:`{ "has_data": true|false }`
|
||||||
|
### GET /api/detection/<session_id>/status
|
||||||
|
- 功能:获取会话最新状态与聚合数据(源自 `get_session_data`)。
|
||||||
|
|
||||||
|
### POST /api/detection/<session_id>/stop
|
||||||
|
- 功能:结束检测并写入总结信息(自动计算时长与结束时间)。
|
||||||
|
- Body 可选:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnosis_info": "string",
|
||||||
|
"treatment_info": "string",
|
||||||
|
"remark_info": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 说明:服务端已统一走 `update_session_endcheck` 数据库流程。
|
||||||
|
### POST /api/detection/<session_id>/start_record
|
||||||
|
- 功能:开始同步录制(屏幕/相机/设备流)。
|
||||||
|
- Body 示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"patient_id": "p001",
|
||||||
|
"screen_location": [0,0,1920,1080],
|
||||||
|
"femtobolt_location": [0,0,640,480],
|
||||||
|
"camera1_location": [0,0,640,480],
|
||||||
|
"camera2_location": [0,0,640,480]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 返回:`recording` 含 `database_updates.video_paths`;服务端会自动调用 `save_detection_video` 记录视频路径(`screen_video_path`, `femtobolt_video_path`, `camera1_video_path`, `camera2_video_path`)。
|
||||||
|
|
||||||
|
### POST /api/detection/<session_id>/stop_record
|
||||||
|
- 功能:停止同步录制。
|
||||||
|
|
||||||
|
### POST /api/detection/<session_id>/save-data
|
||||||
|
- 功能:采集检测数据与截图并持久化。
|
||||||
|
- Body:检测数据载荷(结构由实际采集模块决定)。
|
||||||
|
- 返回:`timestamp` 与是否采集成功。
|
||||||
|
|
||||||
|
### POST /api/detection/<session_id>/save-info
|
||||||
|
- 功能:保存会话信息(诊断、处理、建议、状态)。
|
||||||
|
- Body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnosis_info": "string",
|
||||||
|
"treatment_info": "string",
|
||||||
|
"remark_info": "string",
|
||||||
|
"status": "completed|running|..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 历史与数据查询
|
||||||
|
|
||||||
|
### GET /api/history/sessions
|
||||||
|
- 功能:分页获取检测会话历史。
|
||||||
|
- Query:`page`(默认 1)、`size`(默认 10)、`patient_id`(可选)。
|
||||||
|
- 返回:`sessions` 与 `total`。
|
||||||
|
|
||||||
|
### GET /api/history/sessions/<session_id>
|
||||||
|
- 功能:获取会话详细数据(聚合会话、检测数据、视频)。
|
||||||
|
|
||||||
|
### GET /api/detection/data/details?ids=<id1,id2,...>
|
||||||
|
- 功能:根据多个逗号分隔的 ID 批量获取检测数据详情。
|
||||||
|
- Query:`ids` 逗号分隔字符串。
|
||||||
|
|
||||||
|
## 删除操作
|
||||||
|
|
||||||
|
### DELETE /api/detection/data/<data_id[,data_id2,...]>
|
||||||
|
- 功能:删除检测数据记录,支持多个 ID 逗号分隔。
|
||||||
|
- 返回:`deleted_ids` 列表。
|
||||||
|
|
||||||
|
### DELETE /api/detection/video/<video_id[,video_id2,...]>
|
||||||
|
- 功能:删除检测视频记录,支持多个 ID 逗号分隔。
|
||||||
|
- 返回:`deleted_ids` 列表。
|
||||||
|
|
||||||
|
### DELETE /api/detection/sessions/<session_id>
|
||||||
|
- 功能:删除检测会话,同时清理关联的检测数据与视频记录。
|
||||||
|
|
||||||
|
## 错误码与常见失败
|
||||||
|
|
||||||
|
- 400:缺少必要参数或请求体格式错误。
|
||||||
|
- 403:授权校验失败或未授权功能。
|
||||||
|
- 404:会话或资源不存在。
|
||||||
|
- 500:服务内部错误(设备不可用、数据库失败等)。
|
||||||
|
|
||||||
|
## 典型调用序列(示例)
|
||||||
|
|
||||||
|
1. 创建会话:`POST /api/detection/start`
|
||||||
|
2. 开始录制:`POST /api/detection/<session_id>/start_record`
|
||||||
|
3. 采集数据:多次 `POST /api/detection/<session_id>/save-data`
|
||||||
|
4. 停止录制:`POST /api/detection/<session_id>/stop_record`
|
||||||
|
5. 保存诊断建议:`POST /api/detection/<session_id>/save-info`
|
||||||
|
6. 结束检测:`POST /api/detection/<session_id>/stop`
|
||||||
|
7. 校验有效性:`GET /api/detection/<session_id>/has_data`
|
||||||
|
8. 查询历史:`GET /api/history/sessions`
|
||||||
|
9. 查看详情:`GET /api/history/sessions/<session_id>`
|
||||||
|
|
||||||
|
> 注:具体字段与数据结构以采集模块与数据库模型为准,本文档以主干流程为纲要,建议结合实际返回进行前端适配与校验。
|
||||||
6
frontend/src/renderer/src/assets/new/archive.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1881 -82 )">
|
||||||
|
<path d="M 15.9203959888077 14.5936879851829 L 15.9203959888077 9.2868945127217 C 15.9221961012911 9.26482295156643 15.9221961012911 9.24264146272398 15.9203959888077 9.22056990156871 C 15.9220017133507 9.19738041784726 15.9220017133507 9.17410764417035 15.9203959888077 9.15091816044888 L 14.0364720927064 0.995026809521264 C 13.8850151331878 0.4084253440992 13.3554163441829 -0.00105990688953739 12.7495786070392 0 L 3.17081738213301 0 C 2.55696540559302 5.80938669827447E-05 2.02331072533525 0.421222612478488 1.88059678203786 1.01825442140363 L 0.0165889307122598 9.13765634456156 C 0.0149832061692905 9.16084582828303 0.0149832061692905 9.18411860195991 0.0165889307122598 9.20730808568138 C 0.00842320058131918 9.23321838391806 0.00286597143963263 9.25987953700835 0 9.2868945127217 L 0 14.5936724379193 C 0 15.3264166600063 0.593981351320735 15.9203980098591 1.32669448069123 15.9203980098591 C 1.32669448069123 15.9203980098591 1.32669448105548 15.9203980098591 1.32669448105548 16 L 14.593685960853 16 C 14.5936911432079 15.9203980099199 14.5936963258525 15.9203980099502 14.5937015082077 15.9203980099502 C 15.3264146375782 15.9203980099502 15.9203959888987 15.3264166600974 15.9203959888987 14.5937035325377 Z M 1.49584869436012 8.62355505542087 L 3.17081738213301 1.32669653817167 L 12.746267039875 1.32669653817167 L 14.4245317475484 8.62355505542087 L 11.6086117071895 8.62355505542087 C 11.2422630001633 8.623563650138 10.9452808429667 8.92054580660088 10.9452722482495 9.2868945127217 L 10.9452722482495 10.9452742815956 L 4.97512374092253 10.9452742815956 L 4.97512374092253 9.2868945127217 C 4.97511514596226 8.92053973513232 4.67812351331463 8.62355506390642 4.31176873471895 8.62355505542087 L 1.49584869436012 8.62355505542087 Z M 3.9800990052488 7.29684501519738 C 3.61374422058179 7.2968450152982 3.31675257845859 6.99986034155748 3.31674398349787 6.63350555789654 C 3.31674398349787 6.26714469656511 3.61373814301214 5.9701505377848 3.9800990052488 5.9701505377848 L 11.9402969994705 5.9701505377848 C 12.3053994333072 5.97191897464336 12.6004389839914 6.2683910607167 12.6004389839914 6.6334977764911 C 12.6004389839914 6.99860449226549 12.3053994333072 7.29507657833882 11.9402969994705 7.29684501519738 L 3.9800990052488 7.29684501519738 Z M 4.97512374092253 4.31177078445816 C 4.60876895625574 4.31177077597241 4.31177732109313 4.01478609527105 4.31176873471895 3.64843131161005 C 4.31176874330549 3.2820704624218 4.60876289082921 2.9850763156321 4.97512374092253 2.98507630704557 L 10.9452722482495 2.98507630704557 C 11.3103746820859 2.98684474390417 11.6054142327703 3.28331682997749 11.6054142327703 3.64842354575187 C 11.6054142327703 4.01353026152624 11.3103746820859 4.31000234759958 10.9452722482495 4.31177078445816 L 4.97512374092253 4.31177078445816 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1881 82 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/bi.png
Normal file
|
After Width: | Height: | Size: 492 B |
BIN
frontend/src/renderer/src/assets/new/bi2.png
Normal file
|
After Width: | Height: | Size: 407 B |
BIN
frontend/src/renderer/src/assets/new/close.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
frontend/src/renderer/src/assets/new/conduct.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
7
frontend/src/renderer/src/assets/new/conduct.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1015 -82 )">
|
||||||
|
<path d="M 1023 82 C 1027.48 82 1031 85.52 1031 90 C 1031 94.48 1027.48 98 1023 98 C 1018.52 98 1015 94.48 1015 90 C 1015 85.52 1018.52 82 1023 82 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" fill-opacity="0" />
|
||||||
|
<path d="M 1023 82.5 C 1027.2 82.5 1030.5 85.8 1030.5 90 C 1030.5 94.2 1027.2 97.5 1023 97.5 C 1018.8 97.5 1015.5 94.2 1015.5 90 C 1015.5 85.8 1018.8 82.5 1023 82.5 Z " stroke-width="1" stroke="#ff3300" fill="none" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/del.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/src/renderer/src/assets/new/del2.png
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
frontend/src/renderer/src/assets/new/del3.png
Normal file
|
After Width: | Height: | Size: 409 B |
6
frontend/src/renderer/src/assets/new/endvideo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="14px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -8 -9 )">
|
||||||
|
<path d="M 18 3.09091796875001 C 18 2.63636718750001 17.7545390624998 2.22726562499999 17.3863476562499 2.00000000000001 C 17.0181738281251 1.772734375 16.5681738281251 1.8181640625 16.240904296875 2.13636718750003 L 13.5 4.40908203124998 L 13.5 2.59091796875002 C 13.5 1.18181640624999 12.4772695312499 0 11.168173828125 0 L 2.33182617187504 0 C 1.02273046875007 0.0454492187499937 0 1.18183593750001 0 2.63636718750001 L 0 11.40908203125 C 0 12.81818359375 1.02273046875007 14 2.33182617187504 14 L 11.168173828125 14 C 12.436365234375 14 13.5 12.8636328125 13.5 11.40908203125 L 13.5 9.59091796875002 L 16.240904296875 11.90908203125 C 16.4454609375 12.09091796875 16.6499999999999 12.1363671875 16.8954609375 12.1363671875 C 17.059095703125 12.1363671875 17.2227304687501 12.09091796875 17.386365234375 12 C 17.7545390624998 11.772734375 18 11.3636328125 18 10.90908203125 L 18 3.09091796875001 Z M 12.1909218750002 2.63636718750001 L 12.1909218750002 11.3636328125 C 12.1909218750002 12 11.7409218749999 12.5 11.1681914062501 12.5 L 2.33184374999996 12.5 C 1.75911328124994 12.5 1.30911328125012 12 1.30911328125012 11.3636328125 L 1.30911328125012 2.63636718750001 C 1.30911328125012 2.00000000000001 1.75911328124994 1.49999999999999 2.33184374999996 1.49999999999999 L 11.1681914062501 1.49999999999999 C 11.7409218749999 1.49999999999999 12.1909218750002 2.00000000000001 12.1909218750002 2.63636718750001 Z M 16.6909218750002 10.45455078125 L 13.5 7.77271484374999 L 13.5 6.27271484375001 L 16.7318261718749 3.59091796874999 C 16.690904296875 3.54544921874999 16.690904296875 10.45455078125 16.690904296875 10.45455078125 Z M 4.5 9.33333333333333 L 4.5 4.66666666666667 L 9 4.66666666666667 L 9 9.33333333333333 L 4.5 9.33333333333333 Z " fill-rule="nonzero" fill="#ff3300" stroke="none" transform="matrix(1 0 0 1 8 9 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/file.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
6
frontend/src/renderer/src/assets/new/history2.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -35 -486 )">
|
||||||
|
<path d="M 17.9488636363636 9 C 17.9488636363636 4.05814985795453 13.9418501420455 0.051136363636374 9 0.051136363636374 C 4.05814985795453 0.051136363636374 0.051136363636374 4.05814985795453 0.051136363636374 9 C 0.051136363636374 13.9418501420455 4.05814985795453 17.9488636363636 9 17.9488636363636 C 13.9418501420455 17.9488636363636 17.9488636363636 13.9418501420455 17.9488636363636 9 Z M 14.1136363636364 10.2784090909091 C 14.1136363636364 10.6319691051136 13.8279918323864 10.9176136363636 13.4744318181818 10.9176136363636 L 7.72159090909091 10.9176136363636 C 7.36803089488637 10.9176136363636 7.08238636363637 10.6319691051136 7.08238636363637 10.2784090909091 L 7.08238636363637 4.52556818181819 C 7.08238636363637 4.17200816761363 7.36803089488637 3.88636363636363 7.72159090909091 3.88636363636363 C 8.07515092329547 3.88636363636363 8.36079545454547 4.17200816761363 8.36079545454547 4.52556818181819 L 8.36079545454547 9.63920454545453 L 13.4744318181818 9.63920454545453 C 13.8279918323864 9.63920454545453 14.1136363636364 9.92484907670453 14.1136363636364 10.2784090909091 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 35 486 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/history3.svg
Normal file
6
frontend/src/renderer/src/assets/new/jietu.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="14px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -929 -83 )">
|
||||||
|
<path d="M 15.93125 4.01406249999999 C 15.93125 2.91763139204546 15.0426846590909 2.02904829545454 13.9463423295455 2.02904829545454 L 12.1645063920455 2.02904829545454 C 11.6868607954545 2.02904829545454 11.2775213068181 1.689453125 11.1890802556818 1.21962002840909 L 11.0076349431819 0.245703124999999 C 10.9859019886363 0.127894176136365 10.8836115056819 0.0441406249999972 10.7642045454545 0.0441406249999972 L 5.22031249999998 0.0441406249999972 C 5.1008877840909 0.0441406249999972 4.99850852272732 0.129385653409088 4.9767933238636 0.245703124999999 L 4.79385653409088 1.21803977272727 C 4.70539772727273 1.68796164772727 4.29607599431813 2.0275390625 3.81841264204547 2.0275390625 L 2.0365767045455 2.0275390625 C 0.940234375000045 2.02904829545454 0.0516690340908781 2.91763139204546 0.0516690340908781 4.01406249999999 L 0.0516690340908781 11.9538174715909 C 0.0516690340908781 13.0502485795455 0.94025213068187 13.9388316761364 2.0365767045455 14 L 13.9463423295455 14 C 15.0426846590909 13.9388316761364 15.93125 13.0502485795455 15.93125 11.9538174715909 L 15.93125 4.01406249999999 Z M 2.53284801136363 4.51024502840909 C 2.25836292613633 4.51024502840909 2.0365767045455 4.28854758522728 2.0365767045455 4.01406249999999 C 2.0365767045455 3.87761008522727 2.09245383522727 3.75349786931818 2.18242187500005 3.66354758522728 C 2.27230113636358 3.57357954545454 2.3964133522727 3.5177734375 2.53284801136363 3.5177734375 L 3.52530184659088 3.5177734375 C 3.79978693181818 3.5177734375 4.02157315340912 3.73955965909091 4.02157315340912 4.01404474431818 C 4.02157315340912 4.1504971590909 3.96576704545453 4.27450284090909 3.87579900568187 4.36447088068182 C 3.78584872159092 4.45443892045455 3.6617365056818 4.51022727272728 3.52528409090905 4.51022727272728 L 2.53284801136363 4.51022727272728 Z M 12.2094815340909 8.23203125000001 C 12.2094815340909 10.5612571022727 10.3207208806818 12.4500887784091 7.9915127840909 12.4500887784091 C 5.66226917613642 12.4500887784091 3.77345525568182 10.5612571022727 3.77345525568182 8.23203125000001 C 3.77345525568182 5.90280539772728 5.66228693181813 4.01406249999999 7.9915127840909 4.01406249999999 C 10.3207386363637 4.01406249999999 12.2094815340909 5.90280539772728 12.2094815340909 8.23203125000001 Z M 10.2726207386364 5.95090553977273 C 10.88203125 6.56036931818183 11.2170099431818 7.36988636363637 11.2170099431818 8.23203125000001 C 11.2170099431818 9.09426491477274 10.88203125 9.90379971590909 10.2726207386364 10.5131747159091 C 9.6631569602273 11.12265625 8.85363991477277 11.4576349431818 7.99149502840908 11.4576349431818 C 7.12926136363637 11.4576349431818 6.31972656250002 11.12265625 5.71035156250002 10.5131747159091 C 5.10087002840908 9.90379971590909 4.76589133522725 9.09426491477274 4.76589133522725 8.23203125000001 C 4.76589133522725 7.36990411931819 5.10087002840908 6.56036931818183 5.71035156250002 5.95090553977273 C 6.31974431818185 5.3414950284091 7.12926136363637 5.00651633522727 7.99149502840908 5.00651633522727 C 8.85363991477277 5.00651633522727 9.6631569602273 5.3414950284091 10.2726207386364 5.95090553977273 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 929 83 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/leftjiantou.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
frontend/src/renderer/src/assets/new/leftjiantou2.png
Normal file
|
After Width: | Height: | Size: 502 B |
BIN
frontend/src/renderer/src/assets/new/newbg.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
6
frontend/src/renderer/src/assets/new/refresh.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -373 -138 )">
|
||||||
|
<path d="M 8.07210783203124 6.46195381835938 L 10.2949266738281 4.23849897460937 C 9.45069847884821 3.20458041024776 8.29262410526201 2.47383480857755 6.99599638281251 2.15687204687501 C 4.48952168164061 1.65570431250001 3.16091835937499 2.15687204687501 0 4.30909493164063 C 1.28411899004271 1.62462621176789 4.02088248674812 -0.0594785244032039 6.99599638281251 0.00401316015626207 C 9.01198728514521 -0.0631719713191501 10.9636875416396 0.718523317659617 12.3759175996094 2.15878003906249 L 13.991992765625 0.542704873046887 L 13.991992765625 6.46195381835938 L 8.07210783203124 6.46195381835938 Z M 5.91988493359372 7.53806526757813 L 3.6970660917969 9.76088410937501 C 4.5414496054143 10.7946271618647 5.69946022307948 11.5253325335117 6.99599638281251 11.8425110371094 C 9.50247108398435 12.3443147734375 10.83107440625 11.8425110371094 13.991992765625 9.69092415429688 C 12.7078737755823 12.3753928741696 9.97111027887684 14.0594976103407 6.99599638281251 13.9960059257812 C 4.9802932407564 14.0629719430221 3.02883496413972 13.28183496075 1.61607516601561 11.8425110371094 L 0 13.4573141992188 L 0 7.53806526757813 L 5.91988493359372 7.53806526757813 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 373 138 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/rightjiantou.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
frontend/src/renderer/src/assets/new/rightjiantou2.png
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
frontend/src/renderer/src/assets/new/save.png
Normal file
|
After Width: | Height: | Size: 361 B |
6
frontend/src/renderer/src/assets/new/settings.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1839 -82 )">
|
||||||
|
<path d="M 15.3518281249999 10.057 L 13.6746718750001 8.74328125 C 13.7077187499999 8.4886875 13.7290937499999 8.234109375 13.7290937499999 7.96203125 C 13.7290937499999 7.69190624999999 13.703828125 7.43731249999999 13.6746718750001 7.180796875 L 15.35375 5.86707812500001 C 15.505328125 5.74851562500001 15.5461406249999 5.532796875 15.4489687499999 5.35789062500001 L 13.857359375 2.604125 C 13.762125 2.42923437499999 13.5464218750001 2.36509375 13.371515625 2.42923437499999 L 11.3892499999999 3.226015625 C 10.9753125 2.9073125 10.52834375 2.644953125 10.0444375 2.444765625 L 9.74128124999993 0.334281250000004 C 9.71603125000001 0.141874999999999 9.55081249999989 0 9.35065624999993 0 L 6.16546874999995 0 C 5.96723437500009 0 5.80009375000009 0.143812499999996 5.77484374999995 0.334265625 L 5.47167187500008 2.44478125000001 C 4.9858281249999 2.643 4.54079687500007 2.91509375 4.12685937500009 3.226015625 L 2.14457812499995 2.42921875 C 1.95996874999992 2.35731250000001 1.75396875000001 2.42921875 1.65873437499999 2.604140625 L 0.0671250000000327 5.35789062500001 C -0.0378124999999727 5.532796875 0.0127187499999764 5.74851562500001 0.162359375000051 5.86707812500001 L 1.84143749999998 7.180796875 C 1.80840624999996 7.43537499999999 1.78703124999993 7.69773437500001 1.78703124999993 7.962046875 C 1.78703124999993 8.22632812500001 1.81228125000007 8.48675 1.84143749999998 8.74328125 L 0.160421875000111 10.057 C 0.00881249999997635 10.175546875 -0.0319843750000928 10.391265625 0.0651875000000928 10.56615625 L 1.65681249999989 13.3199375 C 1.75203125000007 13.49484375 1.96775000000002 13.55896875 2.14265625000007 13.49484375 L 4.12492187499993 12.69803125 C 4.5388593749999 13.016765625 4.9858281249999 13.279125 5.46973437499992 13.47928125 L 5.77289062499995 15.5898125 C 5.79815624999992 15.78025 5.96335937499998 15.9240625 6.16351562499995 15.9240625 L 9.34870312499993 15.9240625 C 9.54693750000001 15.9240625 9.71407812500001 15.78025 9.73932812499993 15.5898125 L 10.0425 13.479296875 C 10.52834375 13.2810625 10.973375 13.008984375 11.3873281250001 12.698046875 L 13.3695781250001 13.49484375 C 13.5541874999999 13.56675 13.7602031250001 13.49484375 13.85540625 13.3199375 L 15.44703125 10.56615625 C 15.542265625 10.391265625 15.5014531249999 10.175546875 15.3518281249999 10.057 Z M 10.5458437499999 7.963984375 C 10.5458437499999 9.50120312499999 9.2962500000001 10.75078125 7.75903124999991 10.75078125 C 6.22181249999994 10.75078125 4.97221874999991 9.50118749999999 4.97221874999991 7.963984375 C 4.97221874999991 6.42678125 6.22181249999994 5.177171875 7.75903124999991 5.177171875 C 9.2962500000001 5.177171875 10.5458437499999 6.426765625 10.5458437499999 7.963984375 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1839 82 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/startvideo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -10 -7 )">
|
||||||
|
<path d="M 6.85372340217753 8.92622950819672 C 6.93351063622007 9.0218579213627 7.06648935754654 9.0218579213627 7.14627659782247 8.92622950819672 L 9.95212765957447 5.31967213114754 C 10.0452127638797 5.19672131147541 9.9654255298371 5.00546447873976 9.80585106175199 5.00546447873976 L 4.19414893824801 5.00546447873976 C 4.0345744701629 5.00546447873976 3.95478723612035 5.19672131147541 4.04787234042553 5.31967213114754 L 6.85372340217753 8.92622950819672 Z M 10.90908203125 18 C 11.3636328125 18 11.772734375 17.7545390624998 12 17.3863476562499 C 12.227265625 17.0181738281251 12.1818359375 16.5681738281251 11.8636328125 16.240904296875 L 9.59091796875002 13.5 L 11.40908203125 13.5 C 12.81818359375 13.5 14 12.4772695312499 14 11.168173828125 L 14 2.33182617187504 C 13.95455078125 1.02273046875007 12.8181640625 0 11.3636328125 0 L 2.59091796875002 0 C 1.18181640624999 0 0 1.02273046875007 0 2.33182617187504 L 0 11.168173828125 C 0 12.436365234375 1.13636718749999 13.5 2.59091796875002 13.5 L 4.40908203124998 13.5 L 2.09091796874999 16.240904296875 C 1.90908203124999 16.4454609375 1.86363281250003 16.6499999999999 1.86363281250003 16.8954609375 C 1.86363281250003 17.059095703125 1.90908203124999 17.2227304687501 2.00000000000001 17.386365234375 C 2.22726562499999 17.7545390624998 2.63636718750001 18 3.09091796875001 18 L 10.90908203125 18 Z M 11.3636328125 12.1909218750002 L 2.63636718750001 12.1909218750002 C 2.00000000000001 12.1909218750002 1.49999999999999 11.7409218749999 1.49999999999999 11.1681914062501 L 1.49999999999999 2.33184374999996 C 1.49999999999999 1.75911328124994 2.00000000000001 1.30911328125012 2.63636718750001 1.30911328125012 L 11.3636328125 1.30911328125012 C 12 1.30911328125012 12.5 1.75911328124994 12.5 2.33184374999996 L 12.5 11.1681914062501 C 12.5 11.7409218749999 12 12.1909218750002 11.3636328125 12.1909218750002 Z M 3.54544921874999 16.6909218750002 L 6.22728515625001 13.5 L 7.72728515624999 13.5 L 10.40908203125 16.7318261718749 C 10.45455078125 16.690904296875 3.54544921874999 16.690904296875 3.54544921874999 16.690904296875 Z " fill-rule="nonzero" fill="#00cc00" stroke="none" transform="matrix(1 0 0 1 10 7 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/test.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
frontend/src/renderer/src/assets/new/testheader.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
6
frontend/src/renderer/src/assets/new/title1.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -36 -136 )">
|
||||||
|
<path d="M 7.76028865245167 0.0137816773135455 C 6.70033338656769 0.0137816773135455 5.84026964347377 0.873868281681638 5.8402696240504 1.93380070571477 C 5.8402696240504 2.99387023912293 6.70024196089435 3.85381971469268 7.76028865245167 3.85381971469268 C 8.82035818585982 3.85381971469268 9.68030766142956 2.99387023912293 9.68030766142956 1.93380070571477 C 9.68030766142956 0.873754014157456 8.82026676018646 0.0137816967368849 7.76028865245167 0.0137816773135455 Z M 15.4402047177141 5.45976267265193 C 15.4402275789882 4.92979646063536 15.0101614058184 4.50178727771063 14.4802866388985 4.50178727771063 L 1.04026780473065 4.50178727771063 C 0.510301592714082 4.50178727771063 0.0803268646408704 4.93185343145717 0.0803268646408704 5.46172821780041 C 0.0803268646408704 5.99183155861535 0.510301592714082 6.42180628668854 1.04026780473065 6.42180628668854 L 4.97826319708217 6.42180628668854 C 5.20228979627072 6.46182573377071 5.50628670580112 6.5758045688018 5.66634161343232 6.99183764891229 C 5.85229146883633 7.47385366885359 5.76029931370858 8.33586009582181 5.64428634970649 9.06183244345648 L 5.49428772228937 9.91787367058012 C 5.49428772228937 9.92180476087708 5.49223075146754 9.92580441557321 5.49223075146754 9.92980408969268 L 4.27825434219612 16.8197304320615 C 4.18626218706837 17.34181160221 4.53430105965126 17.8397348152624 5.05638222979971 17.9318183960635 C 5.57825768732734 18.023719125518 6.07035284875692 17.6757716786084 6.1622307363605 17.1538047759841 L 7.00227332527626 12.3558599274862 L 7.00227332527626 12.3618022660566 C 7.00227332527626 12.3618022660566 7.25226341721341 11.0617989209254 7.74024463699929 11.0617989209254 L 7.78428660005178 11.0617989209254 C 8.28223267437845 11.0617989209254 8.52228077304904 12.3618022660566 8.52228077304904 12.3618022660566 L 8.52228077304904 12.3578026113605 L 9.36220909444062 17.1537819341333 C 9.45431551709255 17.6757488173342 9.94826193672307 18.0236962642438 10.470228819924 17.9317955347894 C 10.992195703125 17.8396891121374 11.3402345757079 17.3417658796616 11.2462997367058 16.8197075707873 L 10.0302663760359 9.92781568327003 L 10.0302663760359 9.9257587124482 C 10.0302663760359 9.92175905775207 10.0282094052141 9.91775940305594 10.0282094052141 9.91373688708563 L 9.87626807449932 9.05769564053867 C 9.76023226864642 8.33378026372583 9.66826297479282 7.47177383675759 9.85430423644682 6.98973497496546 C 10.0142448765539 6.57383904307666 10.3202301925069 6.45986018862223 10.5422683852728 6.41984074154006 L 14.4802637776243 6.41984074154006 C 15.0102299896409 6.41984074154006 15.4402047177141 5.98975172651934 15.4402047177141 5.45976267265193 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 36 136 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/title2.svg
Normal file
6
frontend/src/renderer/src/assets/new/title3.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="17px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -500 -616 )">
|
||||||
|
<path d="M 2.81716015429686 0 C 4.36078439648429 0 5.61137635546879 1.25053867968745 5.61137635546879 2.79327425976567 C 5.61137635546879 4.33683490429689 4.36078611914064 5.58739077539065 2.81716015429686 5.58739077539065 C 1.27435923632811 5.58739077539065 0.0237346347656171 4.33681773046874 0.0237346347656171 2.79327425976567 C 0.0237346347656171 1.25053866210931 1.27435925390625 0 2.81716015429686 0 Z M 7.98573373242186 0 C 8.75754585351558 0 9.3828504375 0.625277074218729 9.3828504375 1.39705825781255 C 9.3828504375 2.16880505859376 8.75754587109373 2.79409932421868 7.98573373242186 2.79409932421868 C 7.21392159375 2.79409932421868 6.58863421874997 2.16882225000006 6.58863421874997 1.39705825781255 C 6.58863421874997 0.625277074218729 7.21392161132815 0 7.98573373242186 0 Z M 12.0362949492187 0.348641050781225 C 12.8081087929688 0.348625582031218 13.4333944628906 0.973919830078103 13.4333944628906 1.74569930859377 C 13.4333944628906 2.51748049218747 12.8081070703125 3.14275756640632 12.0362949492187 3.14275756640632 C 11.264482828125 3.14275756640632 10.6391782441406 2.51748049218747 10.6391782441406 1.74569930859377 C 10.6391782441406 0.973918125000068 11.2644828105468 0.348641050781225 12.0362949492187 0.348641050781225 Z M 15.3191332089845 2.16473306835928 C 16.09092815625 2.16473306835928 16.7162499140625 2.79001186523431 16.7162499140625 3.56179132617183 C 16.7162499140625 4.33357250976565 16.0909453476563 4.95884958398437 15.3191332089845 4.95884958398437 C 14.5473210878906 4.95884958398437 13.9220336953125 4.33357250976565 13.9220336953125 3.56179132617183 C 13.9220336953125 2.79001014257813 14.5473210878906 2.16473306835928 15.3191332089845 2.16473306835928 Z M 7.56585943945311 11.6633984765625 C 6.88489969921869 10.7729433281251 3.48011648437495 10.5110448574219 4.37059395703125 7.15877815429678 C 4.37059395703125 7.15877815429678 5.26107144726564 4.69695111328122 8.66587013085939 4.27791735351559 C 8.66587013085939 4.27791735351559 13.9220336953125 3.91125744140618 14.8272487382812 9.09351147656253 C 15.2692139003906 11.6257295214845 15.0850700507812 17.1108151171875 11.0230336933594 17.9455996054687 C 10.0425116953124 18.1469492929688 7.19918404101566 17.8965070664062 7.56585943945311 15.3299357753906 C 7.69517317968746 14.4222851074219 8.54638143750003 12.9450279902344 7.56585943945311 11.6633984765625 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 500 616 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/title4.svg
Normal file
6
frontend/src/renderer/src/assets/new/title5.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="12px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1475 -512 )">
|
||||||
|
<path d="M 12.717571297148 2.13837329676619 L 12.717571297148 3.61361156723547 C 12.717571297148 3.67013410633388 12.6582226310948 3.76904854975606 12.5395252989881 3.9103548975022 C 12.4208279668812 4.05166124524828 12.2936522539098 4.21840273558871 12.1579981600737 4.41057936852343 C 12.0223440662373 4.60275600145809 11.8951683532659 4.8118893961223 11.7764710211591 5.03797955251605 C 11.6577736890526 5.2640697089098 11.598425022999 5.49581211921327 11.598425022999 5.73320678342674 C 11.598425022999 5.97060144764021 11.6577736890526 6.19951773098893 11.7764710211591 6.4199556334728 C 11.8951683532659 6.64039353595672 12.0223440662373 6.84104854975612 12.1579981600737 7.02192067487113 C 12.2936522539098 7.20279279998613 12.4208279668812 7.35257752859707 12.5395252989881 7.47127486070366 C 12.6582226310948 7.58997219281036 12.717571297148 7.66627762059329 12.717571297148 7.70019114405238 L 12.717571297148 9.48065112565303 C 12.717571297148 9.67282775858774 12.6469181232751 9.89043953411681 12.5056117755291 10.13348645224 C 12.3643054277829 10.3765333703633 12.1890855565778 10.605449653712 11.9799521619134 10.8202353022861 C 11.7708187672495 11.0350209508601 11.5390763569458 11.2130669490202 11.2847249310028 11.3543732967662 C 11.0303735050597 11.4956796445123 10.7901527138915 11.5663328183854 10.5640625574977 11.5663328183854 L 2.13655197792104 11.5663328183854 C 1.83133026678934 11.5663328183854 1.55154369825209 11.5126364062418 1.2971922723093 11.4052435819548 C 1.04284084636606 11.2978507576679 0.81957681692711 11.148066029057 0.627400183992449 10.9558893961223 C 0.435223551057788 10.7637127631876 0.285438822447077 10.5376226067938 0.178045998160087 10.277618926941 C 0.07065317387287 10.0176152470882 0.0169567617294888 9.73500255159604 0.0169567617294888 9.42978084046445 L 0.0169567617294888 2.1214165350367 C 0.0169567617294888 1.89532637864295 0.0678270469181825 1.66075784138445 0.169567617295343 1.41771092326121 C 0.271308187672503 1.17466400513791 0.415440662373612 0.951399975699132 0.601965041398216 0.747918834944755 C 0.788489420423275 0.544437694190378 1.00892732290708 0.374870076895036 1.2632787488501 0.239215983058784 C 1.51763017479288 0.103561889222533 1.79741674333036 0.0357348423044641 2.10263845446184 0.0357348423044641 L 10.5301490340387 0.0357348423044641 C 10.8353707451702 0.0357348423044641 11.1208095676172 0.0894312544479021 11.3864655013799 0.196824078734949 C 11.6521214351426 0.304216903021938 11.8838638454463 0.451175504677906 12.0816927322908 0.637699883702737 C 12.2795216191353 0.824224262727569 12.4349586016558 1.04748829216646 12.5480036798526 1.30749197201931 C 12.6610487580497 1.56749565187198 12.717571297148 1.84445609345431 12.717571297148 2.13837329676619 Z M 17.9911241950322 1.98576244120045 L 17.9911241950322 2.01967596465948 L 17.9911241950322 9.76891607505507 C 17.9911241950322 10.0176152470882 17.9261232750689 10.2380531495721 17.7961214351426 10.4302297825068 C 17.6661195952161 10.6224064154415 17.4711168353265 10.7184947319088 17.2111131554739 10.7184947319088 C 17.1206770929161 10.7184947319088 17.0048058877646 10.6845812084498 16.8634995400182 10.6167541615317 C 16.7221931922722 10.5489271146135 16.580886844526 10.4697955598757 16.4395804967801 10.3793594973182 C 16.2982741490339 10.2889234347607 16.1654461821527 10.1984873722032 16.0410965961362 10.1080513096457 C 15.9167470101197 10.0176152470882 15.8263109475622 9.94413594626025 15.7697884084637 9.88761340716178 C 15.6228298068077 9.76326382114524 15.3995657773689 9.54847817257121 15.0999963201473 9.24325646143973 C 14.8004268629254 8.93803475030819 14.4980312787488 8.59324726180773 14.1928095676174 8.2088939959383 C 13.8875878564859 7.82454073006897 13.6191057957683 7.42888295637988 13.3873633854646 7.02192067487113 C 13.1556209751611 6.61495839336237 13.0397497700092 6.24190963531271 13.0397497700092 5.90277440072202 C 13.0397497700092 5.56363916613145 13.1640993560259 5.17645977330722 13.4127985280591 4.7412362222492 C 13.661497700092 4.30601267119124 13.9610671573137 3.87926750099808 14.311506899724 3.46100071166967 C 14.6619466421344 3.0427339223412 15.029343146274 2.65555452951696 15.4136964121435 2.29946253319679 C 15.7980496780128 1.94337053687667 16.120228150874 1.67488847615903 16.3802318307269 1.49401635104414 C 16.481972401104 1.42618930412601 16.6204526218951 1.3442316224332 16.7956724931003 1.24814330596587 C 16.9708923643054 1.15205498949854 17.1263293468262 1.10401083126493 17.2619834406626 1.10401083126493 C 17.567205151794 1.10401083126493 17.7650340386385 1.18879463991254 17.855470101196 1.35836225720789 C 17.9459061637535 1.52792987450306 17.9911241950322 1.73706326916727 17.9911241950322 1.98576244120045 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1475 512 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/u10.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1255 -468 )">
|
||||||
|
<path d="M 15 14.9 C 15 15.7 14.8 16.5 14.3 17.1 C 13.9 17.7 13.2 18 12.5 18 L 2.5 18 C 1.8 18 1.1 17.7 0.7 17.1 C 0.2 16.5 0 15.7 0 14.9 C 0 14.3 0 13.6 0.1 13 C 0.2 12.4 0.3 11.8 0.5 11.2 C 0.7 10.7 0.9 10.2 1.2 9.7 C 1.5 9.3 1.9 8.9 2.3 8.7 C 2.8 8.4 3.3 8.3 3.9 8.3 C 4.9 9.3 6.2 9.8 7.6 9.8 C 9 9.8 10.3 9.3 11.3 8.3 C 11.9 8.3 12.4 8.4 12.9 8.7 C 13.3 9 13.7 9.3 14 9.7 C 14.3 10.2 14.5 10.7 14.7 11.2 C 14.9 11.8 15 12.4 15.1 13 C 15 13.6 15 14.3 15 14.9 Z M 10.7 1.3 C 11.6 2.1 12 3.3 12 4.5 C 12 5.7 11.5 6.8 10.7 7.7 C 9.8 8.5 8.7 9 7.5 9 C 6.3 9 5.2 8.5 4.3 7.7 C 3.5 6.8 3 5.7 3 4.5 C 3 3.3 3.5 2.2 4.3 1.3 C 5.2 0.5 6.3 0 7.5 0 C 8.7 0 9.8 0.5 10.7 1.3 Z " fill-rule="nonzero" fill="#cccccc" stroke="none" transform="matrix(1 0 0 1 1255 468 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
BIN
frontend/src/renderer/src/assets/new/u13.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
6
frontend/src/renderer/src/assets/new/u16.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="18px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1256 -538 )">
|
||||||
|
<path d="M 7.05 1.6 C 8.75 1.6 10.15 3 10.15 4.7 L 11.75 4.7 C 11.65 2.1 9.55 0 7.05 0 C 4.45 0 2.45 2.1 2.45 4.6 L 4.05 4.6 C 3.95 2.9 5.35 1.6 7.05 1.6 Z M 13.95 17 L 13.95 8.3 C 13.85 7.7 13.35 7.2 12.75 7.1 L 1.35 7.1 C 0.65 7.2 0.0499999999999997 7.7 0.0499999999999997 8.4 L 0.0499999999999997 16.8 C 0.15 17.4 0.55 17.8 1.15 18 L 12.95 18 C 13.45 17.9 13.85 17.5 13.95 17 Z " fill-rule="nonzero" fill="#cccccc" stroke="none" transform="matrix(1 0 0 1 1256 538 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/u249.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="98px" height="103px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1677 -169 )">
|
||||||
|
<path d="M 0 103 L 98 103 L 98 87.0421585160202 C 97.1281618887015 83.6694772344013 94.9696458684653 79.9595278246206 92.0016863406408 77.8010118043845 C 85.7284991568295 72.6745362563238 66.7740303541314 64.3102866779089 66.7740303541314 64.3102866779089 L 54.4974704890387 86.637436762226 L 52.7436762225971 81.1736930860034 L 55.7116357504215 74.0236087689713 L 48.9662731871838 67.2782462057336 L 42.2209106239461 74.0236087689713 L 45.5935919055648 80.7689713322091 L 43.5025295109613 86.2327150084317 L 31.6306913996627 63.5008431703204 C 31.6306913996627 63.5008431703204 12.7436762225971 71.8650927487353 6.40303541315347 76.9915682967959 C 3.43507588532884 79.4873524451939 1.27655986509285 82.7925801011804 0 87.0421585160202 L 0 103 Z M 76.0151770657671 30.58347386172 C 76.0151770657671 19.5885328836425 76.0151770657671 0.229342327150079 50.3153456998314 0.229342327150079 C 24.6829679595278 0.229342327150079 24.6829679595278 19.5885328836425 24.6155143338954 30.58347386172 C 24.5480607082632 41.9831365935919 35.1382799325463 62.2192242833052 50.2478920741989 62.2192242833052 C 65.8971332209107 61.8145025295109 76.0151770657671 41.5784148397976 76.0151770657671 30.58347386172 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1677 169 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/u253.svg
Normal file
6
frontend/src/renderer/src/assets/new/u257.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="12px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1764 -306 )">
|
||||||
|
<path d="M 2.51332895992346 8.2419489503817 L 9.44528268129761 1.68571087786256 C 9.85230796755718 1.3006023377863 10.4941555343512 1.31625715648852 10.8823950381679 1.72328244274809 C 11.2737655057254 2.13030772900765 11.258110687023 2.77841722328242 10.847954437023 3.16665672709928 L 3.57472566793899 10.0798246660306 L 1.77129055343516 10.3146469465649 L 2.51332895992346 8.2419489503817 Z M 11.8060293416033 4.17482705152673 C 12.7734971374045 3.25432371183206 12.8110687022902 1.72328244274809 11.8874343988548 0.758945610687078 C 10.9700620229009 -0.199129293893179 9.44841364503804 -0.236700858778647 8.48720777671747 0.674409589694619 L 1.43627743320599 7.34336235687027 C 1.35800333969473 7.41850548664121 1.29538406488541 7.51243439885502 1.2578125 7.61575620229013 L 0.0743082061069344 10.9157919847328 C -0.00709685114520653 11.1443523377862 0.0367366412215233 11.3979604007633 0.187022900763395 11.5889491889313 C 0.340440124045927 11.7799379770993 0.578393368320803 11.873866889313 0.819477576335657 11.8425572519084 L 3.97861999045813 11.4292700381679 C 4.12577528625957 11.4104842557252 4.26040672709905 11.3447340171756 4.36685949427465 11.244543177481 L 11.8060293416033 4.17482705152673 Z M 13.2681894083967 10.4555403148855 L 7.69507395038158 10.4555403148855 C 7.30996541030527 10.4555403148855 6.99686903625957 10.7686366889313 6.99686903625957 11.1537452290077 C 6.99686903625957 11.538853769084 7.30996541030527 11.8519501431298 7.69507395038158 11.8519501431298 L 13.2681894083967 11.8519501431298 C 13.6532979484732 11.8519501431298 13.9663943225191 11.538853769084 13.9663943225191 11.1537452290077 C 13.9663943225191 10.7686366889313 13.6532979484732 10.4555403148855 13.2681894083967 10.4555403148855 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1764 306 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/u264.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="12px" height="12px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1318 -254 )">
|
||||||
|
<path d="M 1.1717647058822 0.240000000000009 L 6 5.06823529411764 L 10.8282352941178 0.240000000000009 C 10.9517949764427 0.116440317674687 11.119377868552 0.047025210941456 11.2941176470588 0.047025210941456 C 11.6579943989025 0.047025210941456 11.9529747890585 0.342005601097583 11.9529747890585 0.705882352941217 C 11.9529747890585 0.880622131448035 11.8835596823253 1.0482050235571 11.7599999999998 1.17176470588242 L 6.93176470588219 6 L 11.7599999999998 10.8282352941176 C 11.8835596823253 10.9517949764429 11.9529747890585 11.119377868552 11.9529747890585 11.2941176470588 C 11.9529747890585 11.6579943989024 11.6579943989025 11.9529747890585 11.2941176470588 11.9529747890585 C 11.119377868552 11.9529747890585 10.9517949764427 11.8835596823253 10.8282352941178 11.76 L 6 6.93176470588236 L 1.1717647058822 11.76 C 1.04820502355733 11.8835596823253 0.880622131448035 11.9529747890585 0.705882352941217 11.9529747890585 C 0.342005601097526 11.9529747890585 0.047025210941456 11.6579943989024 0.047025210941456 11.2941176470588 C 0.047025210941456 11.119377868552 0.116440317674687 10.9517949764429 0.240000000000236 10.8282352941176 L 5.06823529411781 6 L 0.240000000000236 1.17176470588242 C 0.116440317674687 1.0482050235571 0.047025210941456 0.880622131448035 0.047025210941456 0.705882352941217 C 0.047025210941456 0.342005601097583 0.342005601097526 0.047025210941456 0.705882352941217 0.047025210941456 C 0.880622131448035 0.047025210941456 1.04820502355733 0.116440317674687 1.1717647058822 0.240000000000009 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1318 254 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/useravatar.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="30px" height="30px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1519 -247 )">
|
||||||
|
<path d="M 0 30 L 30 30 L 30 25.3520850046661 C 29.7331107822556 24.3697506507965 29.0723405719793 23.28918286154 28.163781532849 22.6604888750634 C 26.2434181092335 21.1673406571817 20.4410297002444 18.7311514595851 20.4410297002444 18.7311514595851 L 16.6828991292978 25.2342048822017 L 16.1460233334481 23.642823228933 L 17.0545823725779 21.5602743987295 L 14.9896754654644 19.5956056909904 L 12.9247685583507 21.5602743987295 L 13.9572220119076 23.5249431064687 L 13.3171008707022 25.1163247597374 L 9.68286471418241 18.4953912146564 C 9.68286471418241 18.4953912146564 3.90112537426421 20.931580412253 1.96011288157752 22.4247286301347 C 1.05155384244745 23.1516560519982 0.390783632171406 24.1143437187904 0 25.3520850046661 L 0 30 Z M 23.26995216299 8.90780792088933 C 23.26995216299 5.7053979272745 23.26995216299 0.0667987360631344 15.4026568468871 0.0667987360631344 C 7.55601059985543 0.0667987360631344 7.55601059985543 5.7053979272745 7.53536153078426 8.90780792088933 C 7.51471246171332 12.2280980369685 10.7566163058816 18.122104160186 15.3820077778159 18.122104160186 C 20.1725918023196 18.0042240377216 23.26995216299 12.1102179145042 23.26995216299 8.90780792088933 Z " fill-rule="nonzero" fill="#b7bac0" stroke="none" transform="matrix(1 0 0 1 1519 247 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/useredit.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="12px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1857 -205 )">
|
||||||
|
<path d="M 2.51332895992346 8.2419489503817 L 9.44528268129761 1.68571087786256 C 9.85230796755718 1.3006023377863 10.4941555343512 1.31625715648852 10.8823950381679 1.72328244274809 C 11.2737655057254 2.13030772900765 11.258110687023 2.77841722328242 10.847954437023 3.16665672709928 L 3.57472566793899 10.0798246660306 L 1.77129055343516 10.3146469465649 L 2.51332895992346 8.2419489503817 Z M 11.8060293416033 4.17482705152673 C 12.7734971374045 3.25432371183206 12.8110687022902 1.72328244274809 11.8874343988548 0.758945610687078 C 10.9700620229009 -0.199129293893179 9.44841364503804 -0.236700858778647 8.48720777671747 0.674409589694619 L 1.43627743320599 7.34336235687027 C 1.35800333969473 7.41850548664121 1.29538406488541 7.51243439885502 1.2578125 7.61575620229013 L 0.0743082061069344 10.9157919847328 C -0.00709685114520653 11.1443523377862 0.0367366412215233 11.3979604007633 0.187022900763395 11.5889491889313 C 0.340440124045927 11.7799379770993 0.578393368320803 11.873866889313 0.819477576335657 11.8425572519084 L 3.97861999045813 11.4292700381679 C 4.12577528625957 11.4104842557252 4.26040672709905 11.3447340171756 4.36685949427465 11.244543177481 L 11.8060293416033 4.17482705152673 Z M 13.2681894083967 10.4555403148855 L 7.69507395038158 10.4555403148855 C 7.30996541030527 10.4555403148855 6.99686903625957 10.7686366889313 6.99686903625957 11.1537452290077 C 6.99686903625957 11.538853769084 7.30996541030527 11.8519501431298 7.69507395038158 11.8519501431298 L 13.2681894083967 11.8519501431298 C 13.6532979484732 11.8519501431298 13.9663943225191 11.538853769084 13.9663943225191 11.1537452290077 C 13.9663943225191 10.7686366889313 13.6532979484732 10.4555403148855 13.2681894083967 10.4555403148855 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1857 205 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/renderer/src/assets/new/userinfo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1860 -349 )">
|
||||||
|
<path d="M 15.9545454545457 8 C 15.9545454545457 12.393939399858 12.3939393998578 15.9545454545454 8 15.9545454545454 C 3.6060606001422 15.9545454545454 0.0454545454542767 12.393939399858 0.0454545454542767 8 C 0.0454545454542767 3.60606060014203 3.6060606001422 0.045454545454561 8 0.045454545454561 C 12.3939393998578 0.045454545454561 15.9545454545457 3.60606060014203 15.9545454545457 8 Z M 14.4393939453128 8 C 14.4393939453128 4.43939392755681 11.5606060546872 1.56060605468747 8 1.56060605468747 C 4.43939394531276 1.56060605468747 1.56060605468724 4.43939394531253 1.56060605468724 8 C 1.56060605468724 11.5606060546875 4.43939392755669 14.4393939453125 8 14.4393939453125 C 11.5606060724433 14.4393939453125 14.4393939453128 11.5606060724432 14.4393939453128 8 Z M 3.45929338997712 8 C 3.45929338997712 8.63118257123517 3.95446233025609 9.14285714285717 4.56528417338677 9.14285714285717 C 5.17610601651791 9.14285714285717 5.67127495679711 8.63118257123517 5.67127495679711 8 C 5.67127495679711 7.36881742876483 5.17610601651791 6.85714285714283 4.56528417338677 6.85714285714283 C 3.95446233025609 6.85714285714283 3.45929338997712 7.36881742876483 3.45929338997712 8 Z M 6.85714285714289 8.00000001702989 C 6.85714285714289 8.63118257885975 7.36881742876471 9.14285714285717 8 9.14285714285717 C 8.63118257123529 9.14285714285717 9.14285714285711 8.63118257885975 9.14285714285711 8.00000001702989 C 9.14285714285711 7.36881742114025 8.63118257123529 6.85714285714283 8 6.85714285714283 C 7.36881742876471 6.85714285714283 6.85714285714289 7.36881742114025 6.85714285714289 7.99999998297011 Z M 10.3287250432029 8 C 10.3287250432029 8.63118257123517 10.8238939834821 9.14285714285717 11.4347158266132 9.14285714285717 C 12.0455376697439 9.14285714285717 12.5407066100229 8.63118257123517 12.5407066100229 8 C 12.5407066100229 7.36881742876483 12.0455376697439 6.85714285714283 11.4347158266132 6.85714285714283 C 10.8238939834821 6.85714285714283 10.3287250432029 7.36881742876483 10.3287250432029 8 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1860 349 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Noto Sans SC', sans-serif;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
color: #333;
|
color: #333;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|||||||
3001
frontend/src/renderer/src/views/Detection - 副本.vue
Normal file
203
frontend/src/renderer/src/views/DiagnosticMessage.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div class="DiagnosticMessage-container">
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
<div class="form-container">
|
||||||
|
<div class="form-container-header">
|
||||||
|
<div>诊断信息</div>
|
||||||
|
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel">
|
||||||
|
</div>
|
||||||
|
<div style="padding:20px 40px;">
|
||||||
|
<el-form :model="diagnosticForm" label-width="60px">
|
||||||
|
<el-form-item label="记录">
|
||||||
|
<el-input v-model="diagnosticForm.diagnosis_info" :rows="6" type="textarea" placeholder="请输入" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理">
|
||||||
|
<el-input v-model="diagnosticForm.treatment_info" :rows="6" type="textarea" placeholder="请输入" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="建议">
|
||||||
|
<el-input v-model="diagnosticForm.suggestion_info" :rows="6" type="textarea" placeholder="请输入" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="form-actions-display">
|
||||||
|
<el-button @click="handleCancel" class="formreturnCancel">退出</el-button>
|
||||||
|
<el-button type="primary" class="formsaveCancel"
|
||||||
|
@click="handleDiagnosticInfo('completed')">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { getBackendUrl, patientAPI } from '../services/api.js'
|
||||||
|
|
||||||
|
const emit = defineEmits([ 'closeDiagnosticMessage']);
|
||||||
|
const router = useRouter()
|
||||||
|
// 后端服务器地址配置
|
||||||
|
const BACKEND_URL = getBackendUrl()
|
||||||
|
const props = defineProps({
|
||||||
|
selectedPatient: {
|
||||||
|
required: false,
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const diagnosticForm =ref({})
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCancel = async () => {
|
||||||
|
emit('closeDiagnosticMessage',false)
|
||||||
|
}
|
||||||
|
async function handleDiagnosticInfo(status) {
|
||||||
|
try {
|
||||||
|
// 检查是否有活跃的会话ID
|
||||||
|
if (!selectedPatient.value.sessionId) {
|
||||||
|
throw new Error('缺少会话Id')
|
||||||
|
}
|
||||||
|
// 调用后端API采集检测数据
|
||||||
|
const response = await fetch(`${BACKEND_URL}/api/detection/${selectedPatient.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: selectedPatient.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
|
||||||
|
})
|
||||||
|
emit('closeDiagnosticMessage',true)
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || '诊断信息失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error({
|
||||||
|
message:'诊断信息失败',
|
||||||
|
duration: 5000
|
||||||
|
})
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.DiagnosticMessage-container {
|
||||||
|
width: 800px;
|
||||||
|
height: 620px;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.formreturnCancel {
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
background: #313131;
|
||||||
|
border: 1px solid rgb(148, 148, 148);
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(148, 148, 148);
|
||||||
|
}
|
||||||
|
|
||||||
|
.formreturnCancel:hover {
|
||||||
|
background: #1e2c49;
|
||||||
|
color: #266fff;
|
||||||
|
border: 1px solid #266fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formsaveCancel {
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
background: #266fff;
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.DiagnosticMessage-container .el-form-item__label{
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #787878;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DiagnosticMessage-container .el-textarea__inner{
|
||||||
|
background: #282828;
|
||||||
|
box-shadow: 0 0 0 1px rgb(54, 54, 54) inset;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.DiagnosticMessage-container .el-textarea__inner:hover{
|
||||||
|
box-shadow: 0 0 0 1px #266fff inset;
|
||||||
|
}
|
||||||
|
.DiagnosticMessage-container .form-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DiagnosticMessage-container .form-container-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;
|
||||||
|
}
|
||||||
|
.DiagnosticMessage-container .form-actions-display{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -14,16 +14,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div style="color:#fff;margin-right: 20px;">登录时间:{{ time }} </div>
|
<div style="color:#fff;margin-right: 20px;">登录时间:{{ time }} </div>
|
||||||
|
<div class="user-line"></div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<img src="@/assets/svg/avatar.svg" alt="Avatar" style="width: 20px;height: 20px;">
|
<img src="@/assets/new/u13.png" alt="Avatar" style="width: 30px;height: 30px;">
|
||||||
<!-- <el-avatar :size="40" :src="userInfo.avatar">
|
|
||||||
<el-icon>
|
|
||||||
<User />
|
|
||||||
</el-icon>
|
|
||||||
</el-avatar> -->
|
|
||||||
<span class="username">{{ userInfo.name }}</span>
|
<span class="username">{{ userInfo.name }}</span>
|
||||||
<el-dropdown @command="handleUserCommand">
|
<div class="user-line"></div>
|
||||||
|
<span class="username user-return" @click="handleUserCommand">退出</span>
|
||||||
|
<!-- <el-dropdown @command="handleUserCommand">
|
||||||
<el-button link class="user-dropdown">
|
<el-button link class="user-dropdown">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<ArrowDown />
|
<ArrowDown />
|
||||||
@ -35,7 +32,7 @@
|
|||||||
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,18 +83,19 @@
|
|||||||
|
|
||||||
|
|
||||||
const handleUserCommand = (command) => {
|
const handleUserCommand = (command) => {
|
||||||
switch (command) {
|
handleLogout()
|
||||||
case 'profile':
|
// switch (command) {
|
||||||
viewInfoClick()
|
// case 'profile':
|
||||||
// 个人信息
|
// viewInfoClick()
|
||||||
break
|
// // 个人信息
|
||||||
case 'settings':
|
// break
|
||||||
// 系统设置
|
// case 'settings':
|
||||||
break
|
// // 系统设置
|
||||||
case 'logout':
|
// break
|
||||||
handleLogout()
|
// case 'logout':
|
||||||
break
|
// handleLogout()
|
||||||
}
|
// break
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
const dialogVisible =ref(false)
|
const dialogVisible =ref(false)
|
||||||
function viewInfoClick(){
|
function viewInfoClick(){
|
||||||
@ -214,7 +212,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
height: 50px;
|
height: 60px;
|
||||||
background: #323232;
|
background: #323232;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -240,17 +238,18 @@
|
|||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #0099ff;
|
background: rgba(38, 111, 255, 1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-title {
|
.system-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 微软雅黑, sans-serif;
|
font-family: 'Noto Sans SC';
|
||||||
font-weight: 400;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
|
color: #FFFFFF;
|
||||||
color: rgb(255, 255, 255);
|
color: rgb(255, 255, 255);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -287,15 +286,15 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
.badge-invalid { background-color: #8b0000; color: #fff; }
|
.badge-invalid { background-color: rgba(67, 67, 67, 1); color: #949494; }
|
||||||
.badge-trial { background-color: #ff8c00; color: #fff; }
|
.badge-trial { background-color: rgba(67, 67, 67, 1); color: #949494; }
|
||||||
.badge-valid { background-color: #2e8b57; color: #fff; }
|
.badge-valid { background-color: rgba(38, 111, 255, 1); color: #fff; }
|
||||||
.activate-btn {
|
.activate-btn {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border: 1px solid #409EFF;
|
border: 1px solid rgba(38, 111, 255, 1);
|
||||||
background-color: #409EFF;
|
background-color: rgba(38, 111, 255, 1);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -316,4 +315,15 @@
|
|||||||
box-shadow: 0 0 0 1px transparent inset;
|
box-shadow: 0 0 0 1px transparent inset;
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
.user-line{
|
||||||
|
width: 1px;
|
||||||
|
height: 15px;
|
||||||
|
margin: 0 10px;
|
||||||
|
background-color: #949494;
|
||||||
|
}
|
||||||
|
.user-return:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(0, 140, 255);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,67 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-page" :style="{ backgroundImage: `url(${bg})` }">
|
<div class="login-page" :style="{ backgroundImage: `url(${bg})` }">
|
||||||
|
|
||||||
<!-- 页面主内容 -->
|
<!-- 页面主内容 -->
|
||||||
<div class="login-content">
|
<div class="login-content">
|
||||||
<!-- 系统标题 -->
|
<!-- 系统标题 -->
|
||||||
<h1 class="system-title">平衡体态检测系统</h1>
|
<div style="margin-right: 150px;">
|
||||||
|
<h1 class="system-title">平衡体态检测系统</h1>
|
||||||
|
|
||||||
<!-- 登录页面 -->
|
<!-- 登录页面 -->
|
||||||
<el-card v-if="!isRegisterMode && !isForgotPasswordMode" class="login-card">
|
<el-card v-if="!isRegisterMode && !isForgotPasswordMode" class="login-card">
|
||||||
<div class="card-header">登录</div>
|
<div class="card-header">欢迎登录</div>
|
||||||
|
|
||||||
<el-form class="login-form">
|
<el-form class="login-form">
|
||||||
<!-- 账号输入框 -->
|
<!-- 账号输入框 -->
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<img src="@/assets/username.png" alt="" srcset="" height="30" style="margin-left: 10px;">
|
<img src="@/assets/new/u10.svg" alt="" srcset="" height="20" style="margin-left: 10px;">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.account"
|
v-model="form.account"
|
||||||
placeholder="请输入账号"
|
placeholder="请输入账号"
|
||||||
class="custom-input"
|
class="custom-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 密码输入框(带显示切换) -->
|
|
||||||
<el-form-item>
|
|
||||||
<div class="input-box">
|
|
||||||
<img src="@/assets/password.png" alt="" srcset="" height="30" style="margin-left: 10px;">
|
|
||||||
<el-input
|
|
||||||
v-model="form.password"
|
|
||||||
:type="passwordVisible ? 'text' : 'password'"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
class="custom-input"
|
|
||||||
@keyup.enter="handleLogin"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<el-icon
|
|
||||||
class="password-toggle"
|
|
||||||
@click="passwordVisible = !passwordVisible"
|
|
||||||
>
|
|
||||||
<component :is="passwordVisible ? Hide : View" />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 记住密码 & 忘记密码 -->
|
<!-- 密码输入框(带显示切换) -->
|
||||||
<div class="form-footer">
|
<el-form-item>
|
||||||
<el-checkbox v-model="form.remember" class="remember-checkbox" @change="handleRememberChange">记住账号及密码</el-checkbox>
|
<div class="input-box">
|
||||||
<a href="#" class="forgot-link" @click="handleForgotPassword">忘记密码?</a>
|
<img src="@/assets/new/u16.svg" alt="" srcset="" height="20" style="margin-left: 10px;">
|
||||||
</div>
|
<el-input
|
||||||
|
v-model="form.password"
|
||||||
|
:type="passwordVisible ? 'text' : 'password'"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
class="custom-input"
|
||||||
|
@keyup.enter="handleLogin"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<el-icon
|
||||||
|
class="password-toggle"
|
||||||
|
@click="passwordVisible = !passwordVisible"
|
||||||
|
>
|
||||||
|
<component :is="passwordVisible ? Hide : View" />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 记住密码 & 忘记密码 -->
|
||||||
<div class="button-group">
|
<div class="form-footer">
|
||||||
<el-button type="primary" class="login-btn" @click="handleLogin" :loading="isLoading">{{ isLoading ? '登录中...' : '登录' }}</el-button>
|
<el-checkbox v-model="form.remember" class="remember-checkbox" @change="handleRememberChange">记住账号及密码</el-checkbox>
|
||||||
<el-button class="register-btn" @click="switchToRegister">注册</el-button>
|
<a href="#" class="forgot-link" @click="handleForgotPassword">忘记密码?</a>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="button-group">
|
||||||
|
<el-button type="primary" class="login-btn" @click="handleLogin" :loading="isLoading">{{ isLoading ? '登录中...' : '登录' }}</el-button>
|
||||||
|
<!-- <el-button class="register-btn" @click="switchToRegister">注册</el-button> -->
|
||||||
|
</div>
|
||||||
|
<div style="display:flex ;justify-content: flex-end;width: 100%;margin-top: 10px;">
|
||||||
|
<span class="register-btn" @click="switchToRegister">注册</span>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
<!-- 注册页面 -->
|
<!-- 注册页面 -->
|
||||||
<el-card v-if="isRegisterMode && !isForgotPasswordMode" class="register-card">
|
<el-card v-if="isRegisterMode && !isForgotPasswordMode" class="register-card">
|
||||||
<div class="card-header">注册</div>
|
<div class="card-header">注册</div>
|
||||||
@ -140,7 +142,6 @@
|
|||||||
<button class="submit-btn" @click="handleRegisterSubmit">注册</button>
|
<button class="submit-btn" @click="handleRegisterSubmit">注册</button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 忘记密码页面 -->
|
<!-- 忘记密码页面 -->
|
||||||
<el-card v-if="isForgotPasswordMode" class="forgot-password-card">
|
<el-card v-if="isForgotPasswordMode" class="forgot-password-card">
|
||||||
<div class="card-header">找回密码</div>
|
<div class="card-header">找回密码</div>
|
||||||
@ -186,6 +187,10 @@
|
|||||||
<button v-if="showRetrievedPassword" class="confirm-btn" @click="backToLoginFromForgot">退出</button>
|
<button v-if="showRetrievedPassword" class="confirm-btn" @click="backToLoginFromForgot">退出</button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义错误提示弹窗 -->
|
<!-- 自定义错误提示弹窗 -->
|
||||||
@ -216,7 +221,7 @@ import { ElMessage } from 'element-plus'
|
|||||||
import { useAuthStore } from '../stores'
|
import { useAuthStore } from '../stores'
|
||||||
import { User, Lock, View, Hide, Phone } from '@element-plus/icons-vue'
|
import { User, Lock, View, Hide, Phone } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import bg from '@/assets/bg.png'
|
import bg from '@/assets/new/newbg.jpg'
|
||||||
import { getBackendUrl,systemAPI } from '../services/api.js'
|
import { getBackendUrl,systemAPI } from '../services/api.js'
|
||||||
const BACKEND_URL = getBackendUrl()
|
const BACKEND_URL = getBackendUrl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -665,58 +670,58 @@ const copyPassword = async () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
/* flex-direction: column; */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 系统标题 */
|
/* 系统标题 */
|
||||||
.system-title {
|
.system-title {
|
||||||
font-size: 2rem;
|
text-align: center;
|
||||||
color: #00ffff;
|
font-family: "阿里妈妈数黑体 Bold", 阿里妈妈数黑体, sans-serif;
|
||||||
text-shadow: 0 0 15px rgba(0, 255, 255, 0.8);
|
font-weight: 700;
|
||||||
margin-bottom: 2.5rem;
|
font-style: normal;
|
||||||
|
font-size: 40px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
margin-bottom: 30px;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 登录卡片 */
|
/* 登录卡片 */
|
||||||
.login-card {
|
.login-card {
|
||||||
width: 482px;
|
width: 482px;
|
||||||
height: 471px;
|
height: 450px;
|
||||||
background: inherit;
|
background-color: #1D1C21;
|
||||||
background-color: rgba(10, 68, 131, 0.9);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(11, 92, 168, 1);
|
border-color: #1D1C21;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 40px 25px 0;
|
padding: 40px 25px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 注册卡片 */
|
/* 注册卡片 */
|
||||||
.register-card {
|
.register-card {
|
||||||
width: 100%;
|
width: 482px;
|
||||||
max-width: 450px;
|
background-color: #1D1C21;
|
||||||
background-color: #003366 !important;
|
box-sizing: border-box;
|
||||||
border: none !important;
|
border-width: 3px;
|
||||||
border-radius: 12px !important;
|
border-style: solid;
|
||||||
box-shadow: 0 0 30px rgba(0, 255, 255, 0.2);
|
border-color: #1D1C21;
|
||||||
|
border-radius: 5px;
|
||||||
padding: 30px 25px !important;
|
padding: 30px 25px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 卡片头部标题 */
|
/* 卡片头部标题 */
|
||||||
.card-header {
|
.card-header {
|
||||||
/* font-size: 1.4rem;
|
font-family: 微软雅黑, sans-serif;
|
||||||
color: #00ffff;
|
font-weight: 400;
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 25px; */
|
|
||||||
|
|
||||||
font-size: 24px;
|
|
||||||
font-family: 'Arial Negreta', 'Arial Normal', 'Arial', sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #00FFFF;
|
font-size: 30px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 登录表单 */
|
/* 登录表单 */
|
||||||
@ -741,7 +746,7 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
/* 密码显示图标 */
|
/* 密码显示图标 */
|
||||||
.password-icon {
|
.password-icon {
|
||||||
color: #00ffff !important;
|
color: #cccccc !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
@ -769,7 +774,7 @@ const copyPassword = async () => {
|
|||||||
|
|
||||||
/* 忘记密码链接 */
|
/* 忘记密码链接 */
|
||||||
.forgot-link {
|
.forgot-link {
|
||||||
color: #00ffff;
|
color: #ffffff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.forgot-link:hover {
|
.forgot-link:hover {
|
||||||
@ -785,30 +790,28 @@ const copyPassword = async () => {
|
|||||||
/* 登录按钮 */
|
/* 登录按钮 */
|
||||||
.login-btn {
|
.login-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: #00ffff !important;
|
background-color: #266fff !important;
|
||||||
border-color: #00ffff !important;
|
border-color: #266fff !important;
|
||||||
color: #003366 !important;
|
color: #fff !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
|
height: 50px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.login-btn:hover {
|
.login-btn:hover {
|
||||||
background-color: #00e6e6 !important;
|
background-color: #266fff !important;
|
||||||
border-color: #00e6e6 !important;
|
border-color: #266fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 注册按钮 */
|
/* 注册按钮 */
|
||||||
.register-btn {
|
.register-btn {
|
||||||
flex: 1;
|
font-family: 微软雅黑, sans-serif;
|
||||||
background-color: transparent !important;
|
font-weight: 400;
|
||||||
border-color: #00ffff !important;
|
font-style: normal;
|
||||||
color: #00ffff !important;
|
font-size: 14px;
|
||||||
font-weight: 500 !important;
|
color: rgb(255, 255, 255);
|
||||||
border-radius: 6px !important;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.register-btn:hover {
|
|
||||||
background-color: #004080 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义错误弹窗样式 */
|
/* 自定义错误弹窗样式 */
|
||||||
.error-dialog-overlay {
|
.error-dialog-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -825,10 +828,10 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error-dialog {
|
.error-dialog {
|
||||||
background-color: #003366;
|
background-color: #1D1C21;
|
||||||
border: 2px solid #00ffff;
|
border: 2px solid #1D1C29;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 0 30px rgba(0, 255, 255, 0.3);
|
box-shadow: 0 0 30px 1D1C29;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
animation: dialogFadeIn 0.3s ease-out;
|
animation: dialogFadeIn 0.3s ease-out;
|
||||||
@ -850,19 +853,19 @@ const copyPassword = async () => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 25px 15px;
|
padding: 20px 25px 15px;
|
||||||
border-bottom: 1px solid rgba(0, 255, 255, 0.2);
|
border-bottom: 1px solid rgba(0, 136, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: #00ffff;
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #00ffff;
|
color: #ffffff;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -876,7 +879,7 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.close-btn:hover {
|
.close-btn:hover {
|
||||||
background-color: rgba(0, 255, 255, 0.1);
|
background-color: rgba(0, 140, 255, 0.1);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,17 +919,14 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-btn {
|
.confirm-btn {
|
||||||
background-color: #00ffff;
|
background-color: #266fff ;
|
||||||
border-color: #00ffff;
|
border-color: #266fff ;
|
||||||
color: #003366;
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-btn:hover {
|
.confirm-btn:hover {
|
||||||
background-color: #00e6e6;
|
|
||||||
border-color: #00e6e6;
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 2px 8px rgba(0, 255, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 注册表单样式 */
|
/* 注册表单样式 */
|
||||||
@ -942,20 +942,25 @@ const copyPassword = async () => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #004080;
|
background: transparent;
|
||||||
border: 1px solid #00ffff;
|
color: #fff;
|
||||||
border-radius: 6px;
|
font-size: 16px;
|
||||||
|
height: 49px;
|
||||||
|
/* background-color: #004080;
|
||||||
|
|
||||||
|
border-radius: 6px; */
|
||||||
|
border: 1px solid rgb(67, 67, 67);
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper:focus-within {
|
.input-wrapper:focus-within {
|
||||||
border-color: #00e6e6;
|
border-color: #cccccc;
|
||||||
box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
|
box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-icon {
|
.input-icon {
|
||||||
color: #00ffff;
|
color: #cccccc;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
@ -975,7 +980,7 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.password-toggle {
|
.password-toggle {
|
||||||
color: #00ffff;
|
color: #cccccc;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -983,7 +988,7 @@ const copyPassword = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.password-toggle:hover {
|
.password-toggle:hover {
|
||||||
color: #00e6e6;
|
color: #cccccc;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,36 +1013,35 @@ const copyPassword = async () => {
|
|||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: #00ffff;
|
border-color: #266fff;
|
||||||
color: #00ffff;
|
color: #266fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:hover {
|
.back-btn:hover {
|
||||||
background-color: #004080;
|
/* background-color: #004080;
|
||||||
transform: translateY(-1px);
|
*/
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
background-color: #00ffff;
|
background-color: #266fff;
|
||||||
border-color: #00ffff;
|
border-color: #266fff;
|
||||||
color: #003366;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn:hover {
|
.submit-btn:hover {
|
||||||
background-color: #00e6e6;
|
|
||||||
border-color: #00e6e6;
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 2px 8px rgba(0, 255, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 忘记密码页面样式 */
|
/* 忘记密码页面样式 */
|
||||||
.forgot-password-card {
|
.forgot-password-card {
|
||||||
width: 100%;
|
width: 482px;
|
||||||
max-width: 450px;
|
background-color: #1D1C21;
|
||||||
background-color: #003366 !important;
|
box-sizing: border-box;
|
||||||
border: none !important;
|
border-width: 3px;
|
||||||
border-radius: 12px !important;
|
border-style: solid;
|
||||||
box-shadow: 0 0 30px rgba(0, 255, 255, 0.2);
|
border-color: #1D1C21;
|
||||||
|
border-radius: 5px;
|
||||||
padding: 30px 25px !important;
|
padding: 30px 25px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1124,16 +1128,12 @@ const copyPassword = async () => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
background-color: #00ffff;
|
background-color: #266fff;
|
||||||
border-color: #00ffff;
|
border-color: #266fff;
|
||||||
color: #003366;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-btn:hover {
|
.confirm-btn:hover {
|
||||||
background-color: #00e6e6;
|
|
||||||
border-color: #00e6e6;
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 2px 8px rgba(0, 255, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-box{
|
.input-box{
|
||||||
@ -1142,11 +1142,11 @@ const copyPassword = async () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 49px;
|
height: 49px;
|
||||||
background-color: rgba(255, 255, 255, 0);
|
background-color: rgba(255, 255, 255, 0);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-width: 2px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(0, 153, 204, 1);
|
border-color: rgb(67, 67, 67);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
@ -1171,7 +1171,7 @@ const copyPassword = async () => {
|
|||||||
|
|
||||||
.login-card .el-button{
|
.login-card .el-button{
|
||||||
padding: 12px 20px !important;
|
padding: 12px 20px !important;
|
||||||
height: 40px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,28 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="patient-create-container">
|
<div class="patient-create-container">
|
||||||
<Header />
|
|
||||||
<div class="nav-container">
|
|
||||||
<div class="nav-container-title" @click="goBack">
|
|
||||||
<img src="@/assets/svg/goback.svg" alt="">
|
|
||||||
<div style="margin-left: 20px;">
|
|
||||||
建档页
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nav-container-info">
|
|
||||||
<!-- <div>测试时间:2025-08-03 17:13:18<span></span></div>
|
|
||||||
<div style="margin-left: 15px;">测试医生:<span>李医生</span></div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 表单内容 -->
|
<!-- 表单内容 -->
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
|
<div class="form-container-header">
|
||||||
|
<div>{{ patienttitle }} </div>
|
||||||
|
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel">
|
||||||
|
</div>
|
||||||
<el-form ref="patientFormRef" :model="patientForm" :rules="formRules" label-width="120px" class="patient-form">
|
<el-form ref="patientFormRef" :model="patientForm" :rules="formRules" label-width="120px" class="patient-form">
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<div class="section-title">
|
|
||||||
<div class="section-title-text">
|
|
||||||
基本信息
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="测试者ID" prop="testerId">
|
<el-form-item label="测试者ID" prop="testerId">
|
||||||
@ -30,10 +15,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
|
<el-form-item label="患者姓名" prop="name" required>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="姓名" prop="name" required>
|
|
||||||
<el-input v-model="patientForm.name" placeholder="请输入" clearable />
|
<el-input v-model="patientForm.name" placeholder="请输入" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -48,7 +30,7 @@
|
|||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="出生日期" prop="birth_date" required>
|
<el-form-item label="出生日期" prop="birth_date" required>
|
||||||
<el-date-picker v-model="patientForm.birth_date" type="date" placeholder="请选择" style="width: 100%"
|
<el-date-picker v-model="patientForm.birth_date" type="date" placeholder="请选择" style="width: 100%"
|
||||||
@change="calculateAgeres" />
|
@change="calculateAgeres" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@ -65,7 +47,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="长期居住地" prop="residence">
|
<el-form-item label="居住地" prop="residence">
|
||||||
<el-input v-model="patientForm.residence" placeholder="请输入" clearable />
|
<el-input v-model="patientForm.residence" placeholder="请输入" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -87,9 +69,6 @@
|
|||||||
<el-form-item label="鞋码" prop="shoe_size">
|
<el-form-item label="鞋码" prop="shoe_size">
|
||||||
<el-input v-model="patientForm.shoe_size" placeholder="请输入" clearable />
|
<el-input v-model="patientForm.shoe_size" placeholder="请输入" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="电话号码" prop="phone" required>
|
<el-form-item label="电话号码" prop="phone" required>
|
||||||
@ -110,43 +89,52 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="单位" prop="workplace">
|
<el-form-item label="证件号" prop="workplace">
|
||||||
<el-input v-model="patientForm.workplace" placeholder="请输入" clearable />
|
<el-input v-model="patientForm.workplace" placeholder="请输入" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<div class="form-actions-display">
|
||||||
|
<el-button @click="handleCancel" class="formreturnCancel">退出</el-button>
|
||||||
|
<el-button type="primary" :loading="saveLoading" @click="handleSave"
|
||||||
|
class="formsaveCancel">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部操作按钮 -->
|
<!-- 底部操作按钮 -->
|
||||||
<div class="footer-actions">
|
<!-- <div class="footer-actions">
|
||||||
<el-button @click="handleCancel">退出</el-button>
|
|
||||||
<el-button type="primary" :loading="saveLoading" @click="handleSave">
|
|
||||||
保存
|
|
||||||
</el-button>
|
|
||||||
<el-button type="success" :loading="saveAndDetectLoading" @click="handleSaveAndDetect">
|
<el-button type="success" :loading="saveAndDetectLoading" @click="handleSaveAndDetect">
|
||||||
保存并开始检测
|
保存并开始检测
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { patientAPI } from '../services/api.js'
|
import { patientAPI } from '../services/api.js'
|
||||||
import Header from '@/views/Header.vue'
|
const emit = defineEmits([ 'closecreatbox']);
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const props = defineProps({
|
||||||
|
selectedPatient: {
|
||||||
|
required: false,
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
patienttype: {
|
||||||
|
required: false,
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
})
|
||||||
// 表单引用
|
// 表单引用
|
||||||
const patientFormRef = ref()
|
const patientFormRef = ref()
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const saveLoading = ref(false)
|
|
||||||
const saveAndDetectLoading = ref(false)
|
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const patientForm = reactive({
|
const patientForm = reactive({
|
||||||
id: '',
|
id: '',
|
||||||
@ -163,6 +151,21 @@ const patientForm = reactive({
|
|||||||
workplace: '',
|
workplace: '',
|
||||||
email: ''
|
email: ''
|
||||||
})
|
})
|
||||||
|
// 加载状态
|
||||||
|
const saveLoading = ref(false)
|
||||||
|
const saveAndDetectLoading = ref(false)
|
||||||
|
const patienttitle = ref("新建患者信息")
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
// 从认证状态管理中加载用户信息
|
||||||
|
if (props.patienttype == 'edit') {
|
||||||
|
patienttitle.value = '编辑患者信息'
|
||||||
|
Object.assign(patientForm, props.selectedPatient)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
const occupationOptions = ref(["学生", "教师", "医生", "护士", "工程师", "程序员", "设计师",
|
const occupationOptions = ref(["学生", "教师", "医生", "护士", "工程师", "程序员", "设计师",
|
||||||
"会计师", "律师", "警察", "消防员", "军人", "公务员", "销售", "市场营销",
|
"会计师", "律师", "警察", "消防员", "军人", "公务员", "销售", "市场营销",
|
||||||
"人力资源", "行政", "财务", "咨询师", "建筑师", "科研人员", "记者", "编辑",
|
"人力资源", "行政", "财务", "咨询师", "建筑师", "科研人员", "记者", "编辑",
|
||||||
@ -206,10 +209,6 @@ const formRules = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法
|
|
||||||
const goBack = () => {
|
|
||||||
router.go(-1)
|
|
||||||
}
|
|
||||||
const calculatedAge = ref('')
|
const calculatedAge = ref('')
|
||||||
// 实现年龄计算方法 calculateAgeres
|
// 实现年龄计算方法 calculateAgeres
|
||||||
const calculateAgeres = (date) => {
|
const calculateAgeres = (date) => {
|
||||||
@ -224,27 +223,7 @@ const calculateAgeres = (date) => {
|
|||||||
calculatedAge.value = age
|
calculatedAge.value = age
|
||||||
}
|
}
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
// 检查是否有未保存的数据
|
emit('closecreatbox',false)
|
||||||
const hasData = Object.values(patientForm).some(value => value !== '')
|
|
||||||
|
|
||||||
if (hasData) {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'您有未保存的数据,确定要退出吗?',
|
|
||||||
'提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定退出',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
router.go(-1)
|
|
||||||
} catch {
|
|
||||||
// 用户取消
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
router.go(-1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateForm = async () => {
|
const validateForm = async () => {
|
||||||
@ -258,27 +237,34 @@ const validateForm = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const savePatient = async () => {
|
const savePatient = async () => {
|
||||||
|
// 性别值映射:中文转英文
|
||||||
|
const genderMap = { '男': 'male', '女': 'female' }
|
||||||
|
const genderValue = genderMap[patientForm.gender] || patientForm.gender
|
||||||
|
|
||||||
const patientData = {
|
const patientData = {
|
||||||
id: patientForm.id,
|
id: patientForm.id,
|
||||||
name: patientForm.name,
|
name: patientForm.name,
|
||||||
gender: patientForm.gender,
|
gender: genderValue,
|
||||||
age: calculatedAge.value,
|
|
||||||
birth_date: patientForm.birth_date,
|
birth_date: patientForm.birth_date,
|
||||||
height: patientForm.height,
|
height: parseFloat(patientForm.height) || null,
|
||||||
weight: patientForm.weight,
|
weight: parseFloat(patientForm.weight) || null,
|
||||||
shoe_size: patientForm.shoe_size,
|
shoe_size: patientForm.shoe_size ? parseFloat(patientForm.shoe_size) : null,
|
||||||
phone: patientForm.phone,
|
phone: patientForm.phone,
|
||||||
occupation: patientForm.occupation,
|
occupation: patientForm.occupation,
|
||||||
email: patientForm.email,
|
email: patientForm.email,
|
||||||
nationality: patientForm.nationality,
|
nationality: patientForm.nationality,
|
||||||
residence: patientForm.residence,
|
residence: patientForm.residence,
|
||||||
workplace: patientForm.workplace
|
workplace: patientForm.workplace,
|
||||||
|
medical_history: '', // 添加病史字段
|
||||||
|
notes: '' // 添加备注字段
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await patientAPI.create(patientData)
|
const response = await patientAPI.create(patientData)
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
emit('closecreatbox',true)
|
||||||
return response.data
|
return response.data
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '保存失败')
|
throw new Error(response.message || '保存失败')
|
||||||
}
|
}
|
||||||
@ -321,10 +307,15 @@ const handleSaveAndDetect = async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.patient-create-container {
|
.patient-create-container {
|
||||||
height: 100vh;
|
width: 800px;
|
||||||
display: flex;
|
height:620px;
|
||||||
flex-direction: column;
|
position: absolute;
|
||||||
background: #000000;
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
background: #1b1b1b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-container {
|
.nav-container {
|
||||||
@ -335,7 +326,7 @@ const handleSaveAndDetect = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-container-title {
|
.nav-container-title {
|
||||||
font-family: 'Arial Negreta', 'Arial Normal', 'Arial', sans-serif;
|
font-family: 'Noto Sans SC';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
@ -349,7 +340,7 @@ const handleSaveAndDetect = async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-family: 'Arial Normal', 'Arial', sans-serif;
|
font-family: 'Noto Sans SC';
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
@ -372,16 +363,11 @@ const handleSaveAndDetect = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px 20px 0px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.patient-form {
|
.patient-form {
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: #333333;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
@ -442,20 +428,16 @@ const handleSaveAndDetect = async () => {
|
|||||||
|
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
font-family: '苹方 粗体', '苹方 中等', '苹方', sans-serif !important;
|
font-family: 'Noto Sans SC';
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
font-style: normal !important;
|
font-style: normal !important;
|
||||||
color: #FFFFFF !important;
|
color: #787878 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-form-item__content) {
|
:deep(.el-form-item__content) {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-textarea__inner) {
|
:deep(.el-textarea__inner) {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
@ -480,39 +462,42 @@ const handleSaveAndDetect = async () => {
|
|||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
.patient-create-container :deep(.el-input__wrapper) {
|
||||||
background-color: rgba(51, 51, 51, 1);
|
background-color: #282828;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(127, 127, 127, 1);
|
border-color: rgb(54, 54, 54);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background-color: rgba(51, 51, 51, 1);
|
background-color: #282828;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(127, 127, 127, 1);
|
border-color: rgb(54, 54, 54);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: '苹方 粗体', '苹方 中等', '苹方', sans-serif;
|
font-family: 'Noto Sans SC';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-col-12) {
|
:deep(.el-col-12) {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input__inner) {
|
:deep(.el-input__inner) {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
@ -549,4 +534,54 @@ const handleSaveAndDetect = async () => {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 1px solid #f56c6c;
|
border: 1px solid #f56c6c;
|
||||||
}
|
}
|
||||||
|
.form-container-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;
|
||||||
|
}
|
||||||
|
.form-actions-display{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
.formreturnCancel{
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
background: #313131;
|
||||||
|
border: 1px solid rgb(148, 148, 148);
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(148, 148, 148);
|
||||||
|
}
|
||||||
|
.formreturnCancel:hover{
|
||||||
|
background: #1e2c49;
|
||||||
|
color: #266fff;
|
||||||
|
border: 1px solid #266fff;
|
||||||
|
}
|
||||||
|
.formsaveCancel{
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
background: #266fff;
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
.el-date-editor.el-input{
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||