# 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 ``` **问题流程**: 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) **审核者**: 待审核 **合并日期**: 待定