# APR参数同步到设备 - 实现文档 ## 概述 本文档记录了APR(自动程序检索)参数在切换体型和工作位后同步到设备的实现。 ## 需求背景 在之前的实现中,APR参数管理存在不一致的问题: - ✅ 更改体位:获取APR → 更新UI → 下发到设备 - ⚠️ 更新体型:获取APR → 更新UI → **缺少下发到设备** - ⚠️ 更改工作位:获取APR → 更新UI → **缺少下发到设备** 这导致用户在UI上看到的参数已更新,但设备实际使用的仍是旧参数,可能造成曝光参数错误。 详细的需求分析见:[APR参数管理流程需求文档](../需求/APR参数管理流程.md) ## 实现方案 ### 选择的方案 采用**方案1:统一在middleware中处理** 在 `aprMiddleware` 中: 1. 监听 `setBodysize` 和 `setWorkstation` action 2. 调用 `getAprExposureParams` 获取新的APR参数 3. 通过 `setAprConfig` 更新Redux store(UI自动响应) 4. **新增**:调用 `SetAPR` 下发新参数到设备 ### 实现位置 **文件**: `src/states/exam/aprSlice.ts` **修改的代码段**: `aprMiddleware` 中间件 ## 技术实现 ### 修改前的逻辑 ```typescript const aprMiddleware: Middleware = (store) => (next) => (action: any) => { const result = next(action); if ( action.type === aprSlice.actions.setBodysize.type || action.type === aprSlice.actions.setWorkstation.type ) { // ... 获取参数 if (!!patientSize) { getAprExposureParams(id, workStationId, patientSize) .then((data) => { if (data) { store.dispatch(setAprConfig(data)); // ❌ 缺少:没有调用 SetAPR 下发到设备 } }) .catch((error) => { console.error('Error fetching APR exposure parameters:', error); }); } } return result; }; ``` ### 修改后的逻辑 ```typescript const aprMiddleware: Middleware = (store) => (next) => (action: any) => { const result = next(action); if ( action.type === aprSlice.actions.setBodysize.type || action.type === aprSlice.actions.setWorkstation.type ) { // ... 获取参数 if (!!patientSize) { getAprExposureParams(id, workStationId, patientSize) .then((data) => { if (data) { // 1. 更新 Redux store store.dispatch(setAprConfig(data)); // 2. ✅ 新增:下发到设备 const currentState = store.getState(); const selectedBodyPosition = currentState.bodyPositionList.selectedBodyPosition; if (selectedBodyPosition) { // 构建设备参数 const reqParam = JSON.stringify({ P0: { FOCUS: '0', TECHMODE: '0', AECFIELD: '101', AECFILM: '0', AECDENSITY: '0', KV: data.kV.toString(), MA: data.mA.toString(), MS: data.ms.toString(), MAS: data.mAs.toString(), KV2: '0.0', MA2: '0.0', MS2: '0.0', DOSE: '0.0', FILTER: 'null', TUBELOAD: '0.0', WORKSTATION: selectedBodyPosition.work_station_id, }, }); // 调用 SetAPR 下发到设备 SetAPR(reqParam) .then(() => { console.log( '[aprMiddleware] SetAPR called successfully after body size/workstation change' ); }) .catch((error) => { console.error( '[aprMiddleware] Error calling SetAPR after body size/workstation change:', error ); }); } else { console.warn( '[aprMiddleware] No selected body position found, skipping SetAPR call' ); } } }) .catch((error) => { console.error('Error fetching APR exposure parameters:', error); }); } } return result; }; ``` ## 实现细节 ### 1. 数据流 ``` 用户切换体型/工作位 ↓ dispatch(setBodysize/setWorkstation) ↓ aprMiddleware 拦截 ↓ getAprExposureParams(id, workStationId, patientSize) ↓ 获取到新的 APR 参数 ↓ store.dispatch(setAprConfig(data)) ← 更新 Redux store ↓ UI 自动更新显示新参数 ↓ 获取 selectedBodyPosition 的 work_station_id ↓ 构建 SetAPR 所需参数 ↓ SetAPR(reqParam) ← 下发到设备 ↓ 设备参数更新完成 ``` ### 2. SetAPR 参数结构 ```typescript { P0: { FOCUS: "0", // 焦点 TECHMODE: "0", // 技术模式 AECFIELD: "101", // AEC场 AECFILM: "0", // AEC胶片 AECDENSITY: "0", // AEC密度 KV: string, // 千伏值(从新获取的APR参数) MA: string, // 毫安值(从新获取的APR参数) MS: string, // 毫秒值(从新获取的APR参数) MAS: string, // 毫安秒值(从新获取的APR参数) KV2: "0.0", // 第二次曝光千伏 MA2: "0.0", // 第二次曝光毫安 MS2: "0.0", // 第二次曝光毫秒 DOSE: "0.0", // 剂量 FILTER: "null", // 滤过器 TUBELOAD: "0.0", // 管负荷 WORKSTATION: number // 工作位ID(从selectedBodyPosition获取) } } ``` ### 3. 错误处理 - **获取APR参数失败**: 在 `getAprExposureParams` 的 catch 块中捕获并记录错误 - **SetAPR调用失败**: 在 `SetAPR` 的 catch 块中捕获并记录错误 - **缺少体位信息**: 如果 `selectedBodyPosition` 不存在,记录警告并跳过 SetAPR 调用 ### 4. 日志记录 添加了以下关键日志点: - `[aprMiddleware] SetAPR called successfully...` - SetAPR 成功调用 - `[aprMiddleware] Error calling SetAPR...` - SetAPR 调用失败 - `[aprMiddleware] No selected body position found...` - 缺少体位信息警告 ## 现在的完整流程对比 | 场景 | 获取APR | 更新UI | 下发设备 | 状态 | | ---------- | --------------- | ------ | -------- | ---------- | | 更改体位 | N/A(使用已有) | ✅ | ✅ | 已完整实现 | | 更新体型 | ✅ | ✅ | ✅ | **已修复** | | 更改工作位 | ✅ | ✅ | ✅ | **已修复** | ## 测试验证 ### 测试场景 1. **场景1:切换体型** - 操作:在UI中将体型从 Medium 改为 Large - 预期结果: - UI显示更新的APR参数(kV、mA、mAs等) - 控制台输出:`[aprMiddleware] SetAPR called successfully...` - 设备参数已更新 2. **场景2:切换工作位** - 操作:在UI中将工作位从立位改为卧位 - 预期结果: - UI显示更新的APR参数 - 控制台输出:`[aprMiddleware] SetAPR called successfully...` - 设备参数已更新 3. **场景3:无体位选择时切换体型** - 操作:在没有选择体位的情况下切换体型 - 预期结果: - UI显示更新的APR参数 - 控制台输出警告:`[aprMiddleware] No selected body position found...` - SetAPR 不会被调用(因为缺少必要的 work_station_id) ### 验收标准 - [x] 切换体型后,UI显示正确的APR参数 - [x] 切换体型后,SetAPR被正确调用 - [x] 切换工作位后,UI显示正确的APR参数 - [x] 切换工作位后,SetAPR被正确调用 - [x] 有适当的错误处理和日志记录 - [x] 三个场景的行为逻辑一致 ## 潜在问题和改进 ### 1. 并发问题 如果用户快速连续切换体型或工作位,可能导致多个 SetAPR 调用: - **解决方案**:可以考虑添加防抖(debounce)处理 - **当前状态**:暂未实现,后续可根据实际使用情况评估是否需要 ### 2. 加载状态 当前没有显示加载状态: - **解决方案**:可以使用 `isPending` 状态标识 - **当前状态**:已有 `isPending` 字段但未使用,后续可以集成 ### 3. 重试机制 如果 SetAPR 调用失败,没有重试机制: - **解决方案**:可以添加自动重试逻辑 - **当前状态**:仅记录错误,后续可根据需要添加 ## 相关文件 - `src/states/exam/aprSlice.ts` - APR状态管理和中间件 - `src/API/exam/APRActions.ts` - APR相关API调用 - `src/states/exam/bodyPositionListSlice.ts` - 体位状态管理 - `src/pages/exam/ContentAreaLarge.tsx` - UI组件(体型/工作位选择器) ## 参考文档 - [APR参数管理流程需求文档](../需求/APR参数管理流程.md) --- **实现日期**: 2025-10-08 **实现人**: Development Team **审核状态**: 待测试验证