合并代码
This commit is contained in:
commit
6e4ec82e17
@ -30,7 +30,7 @@ backend = directshow
|
||||
|
||||
[CAMERA2]
|
||||
enable = True
|
||||
device_index = 1
|
||||
device_index = 2
|
||||
width = 1280
|
||||
height = 720
|
||||
fps = 30
|
||||
@ -39,7 +39,7 @@ fourcc = MJPG
|
||||
backend = directshow
|
||||
|
||||
[FEMTOBOLT]
|
||||
enable = False
|
||||
enable = True
|
||||
algorithm_type = plt
|
||||
color_resolution = 1080P
|
||||
depth_mode = NFOV_2X2BINNED
|
||||
@ -54,13 +54,13 @@ imu_enable = True
|
||||
imu_use_mock = False
|
||||
imu_ble_name = WT901BLE67
|
||||
imu_mac_address = FA:E8:88:06:FE:F3
|
||||
pressure_enable = False
|
||||
pressure_use_mock = False
|
||||
pressure_port = COM5
|
||||
pressure_enable = True
|
||||
pressure_use_mock = True
|
||||
pressure_port = COM3
|
||||
pressure_baudrate = 115200
|
||||
|
||||
[REMOTE]
|
||||
enable = True
|
||||
enable = False
|
||||
port = COM6
|
||||
baudrate = 115200
|
||||
timeout = 0.1
|
||||
|
||||
@ -57,6 +57,10 @@ class DeviceCoordinator:
|
||||
self.is_initialized = False
|
||||
self.is_running = False
|
||||
self.coordinator_lock = threading.RLock()
|
||||
self._init_summary = {
|
||||
'initialized_at': None,
|
||||
'device_results': {},
|
||||
}
|
||||
|
||||
# 监控线程
|
||||
self.monitor_thread = None
|
||||
@ -116,8 +120,12 @@ class DeviceCoordinator:
|
||||
self._register_namespaces()
|
||||
|
||||
# 初始化设备(失败则降级继续)
|
||||
if not self._initialize_devices():
|
||||
self.logger.warning("设备初始化失败,将以降级模式继续运行")
|
||||
init_ok = bool(self._initialize_devices())
|
||||
self._init_summary['initialized_at'] = time.time()
|
||||
if not init_ok:
|
||||
self.logger.warning("设备初始化失败(没有任何设备初始化成功)")
|
||||
self.is_initialized = False
|
||||
return False
|
||||
|
||||
self.is_initialized = True
|
||||
self.stats['start_time'] = time.time()
|
||||
@ -195,12 +203,14 @@ class DeviceCoordinator:
|
||||
try:
|
||||
timeout_s = 45 if device_name == 'imu' else 30
|
||||
result = future.result(timeout=timeout_s)
|
||||
self._init_summary['device_results'][device_name] = bool(result)
|
||||
if result:
|
||||
success_count += 1
|
||||
self.logger.info(f"{device_name}设备初始化成功")
|
||||
else:
|
||||
self.logger.error(f"{device_name}设备初始化失败")
|
||||
except Exception as e:
|
||||
self._init_summary['device_results'][device_name] = False
|
||||
self.logger.error(f"{device_name}设备初始化异常: {e}")
|
||||
|
||||
# 至少需要一个设备初始化成功
|
||||
@ -215,6 +225,97 @@ class DeviceCoordinator:
|
||||
self.logger.error(f"设备初始化失败: {e}")
|
||||
return False
|
||||
|
||||
def get_enabled_devices(self) -> List[str]:
|
||||
enabled = []
|
||||
try:
|
||||
for name, cfg in (self.device_configs or {}).items():
|
||||
if isinstance(cfg, dict) and cfg.get('enable', False):
|
||||
enabled.append(name)
|
||||
except Exception:
|
||||
pass
|
||||
return enabled
|
||||
|
||||
def get_required_devices_for_detection(self) -> List[str]:
|
||||
enabled = self.get_enabled_devices()
|
||||
required = [d for d in enabled if d not in ('remote',)]
|
||||
return required
|
||||
|
||||
def get_device_readiness(self, device_name: str) -> Dict[str, Any]:
|
||||
enabled = bool(self.device_configs.get(device_name, {}).get('enable', False))
|
||||
device = self.devices.get(device_name)
|
||||
readiness = {
|
||||
'device_name': device_name,
|
||||
'enabled': enabled,
|
||||
'exists': device is not None,
|
||||
'initializing': False,
|
||||
'is_connected': False,
|
||||
'is_streaming': False,
|
||||
'ready': False,
|
||||
}
|
||||
|
||||
if not device:
|
||||
return readiness
|
||||
status = None
|
||||
try:
|
||||
if hasattr(device, 'get_status'):
|
||||
status = device.get_status()
|
||||
except Exception:
|
||||
status = None
|
||||
|
||||
try:
|
||||
readiness['initializing'] = bool(getattr(device, '_initializing', False))
|
||||
except Exception:
|
||||
readiness['initializing'] = False
|
||||
try:
|
||||
if isinstance(status, dict) and 'is_connected' in status:
|
||||
readiness['is_connected'] = bool(status.get('is_connected'))
|
||||
else:
|
||||
readiness['is_connected'] = bool(getattr(device, 'is_connected', False))
|
||||
except Exception:
|
||||
readiness['is_connected'] = False
|
||||
try:
|
||||
if isinstance(status, dict) and 'is_streaming' in status:
|
||||
readiness['is_streaming'] = bool(status.get('is_streaming'))
|
||||
else:
|
||||
readiness['is_streaming'] = bool(getattr(device, 'is_streaming', False))
|
||||
except Exception:
|
||||
readiness['is_streaming'] = False
|
||||
|
||||
ok = readiness['is_connected'] and (not readiness['initializing'])
|
||||
readiness['ready'] = bool(ok)
|
||||
return readiness
|
||||
|
||||
def get_readiness_snapshot(self, required_devices: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
required = required_devices if required_devices is not None else self.get_required_devices_for_detection()
|
||||
devices = {}
|
||||
for name in required:
|
||||
devices[name] = self.get_device_readiness(name)
|
||||
|
||||
all_ready = bool(self.is_initialized) and all(devices[name].get('ready', False) for name in devices)
|
||||
return {
|
||||
'coordinator': {
|
||||
'is_initialized': bool(self.is_initialized),
|
||||
'is_running': bool(self.is_running),
|
||||
'enabled_devices': self.get_enabled_devices(),
|
||||
'required_devices': required,
|
||||
'init_summary': self._init_summary,
|
||||
},
|
||||
'devices': devices,
|
||||
'all_ready': all_ready,
|
||||
}
|
||||
|
||||
def wait_until_ready_for_detection(self, timeout_s: float = 10.0, poll_interval_s: float = 0.2) -> Dict[str, Any]:
|
||||
deadline = time.time() + max(0.0, float(timeout_s))
|
||||
last_snapshot = self.get_readiness_snapshot()
|
||||
while time.time() < deadline:
|
||||
last_snapshot = self.get_readiness_snapshot()
|
||||
if last_snapshot.get('all_ready', False):
|
||||
return last_snapshot
|
||||
time.sleep(max(0.05, float(poll_interval_s)))
|
||||
last_snapshot['timeout'] = True
|
||||
last_snapshot['timeout_s'] = float(timeout_s)
|
||||
return last_snapshot
|
||||
|
||||
def _init_camera_by_name(self, device_name: str, section: str = 'CAMERA1') -> bool:
|
||||
"""
|
||||
按名称初始化相机,支持 camera1/camera2 并覆盖配置段
|
||||
|
||||
@ -704,6 +704,7 @@ class PressureManager(BaseDevice):
|
||||
Returns:
|
||||
bool: 初始化是否成功
|
||||
"""
|
||||
self._initializing = True
|
||||
try:
|
||||
self.logger.info(f"正在初始化压力板设备...")
|
||||
|
||||
@ -716,13 +717,28 @@ class PressureManager(BaseDevice):
|
||||
else:
|
||||
self.device = MockPressureDevice()
|
||||
|
||||
connected = False
|
||||
try:
|
||||
if self.use_mock:
|
||||
connected = True
|
||||
elif hasattr(self.device, 'is_connected'):
|
||||
connected = bool(self.device.is_connected)
|
||||
else:
|
||||
connected = bool(self.check_hardware_connection())
|
||||
except Exception:
|
||||
connected = False
|
||||
|
||||
# 使用set_connected方法启动连接监控线程
|
||||
self.set_connected(True)
|
||||
self.set_connected(bool(connected))
|
||||
self._device_info.update({
|
||||
'device_type': 'mock' if self.use_mock else 'real',
|
||||
'matrix_size': '4x4' if hasattr(self.device, 'rows') else 'unknown'
|
||||
})
|
||||
|
||||
if not connected:
|
||||
self.logger.warning("压力板初始化完成但硬件未连接")
|
||||
return False
|
||||
|
||||
self.logger.info(f"压力板初始化成功 - use_mock: {self.use_mock}")
|
||||
return True
|
||||
|
||||
@ -732,6 +748,8 @@ class PressureManager(BaseDevice):
|
||||
self.set_connected(False)
|
||||
self.device = None
|
||||
return False
|
||||
finally:
|
||||
self._initializing = False
|
||||
|
||||
def start_streaming(self) -> bool:
|
||||
|
||||
|
||||
@ -265,7 +265,7 @@ class AppServer:
|
||||
|
||||
def _initialize_devices(self):
|
||||
"""
|
||||
初始化设备(在用户登录成功后调用)
|
||||
|
||||
"""
|
||||
try:
|
||||
self.logger.info('开始初始化设备...')
|
||||
@ -1294,21 +1294,37 @@ class AppServer:
|
||||
if not self.db_manager or not self.device_coordinator:
|
||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
||||
|
||||
# 检查设备是否已初始化
|
||||
if self.device_coordinator and not self.device_coordinator.is_initialized:
|
||||
self.logger.info('设备尚未初始化,等待初始化完成...')
|
||||
# 最多等待10秒
|
||||
start_wait = time.time()
|
||||
while not self.device_coordinator.is_initialized:
|
||||
if time.time() - start_wait > 10:
|
||||
return jsonify({'success': False, 'error': '设备初始化超时,请稍后重试'}), 503
|
||||
time.sleep(0.5)
|
||||
self.logger.info('设备初始化完成,继续开始检测')
|
||||
|
||||
data = flask_request.get_json()
|
||||
data = flask_request.get_json() or {}
|
||||
patient_id = data.get('patient_id')
|
||||
creator_id = data.get('creator_id')
|
||||
force_start = bool(data.get('force_start', False))
|
||||
|
||||
if self.device_coordinator:
|
||||
if not getattr(self.device_coordinator, 'is_initialized', False):
|
||||
try:
|
||||
threading.Thread(target=self._initialize_devices, daemon=True).start()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
readiness = {}
|
||||
if not force_start:
|
||||
try:
|
||||
readiness = self.device_coordinator.wait_until_ready_for_detection(timeout_s=10.0, poll_interval_s=0.2)
|
||||
except Exception as e:
|
||||
self.logger.error(f'等待设备就绪失败: {e}')
|
||||
readiness = {}
|
||||
|
||||
if not readiness or not readiness.get('all_ready', False):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '设备未就绪,请稍后重试',
|
||||
'readiness': readiness
|
||||
}), 503
|
||||
else:
|
||||
try:
|
||||
readiness = self.device_coordinator.get_readiness_snapshot()
|
||||
except Exception:
|
||||
readiness = {}
|
||||
|
||||
if not patient_id or not creator_id:
|
||||
return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400
|
||||
@ -1324,7 +1340,7 @@ class AppServer:
|
||||
except Exception as monitor_error:
|
||||
self.logger.error(f'启动设备连接监控失败: {monitor_error}')
|
||||
|
||||
return jsonify({'success': True, 'session_id': session_id})
|
||||
return jsonify({'success': True, 'session_id': session_id, 'forced': bool(force_start), 'readiness': readiness})
|
||||
except Exception as e:
|
||||
self.logger.error(f'开始检测失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
@ -1368,8 +1384,6 @@ class AppServer:
|
||||
item_copy['type'] = 'video'
|
||||
result_data.append(item_copy)
|
||||
|
||||
# 按时间戳排序
|
||||
result_data.sort(key=lambda x: x.get('timestamp', ''), reverse=False)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
<div class="displayleft" style="width: 550px;
|
||||
justify-content: flex-end;padding-right: 5px;">
|
||||
<div class="icon-box" title="相机参数设置" @click="cameraUpdate">
|
||||
<div class="icon-box" title="设备参数设置" @click="cameraUpdate">
|
||||
<img src="@/assets/detection/settings.png" alt="" >
|
||||
</div>
|
||||
<div class="icon-box" title="查看档案" @click="routerClick">
|
||||
@ -239,16 +239,17 @@
|
||||
<div style="width:100%;height: 50px;"></div>
|
||||
<div class="body-footbottom-leftbottom">
|
||||
<div class="body-footbottom-leftbox">
|
||||
<span class="currencytext1">右前足</span>
|
||||
<span class="currencytext2">
|
||||
<span class="currencytext2">
|
||||
{{ footPressure.right_front }}%
|
||||
</span>
|
||||
<span class="currencytext1">右前足</span>
|
||||
|
||||
</div>
|
||||
<div class="body-footbottom-leftbox">
|
||||
<span class="currencytext1">右后足</span>
|
||||
<span class="currencytext2">
|
||||
<span class="currencytext2">
|
||||
{{ footPressure.right_rear }}%
|
||||
</span>
|
||||
<span class="currencytext1">右后足</span>
|
||||
</div>
|
||||
<!-- <div class="body-footbottom-leftbox">
|
||||
<span class="currencytext1">右足总压力</span>
|
||||
@ -780,6 +781,37 @@
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- 无操作退出提示 -->
|
||||
<div class="pop-up-mask init-mask" v-if="isSystemInitializing">
|
||||
<div class="init-loading-container">
|
||||
<div v-if="!initFailed" class="init-loading-content">
|
||||
<div class="init-spinner"></div>
|
||||
<div class="init-loading-text">{{ initLoadingText }}</div>
|
||||
</div>
|
||||
<div v-else class="init-failed-content">
|
||||
<div class="init-failed-title">设备未就绪</div>
|
||||
<div class="init-failed-subtitle">{{ initErrorText || '请检查设备连接与配置后重试' }}</div>
|
||||
<div class="init-failed-list" v-if="notReadyDevices.length">
|
||||
<div class="init-failed-item" v-for="item in notReadyDevices" :key="item.device_name">
|
||||
<div class="init-failed-item-left">
|
||||
<div class="init-failed-item-name">{{ getDeviceDisplayName(item.device_name) }}</div>
|
||||
<div class="init-failed-item-reason">{{ getNotReadyReason(item) }}</div>
|
||||
</div>
|
||||
<div class="init-failed-item-right">
|
||||
<div class="init-failed-item-kv">是否启用: {{ item.enabled === false ? 'N' : 'Y' }}</div>
|
||||
<div class="init-failed-item-kv">是否创建实例: {{ item.exists ? 'Y' : 'N' }}</div>
|
||||
<div class="init-failed-item-kv">是否初始化中: {{ item.initializing ? 'Y' : 'N' }}</div>
|
||||
<div class="init-failed-item-kv">是否已连接: {{ item.is_connected ? 'Y' : 'N' }}</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="init-failed-actions">
|
||||
<el-button @click="handleInitBack">返回</el-button>
|
||||
<el-button type="primary" @click="handleInitProceed">进入检测</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pop-up-mask" v-if="isTip">
|
||||
<div class="pop-up-tip-container">
|
||||
<div class="pop-up-tip-header">
|
||||
@ -808,7 +840,7 @@
|
||||
<div class="pop-up-mask" v-if="cameraDialogVisible">
|
||||
<div class="pop-up-camera-container">
|
||||
<div class="pop-up-camera-header">
|
||||
<div>相机参数设置</div>
|
||||
<div>设备参数设置</div>
|
||||
<img src="@/assets/close.png" alt="" style="cursor: pointer;" @click="handleCameraCancel">
|
||||
</div>
|
||||
<div class="pop-up-camera-body">
|
||||
@ -826,7 +858,7 @@
|
||||
<el-checkbox v-model="cameraForm.femtobolt.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||||
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 10px;">
|
||||
<div class="pop-up-camera-display" style="padding-top: 20px;padding-bottom: 10px;">
|
||||
<div class="pop-up-camera-line"></div>
|
||||
<div class="pop-up-camera-title">足部相机</div>
|
||||
</div>
|
||||
@ -856,20 +888,9 @@
|
||||
</el-radio-group>
|
||||
<el-checkbox v-model="cameraForm.camera2.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 00px;">
|
||||
<div class="pop-up-camera-line"></div>
|
||||
<div class="pop-up-camera-title">遥控器</div>
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||||
<div class="pop-up-camera-name">串口号</div>
|
||||
<el-select v-model="cameraForm.remote.port" placeholder="请选择" style="width: 434px;">
|
||||
<el-option v-for="item in remotePortData" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-checkbox v-model="cameraForm.remote.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 00px;">
|
||||
<div class="pop-up-camera-display" style="padding-top: 20px;padding-bottom: 00px;">
|
||||
<div class="pop-up-camera-line"></div>
|
||||
<div class="pop-up-camera-title">IMU设备</div>
|
||||
</div>
|
||||
@ -884,6 +905,29 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="pop-up-camera-display" style="padding-top: 20px;padding-bottom: 0px;">
|
||||
<div class="pop-up-camera-line"></div>
|
||||
<div class="pop-up-camera-title">足底压力板</div>
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||||
<div class="pop-up-camera-name">串口号</div>
|
||||
<el-select v-model="cameraForm.pressure.port" placeholder="请选择" style="width: 434px;">
|
||||
<el-option v-for="item in remotePortData" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-checkbox v-model="cameraForm.pressure.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 20px;padding-bottom: 00px;">
|
||||
<div class="pop-up-camera-line"></div>
|
||||
<div class="pop-up-camera-title">遥控器</div>
|
||||
</div>
|
||||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||||
<div class="pop-up-camera-name">串口号</div>
|
||||
<el-select v-model="cameraForm.remote.port" placeholder="请选择" style="width: 434px;">
|
||||
<el-option v-for="item in remotePortData" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-checkbox v-model="cameraForm.remote.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||||
|
||||
</div>
|
||||
<div class="form-actions-display">
|
||||
<el-button @click="handleCameraCancel" class="formreturnCancel">退出</el-button>
|
||||
<el-button type="primary" class="formsaveCancel"
|
||||
@ -960,6 +1004,84 @@ const isCloseCreat =ref(false) // 是否打开患者信息编辑框
|
||||
const isoperation = ref(false) // 是否保存数据
|
||||
const isVideoOperation = ref(false) // 是否录制视频
|
||||
const isTip =ref(false)
|
||||
const isSystemInitializing = ref(false)
|
||||
const initLoadingText = ref('系统设备正在初始化,请等待...')
|
||||
const initFailed = ref(false)
|
||||
const initErrorText = ref('')
|
||||
const initReadiness = ref(null)
|
||||
const notReadyDevices = computed(() => {
|
||||
const r = initReadiness.value
|
||||
const devices = r && r.devices ? r.devices : null
|
||||
if (!devices) return []
|
||||
const list = []
|
||||
Object.keys(devices).forEach((k) => {
|
||||
const v = devices[k]
|
||||
if (v && v.enabled !== false && v.ready === false) {
|
||||
list.push(v)
|
||||
}
|
||||
})
|
||||
return list
|
||||
})
|
||||
function getNotReadyReason(item) {
|
||||
if (!item) return '未知'
|
||||
if (item.enabled === false) return '未启用'
|
||||
if (!item.exists) return '设备实例未创建'
|
||||
if (item.initializing) return '初始化中'
|
||||
if (!item.is_connected) return '未连接'
|
||||
return '未就绪'
|
||||
}
|
||||
function getDeviceDisplayName(deviceName) {
|
||||
const name = String(deviceName || '').trim()
|
||||
const map = {
|
||||
camera1: '足部相机上',
|
||||
camera2: '足部相机下',
|
||||
femtobolt: '深度相机',
|
||||
imu: '头部姿态(IMU)',
|
||||
pressure: '足底压力板',
|
||||
remote: '遥控器',
|
||||
}
|
||||
return map[name] || name
|
||||
}
|
||||
function handleInitBack() {
|
||||
try {
|
||||
isSystemInitializing.value = false
|
||||
initFailed.value = false
|
||||
initErrorText.value = ''
|
||||
initReadiness.value = null
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
emit('endChange', false)
|
||||
} catch (e2) {
|
||||
}
|
||||
try {
|
||||
routeTo('/')
|
||||
} catch (e3) {
|
||||
}
|
||||
}
|
||||
async function handleInitProceed() {
|
||||
try {
|
||||
initLoadingText.value = '正在进入检测...'
|
||||
initFailed.value = false
|
||||
initErrorText.value = ''
|
||||
initReadiness.value = null
|
||||
isSystemInitializing.value = true
|
||||
await startDetection({ forceStart: true, silent: true })
|
||||
connectWebSocket()
|
||||
isSystemInitializing.value = false
|
||||
} catch (e) {
|
||||
const msg = String((e && e.message) ? e.message : (e || ''))
|
||||
const readiness = (e && e.readiness) ? e.readiness : null
|
||||
if (readiness) {
|
||||
initReadiness.value = readiness
|
||||
}
|
||||
initFailed.value = true
|
||||
initErrorText.value = msg || '进入检测失败'
|
||||
initLoadingText.value = '系统设备正在初始化,请等待...'
|
||||
isSystemInitializing.value = true
|
||||
ElMessage.error(`进入检测失败: ${initErrorText.value}`)
|
||||
}
|
||||
}
|
||||
const isStartVideo = ref(false)
|
||||
function startVideoClick() {
|
||||
startRecord()
|
||||
@ -1117,6 +1239,10 @@ const cameraForm = ref({ // 相机参数
|
||||
port: '', // 遥控器串口号
|
||||
enable: false
|
||||
},
|
||||
pressure:{
|
||||
port: '',
|
||||
enable: false
|
||||
},
|
||||
|
||||
})
|
||||
const calculatedAge = ref(null)
|
||||
@ -1359,8 +1485,13 @@ function cameraUpdate() { // 相机设置数据更新弹框
|
||||
enable: false
|
||||
},
|
||||
remote:{
|
||||
port: '', // IMU串口号
|
||||
}
|
||||
port: '',
|
||||
enable: false
|
||||
},
|
||||
pressure:{
|
||||
port: '',
|
||||
enable: false
|
||||
}
|
||||
}
|
||||
// 加载相机参数信息
|
||||
getDevicesInit()
|
||||
@ -2198,10 +2329,13 @@ async function sendDetectionData(data) {
|
||||
// }
|
||||
// }
|
||||
// 开始检测
|
||||
async function startDetection() {
|
||||
async function startDetection(options = {}) {
|
||||
try {
|
||||
console.log('🚀 正在开始检测...')
|
||||
|
||||
const forceStart = !!(options && options.forceStart)
|
||||
const silent = !!(options && options.silent)
|
||||
|
||||
// 调用后端API开始检测
|
||||
const response = await fetch(`${BACKEND_URL}/api/detection/start`, {
|
||||
method: 'POST',
|
||||
@ -2212,10 +2346,21 @@ async function startDetection() {
|
||||
patient_id: patientId.value,
|
||||
// 可以添加其他检测参数
|
||||
creator_id: creatorId.value,
|
||||
force_start: forceStart,
|
||||
})
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
let errorMsg = `HTTP ${response.status}: ${response.statusText}`
|
||||
let readiness = null
|
||||
try {
|
||||
const err = await response.json()
|
||||
readiness = (err && err.readiness) ? err.readiness : null
|
||||
errorMsg = (err && (err.error || err.message)) ? (err.error || err.message) : errorMsg
|
||||
} catch (e) {
|
||||
}
|
||||
const ex = new Error(errorMsg)
|
||||
ex.readiness = readiness
|
||||
throw ex
|
||||
}
|
||||
const result = await response.json()
|
||||
if (result.success) {
|
||||
@ -2228,7 +2373,9 @@ async function startDetection() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 开始检测失败:', error)
|
||||
ElMessage.error(`开始检测失败: ${error.message}`)
|
||||
if (!(options && options.silent)) {
|
||||
ElMessage.error(`开始检测失败: ${error.message}`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@ -2432,7 +2579,7 @@ const getDevicesInit = async () => {
|
||||
if (response.ok) {
|
||||
const result = await response.json()
|
||||
if (result.success) {
|
||||
console.log('相机参数加载成功:', result.data)
|
||||
console.log('设备参数加载成功:', result.data)
|
||||
cameraForm.value = result.data
|
||||
cameraDialogVisible.value = true
|
||||
// console.log('相机参数加载成功:', patientInfo.value)
|
||||
@ -2443,12 +2590,12 @@ const getDevicesInit = async () => {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载相机参数失败:', error)
|
||||
ElMessage.warning('加载相机参数失败,请检查网络连接')
|
||||
console.error('加载设备参数失败:', error)
|
||||
ElMessage.warning('加载设备参数失败,请检查网络连接')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
for(let i = 0; i < 20; i++){
|
||||
let port = "COM" + (i + 1)
|
||||
remotePortData.value.push(port)
|
||||
@ -2459,12 +2606,35 @@ onMounted(() => {
|
||||
}
|
||||
patientId.value = props.selectedPatient.id
|
||||
//patientId.value = '202511150005'
|
||||
// 加载患者信息
|
||||
loadPatientInfo()
|
||||
// 启动检测
|
||||
startDetection()
|
||||
// 页面加载时自动连接WebSocket
|
||||
connectWebSocket()
|
||||
try {
|
||||
isSystemInitializing.value = true
|
||||
initFailed.value = false
|
||||
initErrorText.value = ''
|
||||
initReadiness.value = null
|
||||
await loadPatientInfo()
|
||||
await startDetection()
|
||||
connectWebSocket()
|
||||
isSystemInitializing.value = false
|
||||
} catch (e) {
|
||||
const msg = String((e && e.message) ? e.message : (e || ''))
|
||||
const readiness = (e && e.readiness) ? e.readiness : null
|
||||
if (readiness) {
|
||||
initReadiness.value = readiness
|
||||
initFailed.value = true
|
||||
initErrorText.value = msg || '设备未就绪,请稍后重试'
|
||||
initLoadingText.value = '系统设备正在初始化,请等待...'
|
||||
isSystemInitializing.value = true
|
||||
ElMessage.error('设备初始化失败,请检查未就绪设备')
|
||||
return
|
||||
}
|
||||
isSystemInitializing.value = false
|
||||
if (msg) {
|
||||
ElMessage.error(msg)
|
||||
} else {
|
||||
ElMessage.error('进入检测失败')
|
||||
}
|
||||
handleInitBack()
|
||||
}
|
||||
|
||||
// 监听页面关闭或刷新事件
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
@ -3349,6 +3519,129 @@ function viewClick(e){
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.init-mask{
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.init-loading-container{
|
||||
width: 520px;
|
||||
max-width: calc(100vw - 40px);
|
||||
max-height: calc(100vh - 140px);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, rgba(53, 67, 90, 1) 0%, rgba(53, 67, 90, 1) 0%, rgba(62, 79, 105, 1) 99%, rgba(62, 79, 105, 1) 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: rgb(17, 24, 33) 0px 0px 10px;
|
||||
padding: 18px 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.init-loading-content{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.init-spinner{
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: rgba(59, 242, 198, 1);
|
||||
border-radius: 50%;
|
||||
animation: initSpin 0.9s linear infinite;
|
||||
}
|
||||
.init-loading-text{
|
||||
margin-top: 16px;
|
||||
font-family:'Noto Sans SC Bold', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||||
font-weight:700;
|
||||
font-style:normal;
|
||||
font-size:16px;
|
||||
color:#FFFFFF;
|
||||
text-align: center;
|
||||
}
|
||||
@keyframes initSpin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.init-failed-content{
|
||||
width: 100%;
|
||||
}
|
||||
.init-failed-title{
|
||||
font-family:'Noto Sans SC Bold', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||||
font-weight:700;
|
||||
font-style:normal;
|
||||
font-size:18px;
|
||||
color:#FFFFFF;
|
||||
text-align: left;
|
||||
}
|
||||
.init-failed-subtitle{
|
||||
margin-top: 8px;
|
||||
font-family:'Noto Sans SC', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||||
font-weight:400;
|
||||
font-style:normal;
|
||||
font-size:14px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
text-align: left;
|
||||
}
|
||||
.init-failed-list{
|
||||
margin-top: 14px;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.init-failed-item{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.init-failed-item:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
.init-failed-item-left{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.init-failed-item-name{
|
||||
font-family:'Noto Sans SC Bold', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||||
font-weight:700;
|
||||
font-style:normal;
|
||||
font-size:14px;
|
||||
color:#FFFFFF;
|
||||
}
|
||||
.init-failed-item-reason{
|
||||
margin-top: 4px;
|
||||
font-family:'Noto Sans SC', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||||
font-weight:400;
|
||||
font-style:normal;
|
||||
font-size:12px;
|
||||
color: rgba(59, 242, 198, 0.95);
|
||||
}
|
||||
.init-failed-item-right{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
font-family: 'Noto Sans SC';
|
||||
font-weight:400;
|
||||
font-style:normal;
|
||||
font-size:12px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
.init-failed-item-kv{
|
||||
white-space: nowrap;
|
||||
}
|
||||
.init-failed-actions{
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pop-up-tip-container {
|
||||
width: 400px;
|
||||
@ -3364,7 +3657,7 @@ function viewClick(e){
|
||||
}
|
||||
.pop-up-camera-container{
|
||||
width: 668px;
|
||||
height:630px;
|
||||
height:700px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
@ -426,7 +426,6 @@ const handleRegisterSubmit = async () => {
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
isLoading.value = true
|
||||
// 验证用户名
|
||||
if (!form.value.account) {
|
||||
showError('请输入登录账号!')
|
||||
@ -439,7 +438,9 @@ const handleLogin = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 网络访问测试(最多重试3次,每次延迟2秒)
|
||||
isLoading.value = true
|
||||
|
||||
// 网络访问测试(最多重试3次,短退避:200/400/800ms,总上限约 1.4s)
|
||||
let healthOk = false
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
try {
|
||||
@ -451,9 +452,11 @@ const handleLogin = async () => {
|
||||
// 后台返回非 healthy 状态
|
||||
if (attempt === 3) {
|
||||
showError('后台服务异常,请稍后重试!')
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
const delay = 200 * Math.pow(2, attempt - 1)
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
} catch (error) {
|
||||
// 捕获异常,等待后重试
|
||||
@ -462,7 +465,8 @@ const handleLogin = async () => {
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
const delay = 200 * Math.pow(2, attempt - 1)
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user