Просмотр исходного кода

feat(exam->device): display detector status with corresponding text color based on status

sw 1 месяц назад
Родитель
Сommit
7c7b13b567

+ 28 - 0
src/domain/mqttServiceForDevice.ts

@@ -34,6 +34,7 @@ interface MqttMessage {
 
 const MQTT_TOPIC_2 = 'CCOS/DEVICE/Generator/Notify/GENERATORSYNCSTATE'; //发生器
 const MQTT_TOPIC = 'CCOS/DEVICE/Detector/Notify/XrayON';
+const MQTT_TOPIC_DETECTOR = 'CCOS/DEVICE/Detector/Notify/DetectorStatus'; //探测器
 
 const options = {
   clean: true,
@@ -50,6 +51,21 @@ const handleMqttMessage = (message: MqttMessage) => {
     emitter.emit('ACQUISITION_STARTED');
   }
 };
+const handleMqttMessageFromDetector = (message: MqttMessage) => {
+  console.log(
+    `[mqttServiceForDevice] 收到来自于Detector 的 message.CONTEXT ${message.CONTEXT}`
+  );
+  if (message.CONTEXT === '4') {
+    //探测器状态会被通知为4(表示准备就绪)
+    emitter.emit('DETECTOR_ACQUISITION_STARTED');
+  } else if (message.CONTEXT === '5') {
+    //探测器状态首先被通知为5(表示正在采集)
+    emitter.emit('DETECTOR_ACQUISITION_INPROGRESS');
+  } else {
+    //探测器出错
+    emitter.emit('DETECTOR_ACQUISITION_ERROR');
+  }
+};
 
 const startListening = () => {
   mqttClient = mqtt.connect(MQTT_BROKER_URL, options);
@@ -71,6 +87,14 @@ const startListening = () => {
         );
       }
     });
+    mqttClient.subscribe(MQTT_TOPIC_DETECTOR, (err) => {
+      if (err) {
+        console.error(
+          '[mqttServiceForDevice] Failed to subscribe to topic MQTT_TOPIC_DETECTOR',
+          err
+        );
+      }
+    });
   });
 
   mqttClient.on('error', (error) => {
@@ -86,6 +110,10 @@ const startListening = () => {
       const parsedMessage: MqttMessage = JSON.parse(message.toString());
       handleMqttMessage(parsedMessage);
     }
+    if (topic === MQTT_TOPIC_DETECTOR) {
+      const parsedMessage: MqttMessage = JSON.parse(message.toString());
+      handleMqttMessageFromDetector(parsedMessage);
+    }
   });
 };
 

+ 33 - 5
src/pages/exam/DeviceArea.tsx

@@ -4,18 +4,46 @@ import {
   CameraOutlined,
   TabletOutlined,
 } from '@ant-design/icons';
-import triggerInspection from '../../API/exam/triggerInspection';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/states/store';
 
 const DeviceArea = ({ className }: { className?: string }) => {
+  const generatorStatus = useSelector(
+    (state: RootState) => state.deviceArea.generatorStatus
+  );
+  const exposureStatus = useSelector(
+    (state: RootState) => state.deviceArea.exposureStatus
+  );
+  const tabletStatus = useSelector(
+    (state: RootState) => state.deviceArea.tabletStatus
+  );
+
   return (
     <Flex justify="end" align="center" className={`w-full ${className}`}>
-      <Button icon={<ToolOutlined />} title="发生器状态指示器" />
+      <Button
+        icon={<ToolOutlined className="text-red-500" />}
+        title={`发生器状态指示器: ${generatorStatus}`}
+      />
       <Button
         icon={<CameraOutlined />}
-        title="曝光指示器"
-        onClick={triggerInspection}
+        title={`曝光指示器: ${exposureStatus}`}
+      />
+      <Button
+        icon={
+          <TabletOutlined
+            className={
+              tabletStatus === 'exposing'
+                ? 'text-yellow-500'
+                : tabletStatus === 'ready'
+                  ? 'text-green-500'
+                  : tabletStatus === 'error'
+                    ? 'text-red-500'
+                    : ''
+            }
+          />
+        }
+        title={`平板指示器: ${tabletStatus}`}
       />
-      <Button icon={<TabletOutlined />} title="平板指示器" />
     </Flex>
   );
 };

