Pārlūkot izejas kodu

feat: 实现测量工具一次性模式和光标恢复功能 (v1.23.0 v1.24.0)

- 在 MeasurementToolManager 中实现一次性模式管理,Length 和 Angle 工具绘制完成后自动停用
- 添加 ANNOTATION_COMPLETED 事件监听器,测量完成时自动关闭工具
- 在 deactivateToolByName 方法中添加光标恢复逻辑,工具停用时自动重置为默认光标
- 在应用初始化时调用 initializeOneTimeMode() 设置一次性模式监听器
- 优化用户体验:测量完成后工具自动关闭,光标恢复正常状态,减少手动操作

改动文件:
- src/app.tsx - 添加 MeasurementToolManager 初始化调用
- src/utils/measurementToolManager.ts - 实现一次性模式和光标恢复功能
- CHANGELOG.md - 更新版本号和变更记录
- package.json - 版本升级到 1.24.0
dengdx 3 nedēļas atpakaļ
vecāks
revīzija
8e87ac3195
5 mainītis faili ar 164 papildinājumiem un 3 dzēšanām
  1. 33 0
      CHANGELOG.md
  2. 2 2
      config/dev.ts
  3. 1 1
      package.json
  4. 4 0
      src/app.tsx
  5. 124 0
      src/utils/measurementToolManager.ts

+ 33 - 0
CHANGELOG.md

@@ -2,6 +2,39 @@
 
 本项目的所有重要变更都将记录在此文件中。
 
+## [1.24.0] - 2025-12-23 19:20
+
+### 新增 (Added)
+- **测量工具一次性模式和光标恢复功能** - 实现测量工具绘制完成后自动关闭和光标恢复的功能
+  - 新增 MeasurementToolManager.initializeOneTimeMode() 初始化方法,在应用启动时自动设置一次性模式监听器
+  - 监听 cornerstoneTools.Enums.Events.ANNOTATION_COMPLETED 事件,测量完成时自动停用工具
+  - 实现一次性工具列表管理,只对 Length 和 Angle 工具启用一次性模式
+  - 在 deactivateToolByName 方法中添加光标恢复逻辑,工具停用时自动将光标重置为默认样式
+  - 为所有测量工具激活方法添加一次性模式设置逻辑
+  - 优化用户体验:测量完成后工具自动关闭,光标恢复正常状态
+
+**核心功能实现:**
+- 一次性模式管理:Length 和 Angle 工具绘制完成后自动停用,避免连续绘制
+- 事件驱动架构:基于 Cornerstone 事件系统实现自动化工具管理
+- 光标状态恢复:工具停用时自动恢复默认光标样式
+- 用户体验优化:减少手动操作步骤,提升操作流畅度
+- 状态管理:通过 Redux 清除测量状态,确保界面状态同步
+
+**技术实现:**
+- 在 MeasurementToolManager.ts 中添加事件监听逻辑
+- 使用 Map 数据结构跟踪一次性模式状态
+- 在 deactivateToolByName 中统一处理光标恢复
+- 在 app.tsx 中添加应用初始化调用
+- 集成 cornerstone eventTarget 进行事件监听
+
+**改动文件:**
+- src/app.tsx - 添加 MeasurementToolManager 初始化调用
+- src/utils/measurementToolManager.ts - 实现一次性模式和光标恢复功能
+- CHANGELOG.md
+- package.json (版本更新: 1.23.0 -> 1.24.0)
+
+---
+
 ## [1.23.0] - 2025-12-23 16:58
 
 ### 新增 (Added)

+ 2 - 2
config/dev.ts

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

+ 1 - 1
package.json

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

+ 4 - 0
src/app.tsx

@@ -9,6 +9,7 @@ import { loadI18nMessages } from './states/i18nSlice';
 import { checkServerConnection } from './features/serverConfig';
 import { initializeAnnotationManager, cleanupAnnotationManager } from './features/imageAnnotation';
 import { ToolStateListener } from './utils/toolStateListener';
+import { MeasurementToolManager } from './utils/measurementToolManager';
 import { platform } from './utils/platform';
 import './app.css';
 import ProductSelector from './components/ProductSelector';
