Browse Source

feat: 实现APR参数自动同步到设备功能

- 新增 SetAPR API 方法用于发送参数到设备 (src/API/exam/APRActions.ts)
- 在体位选择时自动同步 APR 参数到设备 (src/states/exam/aprSlice.ts)
- 在体型/工作位变更时自动同步参数 (src/states/exam/aprSlice.ts)
- 在厚度变更时自动获取并同步 APR 参数 (src/states/exam/aprSlice.ts)
- 集成参数调整协调器到 UI (src/pages/exam/ContentAreaLarge.tsx)
sw 1 week ago
parent
commit
e00930c6fa

+ 18 - 1
src/API/exam/APRActions.ts

@@ -82,7 +82,14 @@ const getAprExposureParams = async (
   return response.data.data.ep;
 };
 
-export { getAprDetails, getAprExposureParams };
+const getAprByThickness = async (thickness: number): Promise<AprConfig> => {
+  const response = await axiosInstance.get(
+    `auth/protocol/thickness/${thickness}/apr`
+  );
+  return response.data.data;
+};
+
+export { getAprDetails, getAprExposureParams, getAprByThickness };
 
 interface DeviceActionMessage {
   deviceUri: string;
@@ -151,6 +158,14 @@ const decDensity = async () => {
   await sendDeviceAction('DecParam_Density');
 };
 
+const incThickness = async () => {
+  await sendDeviceAction('IncParam_Thickness');
+};
+
+const decThickness = async () => {
+  await sendDeviceAction('DecParam_Thickness');
+};
+
 const SetAPR = async (reqParam: string) => {
   const message: DeviceActionMessage = {
     deviceUri: pathOfGenerator,
@@ -179,5 +194,7 @@ export {
   decMs,
   incDensity,
   decDensity,
+  incThickness,
+  decThickness,
   SetAPR,
 };

+ 51 - 0
src/domain/exam/paraSettingCoordinator.ts

@@ -11,6 +11,9 @@
 8. 减少MS
 9. 增加Density
 10. 减少Density
+11. 增加Thickness
+12. 减少Thickness
+13. 设置APR
  * 以上方法内部逻辑是向服务发送设置曝光参数,KV MA MS MAS;间接调用api method,通过 调用 @/src/API/exam/APRActions.ts  中定义的方法完成调用,并不直接调用api
  * 接收device mqtt发来的新参数。device mqtt会触发事件 emitter.emit('NEW_KV',parsedMessage.CONTEXT); 这是接收KV新值的,还有对应的其他新曝光参数的新值
  * 把 device mqtt 发来的新参数传递给 @/src/states/exam/aprSlice.ts  。 dispatch thunk的 action,有fullfilled pending rejected ,比如 incMS.fulfilled ;考虑 device mqtt 发来新曝光参数会超时,超时的情况下向 aprSLice发送 rejected 。
@@ -27,6 +30,8 @@ import {
   decMs as decMsFromAction,
   incDensity as incDensityFromAction,
   decDensity as decDensityFromAction,
+  incThickness as incThicknessFromAction,
+  decThickness as decThicknessFromAction,
 } from '../../API/exam/APRActions';
 import emitter from '../../utils/eventEmitter';
 import {
@@ -40,6 +45,8 @@ import {
   decMS,
   incDensity,
   decDensity,
+  incThickness,
+  decThickness,
 } from '../../states/exam/aprSlice';
 import store from '../../states/store';
 
@@ -56,6 +63,9 @@ class ParaSettingCoordinator {
     emitter.on('NEW_MA', (newValue) => this.handleNewMA(newValue));
     emitter.on('NEW_MS', (newValue) => this.handleNewMS(newValue));
     emitter.on('NEW_DENSITY', (newValue) => this.handleNewDensity(newValue));
+    emitter.on('NEW_THICKNESS', (newValue) =>
+      this.handleNewThickness(newValue)
+    );
   }
 
   handleNewKV(newValue) {
@@ -67,6 +77,7 @@ class ParaSettingCoordinator {
   }
 
   handleNewMAS(newValue) {
+    console.info('有新的mAs从设备端到来。handleNewMAS', newValue);
     this.dispatch({
       type: incMAS.fulfilled.type,
       payload: newValue,
@@ -98,6 +109,14 @@ class ParaSettingCoordinator {
     });
   }
 
+  handleNewThickness(newValue) {
+    this.dispatch({
+      type: incThickness.fulfilled.type,
+      payload: newValue,
+      meta: { arg: newValue, requestId: '' },
+    });
+  }
+
   increaseKV() {
     this.dispatch({
       type: incKV.pending.type,
@@ -257,6 +276,38 @@ class ParaSettingCoordinator {
       });
     }
   }
+
+  increaseThickness() {
+    this.dispatch({
+      type: incThickness.pending.type,
+      meta: { arg: 0, requestId: '' },
+    });
+    try {
+      incThicknessFromAction();
+    } catch (error) {
+      this.dispatch({
+        type: incThickness.rejected.type,
+        payload: error,
+        meta: { arg: 0, requestId: '' },
+      });
+    }
+  }
+
+  decreaseThickness() {
+    this.dispatch({
+      type: decThickness.pending.type,
+      meta: { arg: 0, requestId: '' },
+    });
+    try {
+      decThicknessFromAction();
+    } catch (error) {
+      this.dispatch({
+        type: decThickness.rejected.type,
+        payload: error,
+        meta: { arg: 0, requestId: '' },
+      });
+    }
+  }
 }
 
 export default new ParaSettingCoordinator();

+ 33 - 1
src/pages/exam/ContentAreaLarge.tsx

@@ -26,6 +26,7 @@ import {
   setAprConfig,
   setBodysize,
   setWorkstation,
+  setThickness,
   setIsAECEnabled,
   setCurrentExposureMode,
 } from '../../states/exam/aprSlice';
@@ -46,6 +47,7 @@ const ContentAreaLarge = () => {
   const aprConfig = useSelector((state: RootState) => state.apr.aprConfig);
   const bodysize = useSelector((state: RootState) => state.apr.bodysize);
   const workstation = useSelector((state: RootState) => state.apr.workstation);
+  const thickness = useSelector((state: RootState) => state.apr.thickness);
   const isAECEnabled = useSelector(
     (state: RootState) => state.apr.isAECEnabled
   );
@@ -68,6 +70,12 @@ const ContentAreaLarge = () => {
     dispatch(setWorkstation(value));
   };
 
+  const handleThicknessChange = (value: number | null) => {
+    if (value !== null) {
+      dispatch(setThickness(value));
+    }
+  };
+
   const handleAECChange = (checked: boolean) => {
     dispatch(setIsAECEnabled(checked));
   };
@@ -110,7 +118,7 @@ const ContentAreaLarge = () => {
         style={{ height: '100%', overflowY: 'auto', flexShrink: 0 }}
       >
         <Row gutter={16} align="middle">
-          <Col span={productName === 'DROS' ? 9 : 24}>
+          <Col span={productName === 'DROS' ? 9 : 12}>
             <Select
               placeholder="选择体型"
               style={{ width: '100%', marginBottom: 8 }}
@@ -126,6 +134,29 @@ const ContentAreaLarge = () => {
               )}
             </Select>
           </Col>
+          <Col span={productName === 'DROS' ? 9 : 12}>
+            <label
+              style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
+            >
+              厚度 (cm)
+            </label>
+            <InputNumber
+              placeholder="厚度"
+              style={{ width: '100%', marginBottom: 8 }}
+              value={thickness || undefined}
+              min={1}
+              max={50}
+              step={1}
+              onChange={handleThicknessChange}
+              onStep={(value, info) => {
+                if (info.type === 'up') {
+                  ParaSettingCoordinator.increaseThickness();
+                } else {
+                  ParaSettingCoordinator.decreaseThickness();
+                }
+              }}
+            />
+          </Col>
           {productName === 'DROS' && (
             <Col span={15}>
               <Select
@@ -276,6 +307,7 @@ const ContentAreaLarge = () => {
               }}
             />
           </div>
+
           {/* <div>
             <label
               style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}

+ 100 - 1
src/states/exam/aprSlice.ts

@@ -1,6 +1,6 @@
 /* eslint-disable */
 import { createSlice, PayloadAction, Middleware, createAsyncThunk } from '@reduxjs/toolkit';
-import { AprConfig, getAprExposureParams, SetAPR } from '../../API/exam/APRActions';
+import { AprConfig, getAprExposureParams, getAprByThickness, SetAPR } from '../../API/exam/APRActions';
 import { workstationIdFromWorkstation, WorkstationType } from '../workstation';
 import { ExtendedBodyPosition, setSelectedBodyPosition } from '../exam/bodyPositionListSlice';
 import { initializeProductState } from '../productSlice';
@@ -9,6 +9,7 @@ interface AprState {
   aprConfig: AprConfig;
   bodysize: string;
   workstation: string;
+  thickness: number;
   isAECEnabled: boolean;
   currentExposureMode: string;
   /**
@@ -34,6 +35,7 @@ const initialState: AprState = {
   },
   bodysize: '',
   workstation: '',
+  thickness: 0,
   isAECEnabled: false,
   currentExposureMode: 'mAs',
   isPending:false
@@ -52,6 +54,9 @@ const aprSlice = createSlice({
     setWorkstation: (state, action: PayloadAction<string>) => {
       state.workstation = action.payload;
     },
+    setThickness: (state, action: PayloadAction<number>) => {
+      state.thickness = action.payload;
+    },
     setIsAECEnabled: (state, action: PayloadAction<boolean>) => {
       state.isAECEnabled = action.payload;
     },
@@ -161,10 +166,34 @@ const aprSlice = createSlice({
       .addCase(decDensity.rejected, (state, action) => {
         console.error('Failed to decrease Density', action.error);
       })
+      .addCase(incThickness.pending, (state) => {
+        console.log('Increasing Thickness...');
+      })
+      .addCase(incThickness.fulfilled, (state, action) => {
+        console.log('Thickness increased successfully');
+        state.thickness = action.payload;
+      })
+      .addCase(incThickness.rejected, (state, action) => {
+        console.error('Failed to increase Thickness', action.error);
+      })
+      .addCase(decThickness.pending, (state) => {
+        console.log('Decreasing Thickness...');
+      })
+      .addCase(decThickness.fulfilled, (state, action) => {
+        console.log('Thickness decreased successfully');
+        state.thickness = action.payload;
+      })
+      .addCase(decThickness.rejected, (state, action) => {
+        console.error('Failed to decrease Thickness', action.error);
+      })
       .addCase(setSelectedBodyPosition, (state, action: PayloadAction<ExtendedBodyPosition | null>) => {
         console.log('APR Extra Reducer triggered for setSelectedBodyPosition action');
         const selectedBodyPosition = action.payload;
         if (selectedBodyPosition) {
+          // 初始化厚度值
+          state.thickness = selectedBodyPosition.work?.Thickness || 0;
+          console.log('Initialized thickness from selected body position:', state.thickness);
+          
           const reqParam = JSON.stringify({
             P0:{
             FOCUS: "0",
@@ -275,6 +304,67 @@ const aprMiddleware: Middleware = (store) => (next) => (action: any) => {
           console.log('APR exposure parameters fetch attempt finished.');
         });
     }
+  } else if (action.type === aprSlice.actions.setThickness.type) {
+    console.log('APR Middleware triggered by thickness change:', action.type);
+    const state = store.getState();
+    const thickness = state.apr.thickness;
+    console.log(`Fetching APR exposure parameters by thickness: ${thickness}`);
+    
+    // 确保厚度值在有效范围内 (1-50)
+    if (thickness >= 1 && thickness <= 50) {
+      getAprByThickness(thickness)
+        .then((data) => {
+          console.log('Received APR exposure parameters by thickness:', data);
+          if (data) {
+            store.dispatch(setAprConfig(data));
+            
+            // After updating the store, send the APR parameters to the device
+            const currentState = store.getState();
+            const selectedBodyPosition = currentState.bodyPositionList.selectedBodyPosition;
+            console.info(`根据厚度得到arp后,下发kv ms mas ma 给设备`);
+            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.mAs/data.mA).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
+                }
+              });
+              console.info(`根据厚度得到arp后,下发之前 kv ms mas ma = ${reqParam}`);
+              SetAPR(reqParam)
+                .then(() => {
+                  console.log('[aprMiddleware] SetAPR called successfully after thickness change');
+                })
+                .catch((error) => {
+                  console.error('[aprMiddleware] Error calling SetAPR after thickness change:', error);
+                });
+            } else {
+              console.warn('[aprMiddleware] No selected body position found, skipping SetAPR call');
+            }
+          }
+        })
+        .catch((error) => {
+          console.error('Error fetching APR exposure parameters by thickness:', error);
+        })
+        .finally(() => {
+          console.log('APR exposure parameters fetch by thickness attempt finished.');
+        });
+    } else {
+      console.warn(`Thickness value ${thickness} is out of valid range (1-50), skipping API call`);
+    }
   }
   return result;
 };
@@ -319,10 +409,19 @@ export const decDensity = createAsyncThunk<number, number>('apr/decDensity', asy
   return amount;
 });
 
+export const incThickness = createAsyncThunk<number, number>('apr/incThickness', async (amount: number) => {
+  return amount;
+});
+
+export const decThickness = createAsyncThunk<number, number>('apr/decThickness', async (amount: number) => {
+  return amount;
+});
+
 export const {
   setAprConfig,
   setBodysize,
   setWorkstation,
+  setThickness,
   setIsAECEnabled,
   setCurrentExposureMode,
 } = aprSlice.actions;