+ 76 - 0
src/states/exam/deviceAreaSlice.ts

@@ -0,0 +1,76 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import emitter from '../../utils/eventEmitter';
+
+enum GeneratorStatus {
+  GENERATOR_SYNC_ERR = -1, // 错误状态
+  GENERATOR_RAD_OFF, // 手闸抬起
+  GENERATOR_RAD_PREPARE, // 手闸一档按下
+  GENERATOR_RAD_READY, // 手闸二档按下
+  GENERATOR_RAD_XRAYON, // 发生器点片出线
+  GENERATOR_RAD_XRAYOFF, // 发生器点片结束出线
+  GENERATOR_FLU_OFF, // 脚闸抬起
+  GENERATOR_FLU_READY, // 脚闸踩下
+  GENERATOR_FLU_XRAYON, // 透视出线
+  GENERATOR_FLU_XRAYOFF, // 透视结束出线
+  GENERATOR_FLU_CINE_READY, // 电影模式准备
+  GENERATOR_SYNC_MAX, // 状态最大值(枚举边界)
+}
+
+interface DeviceAreaState {
+  generatorStatus: GeneratorStatus;
+  exposureStatus: 'ready' | 'not_ready';
+  tabletStatus: 'exposing' | 'ready' | 'error';
+}
+
+const initialState: DeviceAreaState = {
+  generatorStatus: GeneratorStatus.GENERATOR_RAD_OFF, // Default to idle state
+  exposureStatus: 'not_ready',
+  tabletStatus: 'ready',
+};
+
+const deviceAreaSlice = createSlice({
+  name: 'deviceArea',
+  initialState,
+  reducers: {
+    setGeneratorStatus: (state, action: PayloadAction<GeneratorStatus>) => {
+      state.generatorStatus = action.payload;
+    },
+    setExposureStatus: (
+      state,
+      action: PayloadAction<'ready' | 'not_ready'>
+    ) => {
+      state.exposureStatus = action.payload;
+    },
+    setTabletStatus: (
+      state,
+      action: PayloadAction<'exposing' | 'ready' | 'error'>
+    ) => {
+      state.tabletStatus = action.payload;
+    },
+  },
+});
+
+emitter.on('DETECTOR_ACQUISITION_STARTED', () => {
+  deviceAreaSlice.caseReducers.setTabletStatus(
+    deviceAreaSlice.getInitialState(),
+    { type: 'setTabletStatus', payload: 'ready' }
+  );
+});
+
+emitter.on('DETECTOR_ACQUISITION_INPROGRESS', () => {
+  deviceAreaSlice.caseReducers.setTabletStatus(
+    deviceAreaSlice.getInitialState(),
+    { type: 'setTabletStatus', payload: 'exposing' }
+  );
+});
+
+emitter.on('DETECTOR_ACQUISITION_ERROR', () => {
+  deviceAreaSlice.caseReducers.setTabletStatus(
+    deviceAreaSlice.getInitialState(),
+    { type: 'setTabletStatus', payload: 'error' }
+  );
+});
+
+export const { setGeneratorStatus, setExposureStatus, setTabletStatus } =
+  deviceAreaSlice.actions;
+export default deviceAreaSlice.reducer;

+ 2 - 0
src/states/store.ts

@@ -34,6 +34,7 @@ import {
 } from './patient/worklist/slices/history';
 import generatorMonitorReducer from './exam/generatorMonitorSlice';
 import largeScreenReducer from './exam/largeScreenSlice';
+import deviceAreaReducer from './exam/deviceAreaSlice';
 
 const store = configureStore({
   reducer: {
@@ -63,6 +64,7 @@ const store = configureStore({
     search: searchReducer,
     generatorMonitor: generatorMonitorReducer,
     largeScreen: largeScreenReducer,
+    deviceArea: deviceAreaReducer,
   },
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware().concat(