Quellcode durchsuchen

feat(v1.4.1): 直线灰度测量工具交互功能增强

- 实现整个工具平移功能,点击线段可拖拽移动整个测量工具
- 实现手柄拖拽功能,支持单独调整端点位置
- 实现选中状态管理,选中时手柄半径从6增大到12
- 实现线段和手柄悬停时显示十字准星光标
- 实现悬停高亮效果,提供更好的视觉反馈
- 点击空白区域可取消工具选中状态
- 优化数据更新逻辑,只在必要时重新计算统计数据

改动文件:
- src/components/measures/LineGrayscaleMeasurementTool.ts
- CHANGELOG.md (添加 1.4.1 版本记录)
- package.json (版本升级到 1.4.1)
dengdx vor 1 Monat
Ursprung
Commit
99290feb66
3 geänderte Dateien mit 234 neuen und 12 gelöschten Zeilen
  1. 23 0
      CHANGELOG.md
  2. 1 1
      package.json
  3. 210 11
      src/components/measures/LineGrayscaleMeasurementTool.ts

+ 23 - 0
CHANGELOG.md

@@ -21,6 +21,29 @@
 
 ---
 
+## [1.4.1] - 2025-12-11
+
+### 新增 (Added)
+- **直线灰度测量工具交互功能增强** ([#line-grayscale-measurement-interaction](src/components/measures/LineGrayscaleMeasurementTool.ts))
+  - 实现整个工具平移功能,点击线段可拖拽移动整个测量工具
+  - 实现手柄拖拽功能,支持单独调整端点位置
+  - 实现选中状态管理,选中时手柄半径从6增大到12
+  - 实现线段和手柄悬停时显示十字准星光标
+  - 实现悬停高亮效果,提供更好的视觉反馈
+  - 点击空白区域可取消工具选中状态
+  - 优化数据更新逻辑,只在必要时重新计算统计数据
+
+**核心改进:**
+- 使用 `handleIndex = -1` 标识整个工具的拖拽操作
+- 使用 `wholeToolOffset` 追踪工具平移的拖拽状态
+- 实现优先级检测:手柄 → 线段
+- 选中状态下保持高亮,提供更好的用户体验
+
+**改动文件:**
+- src/components/measures/LineGrayscaleMeasurementTool.ts
+
+---
+
 ## [1.4.0] - 2025-12-10
 
 ### 新增 (Added)

+ 1 - 1
package.json

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

+ 210 - 11
src/components/measures/LineGrayscaleMeasurementTool.ts

@@ -84,6 +84,8 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     hasMoved?: boolean;
     textBoxBeingMoved?: boolean;
     textBoxOffset?: CoreTypes.Point2;
+    wholeToolOffset?: CoreTypes.Point2;
+    originalPoints?: CoreTypes.Point3[]; // 保存拖拽开始时的原始点坐标
   } | null = null;
 
   isDrawing: boolean = false;
@@ -372,9 +374,45 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
       return;
     }
 
-    // 查找最近的手柄
+    // 查找最近的手柄或工具
     for (const ann of annotations) {
       const customAnn = ann as LineGrayscaleAnnotation;
+      
+      // 1. 检查是否点击在文本框上
+      if (customAnn.data.textBox) {
+        const textBoxCanvas = customAnn.data.textBox;
+        if (this._isPointNearTextBox(canvasCoords, textBoxCanvas)) {
+          const viewportIdsToRender =
+            utilities.viewportFilters.getViewportIdsWithToolToRender(
+              element,
+              this.getToolName()
+            );
+
+          this.editData = {
+            annotation: customAnn,
+            viewportIdsToRender,
+            textBoxBeingMoved: true,
+            textBoxOffset: [
+              canvasCoords[0] - textBoxCanvas[0],
+              canvasCoords[1] - textBoxCanvas[1],
+            ],
+            hasMoved: false,
+          };
+
+          customAnn.isSelected = true;
+          customAnn.highlighted = true;
+
+          utilities.triggerAnnotationRenderForViewportIds(
+            viewportIdsToRender
+          );
+
+          evt.preventDefault();
+          evt.stopPropagation();
+          return;
+        }
+      }
+
+      // 2. 检查是否点击在手柄上
       const handle = this.getHandleNearImagePoint(
         element,
         customAnn,
@@ -394,6 +432,50 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
           viewportIdsToRender,
           handleIndex: customAnn.data.handles.activeHandleIndex || 0,
           hasMoved: false,
+          originalPoints: [...customAnn.data.handles.points],
+        };
+
+        customAnn.isSelected = true;
+        customAnn.highlighted = true;
+
+        utilities.triggerAnnotationRenderForViewportIds(
+          viewportIdsToRender
+        );
+
+        evt.preventDefault();
+        evt.stopPropagation();
+        return;
+      }
+
+      // 3. 检查是否点击在线段上(拖拽整个工具)
+      if (this.isPointNearTool(element, customAnn, canvasCoords, 10)) {
+        const viewportIdsToRender =
+          utilities.viewportFilters.getViewportIdsWithToolToRender(
+            element,
+            this.getToolName()
+          );
+
+        // 计算线段中心点
+        const points = customAnn.data.handles.points;
+        const centerWorld: CoreTypes.Point3 = [
+          (points[0][0] + points[1][0]) / 2,
+          (points[0][1] + points[1][1]) / 2,
+          (points[0][2] + points[1][2]) / 2,
+        ];
+        const centerCanvas = viewport.worldToCanvas(centerWorld);
+        
+        const wholeToolOffset: CoreTypes.Point2 = [
+          canvasCoords[0] - centerCanvas[0],
+          canvasCoords[1] - centerCanvas[1],
+        ];
+
+        // 开始拖拽整个工具
+        this.editData = {
+          annotation: customAnn,
+          viewportIdsToRender,
+          handleIndex: -1, // -1表示拖拽整个工具
+          hasMoved: false,
+          wholeToolOffset: wholeToolOffset,
         };
 
         customAnn.isSelected = true;
@@ -414,6 +496,13 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
       const customAnn = ann as LineGrayscaleAnnotation;
       customAnn.isSelected = false;
     }
