|
@@ -0,0 +1,290 @@
|
|
|
+# 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
|
|
|
+**审核状态**: 待测试验证
|