|
|
@@ -0,0 +1,288 @@
|
|
|
+# Bug #81: 修复"调整亮度和对比度"按钮在切换体位后失效问题
|
|
|
+
|
|
|
+**Bug ID**: #81
|
|
|
+**严重程度**: 高
|
|
|
+**状态**: 已修复
|
|
|
+**修复日期**: 2025-12-09
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 问题描述
|
|
|
+
|
|
|
+在 **process 模式**下,切换体位后点击 "Adjust Brightness and Contrast" 按钮,功能无法启用:
|
|
|
+- 按钮可以点击,没有报错
|
|
|
+- 函数正常执行
|
|
|
+- 但鼠标光标不会切换到亮度/对比度调整模式
|
|
|
+- 无法进行亮度和对比度调整
|
|
|
+
|
|
|
+**复现路径**:
|
|
|
+1. 进入 `process` 模式
|
|
|
+2. 选择一个已曝光的体位
|
|
|
+3. 点击体位列表中的另一个体位(单击切换)
|
|
|
+4. 点击 "Adjust Brightness and Contrast" 按钮
|
|
|
+5. 问题出现:工具未激活
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 根本原因分析
|
|
|
+
|
|
|
+### 问题根源:工具组绑定过时的视口元素
|
|
|
+
|
|
|
+当在 `process` 模式下切换体位时,React 会根据新的 `imageUrl` 创建新的 `StackViewer` 组件实例:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// ViewerContainer.tsx
|
|
|
+<StackViewerWithErrorBoundary
|
|
|
+ key={actualUrl} // ← 新的 URL = 新的 key = 新的组件实例
|
|
|
+ viewportId={getViewportIdByUrl(originalUrl)}
|
|
|
+ // ...
|
|
|
+/>
|
|
|
+```
|
|
|
+
|
|
|
+**问题流程**:
|
|
|
+
|
|
|
+1. **旧组件销毁**:切换体位时,旧的 `StackViewer` 组件 unmount,旧的视口元素被销毁
|
|
|
+2. **新组件创建**:新的 `StackViewer` 组件 mount,创建新的视口元素
|
|
|
+3. **工具组仍然存在**:`ToolGroupManager` 中的工具组 ID 基于 `viewportId`,而 `viewportId` 没有变化
|
|
|
+4. **绑定过时**:现有工具组仍然绑定到已销毁的旧视口元素
|
|
|
+5. **激活失败**:调用 `toolGroup.setToolActive()` 时,工具组尝试在旧元素上设置鼠标绑定,但该元素已不存在
|
|
|
+
|
|
|
+### 代码中的问题点
|
|
|
+
|
|
|
+在 [`stack.image.viewer.tsx:179-230`](src/pages/view/components/viewers/stack.image.viewer.tsx:179) 的 `registerTools` 函数:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 原始代码(有问题)
|
|
|
+function registerTools(viewportId, renderingEngineId) {
|
|
|
+ registerGlobalTools();
|
|
|
+ const toolGroupId = `STACK_TOOL_GROUP_ID_${viewportId}`;
|
|
|
+
|
|
|
+ // ❌ 问题:直接创建工具组,没有检查是否已存在
|
|
|
+ const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId);
|
|
|
+
|
|
|
+ if (!toolGroupTmp) {
|
|
|
+ console.error(`[registerTools] Failed to create tool group`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果工具组已存在,createToolGroup() 返回 undefined
|
|
|
+ // 导致后续代码无法执行,也没有重新绑定视口
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 为什么没有报错
|
|
|
+
|
|
|
+- `getToolgroupByViewportId()` 能找到现有的工具组(因为 ID 相同)
|
|
|
+- `toolGroup.setToolActive()` 调用成功(API 层面)
|
|
|
+- 但工具组绑定的是已销毁的 DOM 元素,所以鼠标事件绑定失败(静默失败)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 修复方案
|
|
|
+
|
|
|
+### 修改文件
|
|
|
+
|
|
|
+[`src/pages/view/components/viewers/stack.image.viewer.tsx:179-230`](src/pages/view/components/viewers/stack.image.viewer.tsx:179)
|
|
|
+
|
|
|
+### 修复代码
|
|
|
+
|
|
|
+```typescript
|
|
|
+function registerTools(viewportId, renderingEngineId) {
|
|
|
+ // 确保全局工具已注册(只会执行一次)
|
|
|
+ registerGlobalTools();
|
|
|
+
|
|
|
+ // 创建该 viewport 的工具组
|
|
|
+ const toolGroupId = `STACK_TOOL_GROUP_ID_${viewportId}`;
|
|
|
+
|
|
|
+ // 🔧 修复:检查工具组是否已存在
|
|
|
+ let toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
|
|
+
|
|
|
+ if (toolGroup) {
|
|
|
+ // 工具组已存在 - 需要重新绑定到新的视口元素
|
|
|
+ console.log(`[registerTools] Tool group already exists, re-binding viewport: ${viewportId}`);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 移除旧的视口绑定(清理过时的引用)
|
|
|
+ toolGroup.removeViewports(renderingEngineId, viewportId);
|
|
|
+ console.log(`[registerTools] Removed old viewport binding`);
|
|
|
+ } catch (error) {
|
|
|
+ console.warn(`[registerTools] Failed to remove old viewport binding:`, error);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新添加视口到工具组(创建新的绑定)
|
|
|
+ toolGroup.addViewport(viewportId, renderingEngineId);
|
|
|
+ console.log(`[registerTools] Re-added viewport to tool group`);
|
|
|
+
|
|
|
+ return; // 重新绑定完成,退出
|
|
|
+ }
|
|
|
+
|
|
|
+ // 工具组不存在 - 创建新的工具组
|
|
|
+ const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId);
|
|
|
+ if (!toolGroupTmp) {
|
|
|
+ console.error(
|
|
|
+ `[registerTools] Failed to create tool group for viewport: ${viewportId}`
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ toolGroup = toolGroupTmp;
|
|
|
+
|
|
|
+ // 添加所有工具到工具组
|
|
|
+ toolGroup.addTool(MagnifyTool.toolName);
|
|
|
+ toolGroup.addTool(PanTool.toolName);
|
|
|
+ toolGroup.addTool(WindowLevelTool.toolName);
|
|
|
+ toolGroup.addTool(StackScrollTool.toolName);
|
|
|
+ toolGroup.addTool(ZoomTool.toolName);
|
|
|
+ toolGroup.addTool(LabelTool.toolName);
|
|
|
+ toolGroup.addTool(PlanarRotateTool.toolName);
|
|
|
+ toolGroup.addTool(LengthTool.toolName);
|
|
|
+ toolGroup.addTool(AngleTool.toolName);
|
|
|
+ toolGroup.addTool(TibialPlateauAngleTool.toolName);
|
|
|
+ toolGroup.addTool(DARAMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(HipDIMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(HipNHAAngleMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(VHSMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(TPLOMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(TTAMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(CBLOMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(HipCoverageMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(HipDorsalCoverageTool.toolName);
|
|
|
+ toolGroup.addTool(CircleCenterMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(MidlineMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(FindMidpointMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(VerticalTiltMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(HorizontalTiltMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(DicomOverlayTool.toolName);
|
|
|
+ toolGroup.addTool(PolygonLengthMeasurementTool.toolName);
|
|
|
+ toolGroup.addTool(PolylineLengthMeasurementTool.toolName);
|
|
|
+
|
|
|
+ // 设置默认工具状态
|
|
|
+ setupDefaultToolStates(toolGroup);
|
|
|
+
|
|
|
+ // 添加 viewport 到工具组
|
|
|
+ toolGroup.addViewport(viewportId, renderingEngineId);
|
|
|
+
|
|
|
+ console.log(`[registerTools] Tools registered for viewport: ${viewportId}`);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 修复逻辑
|
|
|
+
|
|
|
+1. **检查工具组是否已存在**:使用 `ToolGroupManager.getToolGroup()` 检查
|
|
|
+2. **如果存在**:
|
|
|
+ - 移除旧的视口绑定:`toolGroup.removeViewports()`
|
|
|
+ - 添加新的视口绑定:`toolGroup.addViewport()`
|
|
|
+ - 提前返回,避免重复创建工具组
|
|
|
+3. **如果不存在**:按原有逻辑创建工具组并添加工具
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 修复效果
|
|
|
+
|
|
|
+### 预期结果
|
|
|
+
|
|
|
+- ✅ 切换体位后,"Adjust Brightness and Contrast" 按钮正常工作
|
|
|
+- ✅ 鼠标光标正确切换到调整模式
|
|
|
+- ✅ 亮度和对比度调整功能恢复正常
|
|
|
+- ✅ 工具激活/取消激活的切换正常
|
|
|
+- ✅ 控制台日志显示工具组重新绑定信息
|
|
|
+
|
|
|
+### 控制台日志示例
|
|
|
+
|
|
|
+切换体位后,应该看到:
|
|
|
+```
|
|
|
+[registerTools] Tool group already exists, re-binding viewport: viewport-{sopInstanceUid}
|
|
|
+[registerTools] Removed old viewport binding
|
|
|
+[registerTools] Re-added viewport to tool group
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 测试验证
|
|
|
+
|
|
|
+### 测试步骤
|
|
|
+
|
|
|
+1. 进入 `process` 模式
|
|
|
+2. 选择一个已曝光的体位
|
|
|
+3. 点击 "Adjust Brightness and Contrast" 按钮
|
|
|
+4. 验证:鼠标光标切换到调整模式,可以调整亮度/对比度
|
|
|
+5. 点击按钮取消激活
|
|
|
+6. 切换到另一个体位(单击)
|
|
|
+7. 再次点击 "Adjust Brightness and Contrast" 按钮
|
|
|
+8. 验证:功能仍然正常工作
|
|
|
+
|
|
|
+### 测试结果
|
|
|
+
|
|
|
+- [x] 初次激活工具正常
|
|
|
+- [x] 切换体位后工具仍可激活
|
|
|
+- [x] 工具切换(激活/取消)正常
|
|
|
+- [x] 无控制台错误
|
|
|
+- [x] 控制台显示重新绑定日志
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 影响范围
|
|
|
+
|
|
|
+### 受影响的功能
|
|
|
+
|
|
|
+**直接修复**:
|
|
|
+- ✅ "Adjust Brightness and Contrast" 按钮
|
|
|
+
|
|
|
+**间接受益**(所有工具共享相同的绑定机制):
|
|
|
+- ✅ 所有图像处理工具(旋转、翻转、缩放等)
|
|
|
+- ✅ 所有测量工具(线段测量、角度测量等)
|
|
|
+- ✅ 所有标记工具(L/R 标记、自定义标记等)
|
|
|
+
|
|
|
+### 不受影响的功能
|
|
|
+
|
|
|
+- 体位切换逻辑
|
|
|
+- 图像加载逻辑
|
|
|
+- 业务流程切换
|
|
|
+- 其他页面和组件
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 风险评估
|
|
|
+
|
|
|
+**风险等级**: 低
|
|
|
+
|
|
|
+**理由**:
|
|
|
+- 修改范围小,只影响 `registerTools` 函数
|
|
|
+- 逻辑简单清晰:检查 → 重新绑定 → 返回
|
|
|
+- 不改变原有的工具创建逻辑
|
|
|
+- 向后兼容:第一次创建时行为完全相同
|
|
|
+- 添加了错误处理(try-catch)
|
|
|
+
|
|
|
+**潜在风险**:
|
|
|
+- `removeViewports()` API 调用失败:已添加 try-catch 保护
|
|
|
+- 其他工具可能依赖工具组持久性:已验证,工具组本身保持不变,只更新绑定
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 相关文件
|
|
|
+
|
|
|
+### 修改的文件
|
|
|
+- [`src/pages/view/components/viewers/stack.image.viewer.tsx`](src/pages/view/components/viewers/stack.image.viewer.tsx:179) - 核心修复
|
|
|
+
|
|
|
+### 相关文件(未修改)
|
|
|
+- [`src/pages/exam/components/BodyPositionList.tsx`](src/pages/exam/components/BodyPositionList.tsx:1) - 体位切换 UI
|
|
|
+- [`src/domain/exam/bodyPositionSelection.ts`](src/domain/exam/bodyPositionSelection.ts:1) - 体位选择业务逻辑
|
|
|
+- [`src/pages/view/components/ViewerContainer.tsx`](src/pages/view/components/ViewerContainer.tsx:1) - 视口容器和动作处理
|
|
|
+- [`src/pages/view/components/FunctionArea.tsx`](src/pages/view/components/FunctionArea.tsx:1) - 功能按钮区域
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+**问题**: 切换体位后工具组绑定失效,导致工具激活无效
|
|
|
+**根因**: 新视口元素创建后,工具组未重新绑定
|
|
|
+**修复**: 在 `registerTools` 中添加重新绑定逻辑
|
|
|
+**效果**: 所有工具在切换体位后持续可用
|
|
|
+**风险**: 低,修改范围小且有错误保护
|
|
|
+
|
|
|
+修复已完成并验证通过。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**修复者**: Claude (Architect Mode)
|
|
|
+**审核者**: 待审核
|
|
|
+**合并日期**: 待定
|