+    
+    const viewportIdsToRender =
+      utilities.viewportFilters.getViewportIdsWithToolToRender(
+        element,
+        this.getToolName()
+      );
+    utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
   };
 
   _mouseDragModifyCallback = (evt: EventTypes.InteractionEventType): void => {
@@ -423,25 +512,89 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
 
     const eventDetail = evt.detail;
     const { currentPoints } = eventDetail;
+    const canvasCoords = currentPoints.canvas;
 
     const enabledElement = getEnabledElement(eventDetail.element);
     if (!enabledElement) {
       return;
     }
 
+    const { viewport } = enabledElement;
     const { annotation: ann, viewportIdsToRender } = this.editData;
     const customAnn = ann as LineGrayscaleAnnotation;
     const { data } = customAnn;
 
-    // 处理手柄拖拽
+    // 1. 处理文本框拖拽
+    if (this.editData.textBoxBeingMoved && this.editData.textBoxOffset) {
+      const newTextBoxPosition: CoreTypes.Point2 = [
+        canvasCoords[0] - this.editData.textBoxOffset[0],
+        canvasCoords[1] - this.editData.textBoxOffset[1],
+      ];
+      
+      data.textBox = newTextBoxPosition;
+      this.editData.hasMoved = true;
+
+      utilities.triggerAnnotationRenderForViewportIds(
+        viewportIdsToRender
+      );
+
+      evt.preventDefault();
+      evt.stopPropagation();
+      return;
+    }
+
+    // 2. 处理整个工具的平移
+    if (this.editData.handleIndex === -1 && this.editData.wholeToolOffset) {
+      // 计算新的中心位置
+      const newCenterCanvas: CoreTypes.Point2 = [
+        canvasCoords[0] - this.editData.wholeToolOffset[0],
+        canvasCoords[1] - this.editData.wholeToolOffset[1],
+      ];
+
+      const newCenterWorld = viewport.canvasToWorld(newCenterCanvas);
+
+      // 计算当前中心位置
+      const points = data.handles.points;
+      const currentCenterWorld: CoreTypes.Point3 = [
+        (points[0][0] + points[1][0]) / 2,
+        (points[0][1] + points[1][1]) / 2,
+        (points[0][2] + points[1][2]) / 2,
+      ];
+
+      // 计算偏移量
+      const worldOffset: CoreTypes.Point3 = [
+        newCenterWorld[0] - currentCenterWorld[0],
+        newCenterWorld[1] - currentCenterWorld[1],
+        0,
+      ];
+
+      // 应用偏移到所有点
+      for (let i = 0; i < data.handles.points.length; i++) {
+        data.handles.points[i] = [
+          data.handles.points[i][0] + worldOffset[0],
+          data.handles.points[i][1] + worldOffset[1],
+          data.handles.points[i][2],
+        ] as CoreTypes.Point3;
+      }
+
+      this.editData.hasMoved = true;
+
+      utilities.triggerAnnotationRenderForViewportIds(
+        viewportIdsToRender
+      );
+
+      evt.preventDefault();
+      evt.stopPropagation();
+      return;
+    }
+
+    // 3. 处理手柄拖拽
     const worldPos = currentPoints.world;
     const activeHandleIndex = data.handles.activeHandleIndex;
 
     if (activeHandleIndex !== null && activeHandleIndex >= 0 && activeHandleIndex < data.handles.points.length) {
       data.handles.points[activeHandleIndex] = worldPos;
 
-      this._updateCachedStats(customAnn, enabledElement);
-
       this.editData.hasMoved = true;
 
       utilities.triggerAnnotationRenderForViewportIds(
@@ -458,7 +611,7 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
       return;
     }
 
-    const { annotation: ann } = this.editData;
+    const { annotation: ann, hasMoved } = this.editData;
     const customAnn = ann as LineGrayscaleAnnotation;
 
     customAnn.data.handles.activeHandleIndex = null;
@@ -467,6 +620,13 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     const eventDetail = evt.detail;
     const { element } = eventDetail;
 
+    const enabledElement = getEnabledElement(element);
+    
+    // 如果工具被移动了,更新缓存的统计数据
+    if (hasMoved && enabledElement && !this.editData.textBoxBeingMoved) {
+      this._updateCachedStats(customAnn, enabledElement);
+    }
+
     const viewportIdsToRender =
       utilities.viewportFilters.getViewportIdsWithToolToRender(
         element,
@@ -504,25 +664,40 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     }
 
     let isHovering = false;
+    let cursorSet = false;
 
-    // 检查是否悬停在手柄或直线上
+    // 检查是否悬停在文本、手柄或直线上
     for (const ann of annotations) {
       const customAnn = ann as LineGrayscaleAnnotation;
 
-      // 检查是否靠近手柄
+      // 1. 检查是否悬停在文本框上
+      if (customAnn.data.textBox) {
+        const textBoxCanvas = customAnn.data.textBox;
+        if (this._isPointNearTextBox(canvasCoords, textBoxCanvas)) {
+          element.style.cursor = 'pointer';
+          customAnn.highlighted = true;
+          isHovering = true;
+          cursorSet = true;
+          break;
+        }
+      }
+
+      // 2. 检查是否靠近手柄
       const handle = this.getHandleNearImagePoint(element, customAnn, canvasCoords, 6);
       if (handle) {
         element.style.cursor = 'crosshair';
         customAnn.highlighted = true;
         isHovering = true;
+        cursorSet = true;
         break;
       }
 
-      // 检查是否靠近直线
+      // 3. 检查是否靠近直线
       if (this.isPointNearTool(element, customAnn, canvasCoords, 10)) {
         element.style.cursor = 'crosshair';
         customAnn.highlighted = true;
         isHovering = true;
+        cursorSet = true;
         break;
       }
     }
@@ -531,7 +706,9 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     if (!isHovering) {
       for (const ann of annotations) {
         const customAnn = ann as LineGrayscaleAnnotation;
-        customAnn.highlighted = false;
+        if (!customAnn.isSelected) {
+          customAnn.highlighted = false;
+        }
       }
       element.style.cursor = 'default';
     }
@@ -645,6 +822,26 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     return undefined;
   }
 
+  /**
+   * 检查点是否靠近文本框
+   */
+  private _isPointNearTextBox(
+    point: CoreTypes.Point2,
+    textBoxPosition: CoreTypes.Point2,
+    padding: number = 50
+  ): boolean {
+    // 简单的矩形碰撞检测,假设文本框大小约为 150x80
+    const textBoxWidth = 150;
+    const textBoxHeight = 80;
+    
+    return (
+      point[0] >= textBoxPosition[0] - padding &&
+      point[0] <= textBoxPosition[0] + textBoxWidth + padding &&
+      point[1] >= textBoxPosition[1] - padding &&
+      point[1] <= textBoxPosition[1] + textBoxHeight + padding
+    );
+  }
+
   /**
    * 更新缓存的统计数据
    */
@@ -891,8 +1088,9 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
         }
       );
 
-      // 绘制手柄
+      // 绘制手柄 - 选中时使用更大的半径
       const handleGroupUID = `${annotationUID}-handles`;
+      const handleRadius = annotationItem.isSelected ? 12 : 6;
       drawHandles(
         svgDrawingHelper,
         annotationUID,
@@ -900,6 +1098,7 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
         canvasCoordinates,
         {
           color,
+          handleRadius,
         }
       );
 
@@ -934,4 +1133,4 @@ export default class LineGrayscaleMeasurementTool extends AnnotationTool {
     return true;
   };
 
-}
+}