Explorar o código

feat (1.37.0 -> 1.38.0): 实现发生器就绪状态管理和宠物专用测量功能条件显示

- 在 mqttService.ts 中添加 GENReady 消息处理,发送 GENStatus_TRUE/FALSE 事件
- 在 deviceAreaSlice.ts 中添加 generatorReady 状态管理,支持 setgeneratorReady action
- 在 DeviceArea.tsx 中简化发生器图标逻辑,基于 generatorReady 状态显示颜色
- 在 MeasurementPanel.tsx 中条件化显示宠物专用测量功能,仅在 VETDROS 产品中显示

改动文件:
- src/domain/mqttService.ts
- src/pages/exam/DeviceArea.tsx
- src/pages/view/components/MeasurementPanel.tsx
- src/states/exam/deviceAreaSlice.ts
dengdx hai 2 semanas
pai
achega
a3cfbd57a7

+ 25 - 0
CHANGELOG.md

@@ -2,6 +2,31 @@
 
 本项目的所有重要变更都将记录在此文件中。
 
+## [1.38.0] - 2025-12-31 14:07
+
+### 新增 (Added)
+
+- **发生器就绪状态管理和宠物专用测量功能条件显示** - 实现发生器状态监听和UI条件化显示
+  - 在 mqttService.ts 中添加 GENReady 消息处理,发送 GENStatus_TRUE/FALSE 事件
+  - 在 deviceAreaSlice.ts 中添加 generatorReady 状态管理,支持 setgeneratorReady action
+  - 在 DeviceArea.tsx 中简化发生器图标逻辑,基于 generatorReady 状态显示颜色
+  - 在 MeasurementPanel.tsx 中条件化显示宠物专用测量功能,仅在 VETDROS 产品中显示
+
+**核心功能实现:**
+
+- 发生器状态监听:MQTT 服务监听 GENReady 字段,实时更新设备就绪状态
+- UI 状态同步:设备区域组件根据 generatorReady 状态动态调整图标颜色
+- 产品功能区分:测量面板根据产品名称条件渲染宠物专用测量工具
+- 状态管理扩展:Redux slice 新增 generatorReady 状态,支持事件驱动更新
+
+**改动文件:**
+
+- src/domain/mqttService.ts
+- src/pages/exam/DeviceArea.tsx
+- src/pages/view/components/MeasurementPanel.tsx
+- src/states/exam/deviceAreaSlice.ts
+- package.json (版本更新: 1.37.0 -> 1.38.0)
+
 ## [1.37.0] - 2025-12-31 13:06
 
 ### 新增 (Added)

+ 4 - 4
config/dev.ts

