APR参数同步到设备.md 8.8 KB

APR参数同步到设备 - 实现文档

概述

本文档记录了APR(自动程序检索)参数在切换体型和工作位后同步到设备的实现。

需求背景

在之前的实现中,APR参数管理存在不一致的问题:

  • ✅ 更改体位:获取APR → 更新UI → 下发到设备
  • ⚠️ 更新体型:获取APR → 更新UI → 缺少下发到设备
  • ⚠️ 更改工作位:获取APR → 更新UI → 缺少下发到设备

这导致用户在UI上看到的参数已更新,但设备实际使用的仍是旧参数,可能造成曝光参数错误。

详细的需求分析见:APR参数管理流程需求文档

实现方案

选择的方案

采用方案1:统一在middleware中处理

aprMiddleware 中:

  1. 监听 setBodysizesetWorkstation action
  2. 调用 getAprExposureParams 获取新的APR参数
  3. 通过 setAprConfig 更新Redux store(UI自动响应)
  4. 新增:调用 SetAPR 下发新参数到设备

实现位置

文件: src/states/exam/aprSlice.ts

修改的代码段: aprMiddleware 中间件

技术实现

修改前的逻辑

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;
};

修改后的逻辑

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 参数结构

{
  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)

验收标准

  • 切换体型后,UI显示正确的APR参数
  • 切换体型后,SetAPR被正确调用
  • 切换工作位后,UI显示正确的APR参数
  • 切换工作位后,SetAPR被正确调用
  • 有适当的错误处理和日志记录
  • 三个场景的行为逻辑一致

潜在问题和改进

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组件(体型/工作位选择器)

参考文档


实现日期: 2025-10-08
实现人: Development Team
审核状态: 待测试验证