@@ -123,6 +124,9 @@ function AppContent({ children }: { children: ReactNode }): JSX.Element {
       // ✅ 初始化工具状态监听器(监听cornerstone工具状态变化)
       ToolStateListener.getInstance().init();
 
+      // ✅ 初始化测量工具一次性模式(监听测量完成事件)
+      MeasurementToolManager.initializeOneTimeMode();
+
       setIsI18nReady(true);
     } catch (error) {
       console.error('应用初始化失败:', error);

+ 124 - 0
src/utils/measurementToolManager.ts

@@ -1,5 +1,8 @@
 import * as cornerstone from '@cornerstonejs/core';
 import * as cornerstoneTools from '@cornerstonejs/tools';
+import { eventTarget } from '@cornerstonejs/core';
+import store from '@/states/store';
+import { clearMeasurementAction } from '@/states/view/measurementPanelSlice';
 import TibialPlateauAngleTool from '@/components/measures/TibialPlateauAngleTool';
 import DARAMeasurementTool from '@/components/measures/DARAMeasurementTool';
 import HipDIMeasurementTool from '@/components/measures/HipDIMeasurementTool';
@@ -37,6 +40,118 @@ const { MouseBindings } = csToolsEnums;
  * 统一管理所有测量相关的工具操作
  */
 export class MeasurementToolManager {
+  /**
+   * 一次性测量工具列表 - 这些工具在绘制完成后会自动停用
+   */
+  private static readonly ONE_TIME_TOOLS = new Set([
+    'Length',
+    'Angle',
+  ]);
+
+  /**
+   * 跟踪哪些工具处于一次性模式
+   */
+  private static oneTimeModes = new Map<string, string>();
+
+  /**
+   * 检查工具是否为一次性工具
+   */
+  static isOneTimeTool(toolName: string): boolean {
+    return this.ONE_TIME_TOOLS.has(toolName);
+  }
+
+  /**
+   * 设置工具为一次性模式
+   */
+  private static setOneTimeMode(viewportId: string, toolName: string): void {
+    const key = `${viewportId}-${toolName}`;
+    this.oneTimeModes.set(key, toolName);
+  }
+
+  /**
+   * 检查工具是否处于一次性模式
+   */
+  private static isInOneTimeMode(viewportId: string, toolName: string): boolean {
+    const key = `${viewportId}-${toolName}`;
+    return this.oneTimeModes.has(key);
+  }
+
+  /**
+   * 清除工具的一次性模式
+   */
+  private static clearOneTimeMode(viewportId: string, toolName: string): void {
+    const key = `${viewportId}-${toolName}`;
+    this.oneTimeModes.delete(key);
+  }
+
+  /**
+   * 初始化一次性模式监听器
+   */
+  static initializeOneTimeMode(): void {
+    // 防止重复初始化
+    if ((this as any).initialized) return;
+
+    // 监听 annotation completed 事件
+    eventTarget.addEventListener(
+      cornerstoneTools.Enums.Events.ANNOTATION_COMPLETED,
+      (evt: any) => {
+        const { annotation } = evt.detail;
+        const toolName = annotation?.metadata?.toolName;
+
+        if (toolName && this.isOneTimeTool(toolName)) {
+          // 查找工具所在的 viewport
+          const viewports = cornerstone.getEnabledElements();
+          for (const enabledElement of viewports) {
+            const viewportId = enabledElement.viewport.id ||
+                             enabledElement.viewport.element?.id;
+            if (viewportId && this.isInOneTimeMode(viewportId, toolName)) {
+              // 自动停用工具
+              this.deactivateToolByName(viewportId, toolName);
+              this.clearOneTimeMode(viewportId, toolName);
+
+              // 清除 Redux 测量状态
+              store.dispatch(clearMeasurementAction());
+
+              console.log(`[MeasurementToolManager] Auto-deactivated one-time tool: ${toolName} for viewport: ${viewportId}`);
+              break;
+            }
+          }
+        }
+      }
+    );
+
+    (this as any).initialized = true;
+    console.log('[MeasurementToolManager] One-time mode initialized');
+  }
+
+  /**
+   * 根据工具名称停用工具
+   */
+  private static deactivateToolByName(viewportId: string, toolName: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      toolGroup.setToolPassive(toolName, { removeAllBindings: true });
+      console.log(`[MeasurementToolManager] Tool ${toolName} deactivated for viewport: ${viewportId}`);
+
+      // 恢复viewport的默认光标
+      try {
+        const enabledElement = cornerstone.getEnabledElementByViewportId(viewportId);
+        if (enabledElement?.viewport?.element) {
+          enabledElement.viewport.element.style.cursor = 'default';
+        }
+      } catch (error) {
+        console.warn(`[MeasurementToolManager] Failed to reset cursor for viewport: ${viewportId}`, error);
+      }
+
+      return true;
+    } catch (error) {
+      console.error(`[MeasurementToolManager] Error deactivating tool ${toolName}:`, error);
+      return false;
+    }
+  }
+
   /**
    * 根据 viewportId 获取对应的工具组
    */
@@ -76,6 +191,9 @@ export class MeasurementToolManager {
         bindings: [{ mouseButton: MouseBindings.Primary }],
       });
 
+      // 设置一次性模式
+      this.setOneTimeMode(viewportId, LengthTool.toolName);
+
       console.log(
         `[MeasurementToolManager] Length tool activated for viewport: ${viewportId}`
       );
@@ -169,6 +287,9 @@ export class MeasurementToolManager {
         bindings: [{ mouseButton: MouseBindings.Primary }],
       });
 
+      // 设置一次性模式
+      this.setOneTimeMode(viewportId, AngleTool.toolName);
+
       console.log(
         `[MeasurementToolManager] Angle tool activated for viewport: ${viewportId}`
       );
@@ -530,6 +651,9 @@ export class MeasurementToolManager {
         // 注解创建失败不影响工具激活
       }
 
+      // 设置一次性模式
+      this.setOneTimeMode(viewportId, TibialPlateauAngleTool.toolName);
+
       console.log(
         `[MeasurementToolManager] TibialPlateauAngle tool activated for viewport: ${viewportId}`
       );