@@ -81,16 +81,16 @@ export default {
     devServer: {
       proxy: {
         '/dr': {
-          target: 'http://192.168.110.13:6001', // 你的后端服务地址
-          // target: 'http://192.168.110.245:6001',
+          // target: 'http://192.168.110.13:6001', // 你的后端服务地址
+          target: 'http://192.168.110.245:6001',
           changeOrigin: true, // 允许跨域
           // pathRewrite: {
           //   '^/dr/api': '' // 可选,用于重写路径
           // }
         },
         '/mqtt': {
-          target: 'ws://192.168.110.13:8083', // MQTT WebSocket 服务地址
-          // target: 'ws://192.168.110.245:8083',
+          // target: 'ws://192.168.110.13:8083', // MQTT WebSocket 服务地址
+          target: 'ws://192.168.110.245:8083',
           changeOrigin: true,
           ws: true, // 启用 WebSocket 代理
           // pathRewrite: {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.37.0",
+  "version": "1.38.0",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 15 - 8
src/domain/mqttService.ts

@@ -68,7 +68,7 @@ const startListening = async () => {
 
     const MQTT_BROKER_URL = getMqttBrokerUrl();
     const timestamp = () => new Date().toISOString();
-    
+
     console.log(`[${timestamp()}] [mqttService] startListening with broker: ${MQTT_BROKER_URL}`);
 
     // 清理旧连接
@@ -87,7 +87,7 @@ const startListening = async () => {
     }
 
     isConnecting = true;
-    
+
     mqttClient = mqtt.connect(MQTT_BROKER_URL, options);
     mqttClient.on('connect', (connack) => {
       isConnecting = false;
@@ -96,10 +96,10 @@ const startListening = async () => {
         sessionPresent: connack.sessionPresent,
         returnCode: connack.returnCode
       });
-      
+
       // 发送连接成功事件到应用层
       emitter.emit('MQTT_CONNECTED');
-      
+
       mqttClient.subscribe(MQTT_TOPIC, (err) => {
         if (err) {
           console.error('[mqttService] Failed to subscribe to topic', err);
@@ -148,7 +148,7 @@ const startListening = async () => {
         code: error.code,
         stack: error.stack?.split('\n')[0] // 只记录第一行堆栈
       });
-      
+
       // 发送错误事件到应用层
       emitter.emit('MQTT_ERROR', {
         message: error.message,
@@ -168,6 +168,13 @@ const startListening = async () => {
         } else {
           emitter.emit('AllReady_FALSE');
         }
+        // 发生器
+        if (parsedMessage.GENReady) {
+          console.log('[mqttService] 发生器就绪');
+          emitter.emit('GENStatus_TRUE');
+        } else {
+          emitter.emit('GENStatus_FALSE');
+        }
       }
     });
   } catch (error) {
@@ -177,14 +184,14 @@ const startListening = async () => {
 
 const stopListening = () => {
   const timestamp = () => new Date().toISOString();
-  
+
   if (!mqttClient) {
     console.log(`[${timestamp()}] [mqttService] 没有活跃的连接需要关闭`);
     return;
   }
-  
+
   console.log(`[${timestamp()}] [mqttService] 正在停止监听...`);
-  
+
   try {
     // 移除所有事件监听器
     mqttClient.removeAllListeners();

+ 9 - 13
src/pages/exam/DeviceArea.tsx

@@ -19,6 +19,10 @@ const DeviceArea = ({ className }: { className?: string }) => {
   const generatorStatus_2 = useSelector(
     (state: RootState) => state.deviceArea.generatorStatus_2
   );
+  // 只关注发生器的ready状态
+  const generatorReady = useSelector(
+    (state: RootState) => state.deviceArea.generatorReady
+  );
   const exposureStatus = useSelector(
     (state: RootState) => state.deviceArea.exposureStatus
   );
@@ -41,13 +45,7 @@ const DeviceArea = ({ className }: { className?: string }) => {
         icon={
           <ToolOutlined
             className={
-              generatorStatus_2 === GENERATOR_STATUS.GENERATOR_STATUS_STANDBY
-                ? 'text-green-500'
-                : generatorStatus === GeneratorStatus.GENERATOR_RAD_PREPARE
-                  ? 'text-yellow-500'
-                  : generatorStatus === GeneratorStatus.GENERATOR_RAD_READY
-                    ? 'text-yellow-500'
-                    : ''
+              generatorReady ? 'text-green-500' : 'text-yellow-500'
             }
           />
         }
@@ -57,13 +55,12 @@ const DeviceArea = ({ className }: { className?: string }) => {
         <Button
           style={btnStyle}
           data-testid="device-all-ready"
-          className={`${classValue} ${
-            exposureStatus === 'ready'
+          className={`${classValue} ${exposureStatus === 'ready'
               ? 'text-green-500'
               : exposureStatus === 'not_ready'
                 ? ''
                 : ''
-          }`}
+            }`}
           icon={
             <CameraOutlined
             // className={
@@ -89,15 +86,14 @@ const DeviceArea = ({ className }: { className?: string }) => {
       </Badge>
       <Button
         style={btnStyle}
-        className={`${classValue} ${
-          tabletStatus === 'exposing'
+        className={`${classValue} ${tabletStatus === 'exposing'
             ? 'text-yellow-500'
             : tabletStatus === 'ready'
               ? 'text-green-500'
               : tabletStatus === 'error'
                 ? 'text-red-500'
                 : ''
-        }`}
+          }`}
         icon={
           <TabletOutlined
           // className={

+ 135 - 131
src/pages/view/components/MeasurementPanel.tsx

@@ -2,12 +2,13 @@ import React from 'react';
 import { Layout, Button, Typography, Divider, Flex } from 'antd';
 import { ArrowLeftOutlined } from '@ant-design/icons';
 import { useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
 import { switchToOperationPanel } from '../../../states/panelSwitchSliceForView';
 import {
   setMeasurementAction,
   type MeasurementAction,
 } from '../../../states/view/measurementPanelSlice';
+import type { RootState } from '../../../states/store';
 import Icon from '@/components/Icon';
 import '@/themes/truncateText.css';
 
@@ -65,6 +66,7 @@ const FunctionButton = ({
 const MeasurementPanel = () => {
   const intl = useIntl();
   const dispatch = useDispatch();
+  const productName = useSelector((state: RootState) => state.product.productName);
 
   const handleReturn = () => {
     dispatch(switchToOperationPanel());
@@ -152,136 +154,138 @@ const MeasurementPanel = () => {
         <Divider />
 
         {/* 宠物专用测量组 */}
-        <div style={{ marginBottom: '24px' }}>
-          <Text
-            strong
-            style={{ fontSize: '14px', marginBottom: '12px', display: 'block' }}
-          >
-            {intl.formatMessage({ id: 'measurementPanel.petSpecificMeasurements' })}
-          </Text>
-          <Flex wrap gap="small" align="center" justify="start" className="p-1">
-            <FunctionButton
-              title="髋臼水平角"
-              action="髋臼水平角"
-              iconName="btn_DAR"
-              productId="animal"
-            />
-            <FunctionButton
-              title="胫骨平台夹角"
-              action="胫骨平台夹角"
-              iconName="btn_TPA"
-              productId="animal"
-            />
-            <FunctionButton
-              title="髋关节牵引指数"
-              action="髋关节牵引指数"
-              iconName="btn_HDI"
-              productId="animal"
-            />
-            <FunctionButton
-              title="髋关节水平角"
-              action="髋关节水平角"
-              iconName="btn_NHA"
-              productId="animal"
-            />
-            <FunctionButton
-              title="心锥比"
-              action="心锥比"
-              iconName="btn_VHS"
-              productId="animal"
-            />
-            <FunctionButton
-              title="胫骨平台骨切开术"
-              action="胫骨平台骨切开术"
-              iconName="btn_TPLO"
-              productId="animal"
-            />
-            <FunctionButton
-              title="胫骨结节前移术"
-              action="胫骨结节前移术"
-              iconName="btn_TTA"
-              productId="animal"
-            />
-            <FunctionButton
-              title="胫骨结节前移术5点测量法"
-              action="胫骨结节前移术5点测量法"
-              iconName="btn_TTA2"
-              productId="animal"
-            />
-            <FunctionButton
-              title="水平截骨术"
-              action="水平截骨术"
-              iconName="btn_CBLO"
-              productId="animal"
-            />
-            <FunctionButton
-              title="股骨头覆盖率"
-              action="股骨头覆盖率"
-              iconName="btn_DLS"
-              productId="animal"
-            />
-            <FunctionButton
-              title="髋臼背覆盖"
-              action="髋臼背覆盖"
-              iconName="btn_DAC"
-              productId="animal"
-            />
-            <FunctionButton
-              title="拆线长度测量"
-              action="拆线长度测量"
-              iconName="btn_FoldLine"
-              productId="animal"
-            />
-            <FunctionButton
-              title="多边形长度测量"
-              action="多边形长度测量"
-              iconName="btn_Polygon"
-              productId="animal"
-            />
-            <FunctionButton
-              title="找圆心"
-              action="找圆心"
-              iconName="btn_CenterCircle"
-              productId="animal"
-            />
-            <FunctionButton
-              title="找中线"
-              action="找中线"
-              iconName="btn_CenterLine"
-              productId="animal"
-            />
-            <FunctionButton
-              title="找中点"
-              action="找中点"
-              iconName="btn_Midpoint"
-              productId="animal"
-            />
-            <FunctionButton
-              title="直线垂直倾斜度"
-              action="直线垂直倾斜度"
-              iconName="btn_VerticalInclination"
-              productId="animal"
-            />
-            <FunctionButton
-              title="直线水平倾斜度"
-              action="直线水平倾斜度"
-              iconName="btn_HorizontalInclination"
-              productId="animal"
-            />
-            <FunctionButton
-              title="矩形区域灰度"
-              action="矩形区域灰度"
-              iconName="btn_RectangularGrayscale"
-              productId="animal"
-            />
-            <FunctionButton
-              title="直线灰度"
-              action="直线灰度"
-              iconName="btn_LineGrayscale"
-              productId="animal"
-            />
-          </Flex>
-        </div>
+        {productName === 'VETDROS' && (
+          <div style={{ marginBottom: '24px' }}>
+            <Text
+              strong
+              style={{ fontSize: '14px', marginBottom: '12px', display: 'block' }}
+            >
+              {intl.formatMessage({ id: 'measurementPanel.petSpecificMeasurements' })}
+            </Text>
+            <Flex wrap gap="small" align="center" justify="start" className="p-1">
+              <FunctionButton
+                title="髋臼水平角"
+                action="髋臼水平角"
+                iconName="btn_DAR"
+                productId="animal"
+              />
+              <FunctionButton
+                title="胫骨平台夹角"
+                action="胫骨平台夹角"
+                iconName="btn_TPA"
+                productId="animal"
+              />
+              <FunctionButton
+                title="髋关节牵引指数"
+                action="髋关节牵引指数"
+                iconName="btn_HDI"
+                productId="animal"
+              />
+              <FunctionButton
+                title="髋关节水平角"
+                action="髋关节水平角"
+                iconName="btn_NHA"
+                productId="animal"
+              />
+              <FunctionButton
+                title="心锥比"
+                action="心锥比"
+                iconName="btn_VHS"
+                productId="animal"
+              />
+              <FunctionButton
+                title="胫骨平台骨切开术"
+                action="胫骨平台骨切开术"
+                iconName="btn_TPLO"
+                productId="animal"
+              />
+              <FunctionButton
+                title="胫骨结节前移术"
+                action="胫骨结节前移术"
+                iconName="btn_TTA"
+                productId="animal"
+              />
+              <FunctionButton
+                title="胫骨结节前移术5点测量法"
+                action="胫骨结节前移术5点测量法"
+                iconName="btn_TTA2"
+                productId="animal"
+              />
+              <FunctionButton
+                title="水平截骨术"
+                action="水平截骨术"
+                iconName="btn_CBLO"
+                productId="animal"
+              />
+              <FunctionButton
+                title="股骨头覆盖率"
+                action="股骨头覆盖率"
+                iconName="btn_DLS"
+                productId="animal"
+              />
+              <FunctionButton
+                title="髋臼背覆盖"
+                action="髋臼背覆盖"
+                iconName="btn_DAC"
+                productId="animal"
+              />
+              <FunctionButton
+                title="拆线长度测量"
+                action="拆线长度测量"
+                iconName="btn_FoldLine"
+                productId="animal"
+              />
+              <FunctionButton
+                title="多边形长度测量"
+                action="多边形长度测量"
+                iconName="btn_Polygon"
+                productId="animal"
+              />
+              <FunctionButton
+                title="找圆心"
+                action="找圆心"
+                iconName="btn_CenterCircle"
+                productId="animal"
+              />
+              <FunctionButton
+                title="找中线"
+                action="找中线"
+                iconName="btn_CenterLine"
+                productId="animal"
+              />
+              <FunctionButton
+                title="找中点"
+                action="找中点"
+                iconName="btn_Midpoint"
+                productId="animal"
+              />
+              <FunctionButton
+                title="直线垂直倾斜度"
+                action="直线垂直倾斜度"
+                iconName="btn_VerticalInclination"
+                productId="animal"
+              />
+              <FunctionButton
+                title="直线水平倾斜度"
+                action="直线水平倾斜度"
+                iconName="btn_HorizontalInclination"
+                productId="animal"
+              />
+              <FunctionButton
+                title="矩形区域灰度"
+                action="矩形区域灰度"
+                iconName="btn_RectangularGrayscale"
+                productId="animal"
+              />
+              <FunctionButton
+                title="直线灰度"
+                action="直线灰度"
+                iconName="btn_LineGrayscale"
+                productId="animal"
+              />
+            </Flex>
+          </div>
+        )}
       </Content>
     </Layout>
   );

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

@@ -35,6 +35,7 @@ interface DeviceAreaState {
   generatorStatus_2: GENERATOR_STATUS; //发生器状态
   exposureStatus: 'ready' | 'not_ready';
   tabletStatus: 'exposing' | 'ready' | 'error' | 'not_ready';
+  generatorReady?: boolean;
 }
 
 const initialState: DeviceAreaState = {
@@ -42,6 +43,7 @@ const initialState: DeviceAreaState = {
   generatorStatus_2: GENERATOR_STATUS.GENERATOR_STATUS_INIT,
   exposureStatus: 'not_ready',
   tabletStatus: 'not_ready',
+  generatorReady:false,
 };
 
 const deviceAreaSlice = createSlice({
@@ -66,6 +68,10 @@ const deviceAreaSlice = createSlice({
     setGeneratorStatus_2: (state, action: PayloadAction<GENERATOR_STATUS>) => {
       state.generatorStatus_2 = action.payload;
     },
+    setgeneratorReady: (state, action: PayloadAction<boolean>) => {
+      console.log('[deviceAreaSlice] slice中 设置发生器状态:', action.payload);
+      state.generatorReady = action.payload;
+    }
   },
 });
 
@@ -113,11 +119,19 @@ emitter.on('AllReady_FALSE', () => {
   store.dispatch(setExposureStatus('not_ready'));
 });
 
+emitter.on('GENStatus_TRUE', () => {
+    store.dispatch(setgeneratorReady(true));
+});
+emitter.on('GENStatus_FALSE', () => {
+    store.dispatch(setgeneratorReady(false));
+});
+
 export const {
   setGeneratorStatus,
   setExposureStatus,
   setTabletStatus,
   setGeneratorStatus_2,
+  setgeneratorReady
 } = deviceAreaSlice.actions;
 export { GeneratorStatus, GENERATOR_STATUS };
 export default deviceAreaSlice.reducer;