Bläddra i källkod

feat: 实现有状态按钮工具状态自动同步功能 (版本: 1.16.0 1.17.0)

实现cornerstone工具激活状态与按钮状态的自动同步机制,确保按钮状态始终反映工具的实际激活状态。

核心功能:
- 新增 ToolStateListener 类监听 cornerstone 工具状态变化事件
- 扩展 Redux 状态管理维护工具激活状态映射
- 修改按钮状态判断逻辑基于工具激活状态显示激活效果
- 在应用初始化时自动启动工具状态监听器

技术实现:
- 创建工具状态监听器自动更新 Redux 状态
- 有状态按钮状态自动反映 cornerstone 工具激活状态
- 单例模式确保监听器唯一性,避免重复监听
- 完善的错误处理和日志记录

改动文件:
- src/utils/toolStateListener.ts (新增)
- src/states/view/functionAreaSlice.ts
- src/pages/view/components/FunctionArea.tsx
- src/app.tsx
- CHANGELOG.md
- package.json (版本更新: 1.16.0 -> 1.17.0)
dengdx 3 veckor sedan
förälder
incheckning
db24db5bd4

+ 32 - 0
CHANGELOG.md

@@ -2,6 +2,38 @@
 
 本项目的所有重要变更都将记录在此文件中。
 
+## [1.17.0] - 2025-12-20 16:00
+
+### 新增 (Added)
+- **有状态按钮工具状态自动同步功能** - 实现cornerstone工具激活状态与按钮状态的自动同步
+  - 新增 `ToolStateListener` 类,监听cornerstone工具状态变化事件
+  - 实现Redux状态管理,维护激活工具的状态映射
+  - 修改按钮状态判断逻辑,有状态按钮基于工具激活状态显示激活效果
+  - 在应用初始化时自动启动工具状态监听器
+  - 支持"Adjust Brightness and Contrast"和"Magnifier"按钮的状态自动同步
+
+**核心功能实现:**
+- 工具状态监听:监听`TOOL_MODE_CHANGED`事件,自动更新Redux状态
+- 按钮状态同步:有状态按钮的激活状态自动反映cornerstone工具的实际状态
+- 单例模式:确保只有一个工具状态监听器实例,避免重复监听
+- 错误处理:完善的异常处理和日志记录,确保系统稳定性
+
+**技术实现:**
+- 创建`src/utils/toolStateListener.ts`工具状态监听器
+- 扩展`functionAreaSlice.ts`添加工具激活状态管理
+- 修改`FunctionArea.tsx`按钮状态基于工具激活状态判断
+- 在`app.tsx`应用初始化时启动监听器
+
+**改动文件:**
+- src/utils/toolStateListener.ts (新增)
+- src/states/view/functionAreaSlice.ts
+- src/pages/view/components/FunctionArea.tsx
+- src/app.tsx
+- CHANGELOG.md
+- package.json (版本更新: 1.16.0 -> 1.17.0)
+
+---
+
 ## [1.16.0] - 2025-12-20 14:06
 
 ### 新增 (Added)

+ 1 - 1
package.json

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

+ 4 - 0
src/app.tsx

@@ -8,6 +8,7 @@ import { initializeProductState } from './states/productSlice';
 import { loadI18nMessages } from './states/i18nSlice';
 import { checkServerConnection } from './features/serverConfig';
 import { initializeAnnotationManager, cleanupAnnotationManager } from './features/imageAnnotation';
+import { ToolStateListener } from './utils/toolStateListener';
 import { platform } from './utils/platform';
 import './app.css';
 import ProductSelector from './components/ProductSelector';
@@ -119,6 +120,9 @@ function AppContent({ children }: { children: ReactNode }): JSX.Element {
       // ✅ 初始化注释管理器(在产品状态和国际化之后)
       await initializeAnnotationManager();
 
+      // ✅ 初始化工具状态监听器(监听cornerstone工具状态变化)
+      ToolStateListener.getInstance().init();
+
       setIsI18nReady(true);
     } catch (error) {
       console.error('应用初始化失败:', error);

+ 12 - 3
src/pages/view/components/FunctionArea.tsx

@@ -9,6 +9,12 @@ import { showNotImplemented } from '@/utils/notificationHelper';
 import { useAppSelector } from '@/states/store';
 import { useButtonAvailability } from '@/utils/useButtonAvailability';
 
+// 有状态按钮列表 - 这些按钮代表持续的状态,不应在执行后清理action
+const STATEFUL_BUTTON_ACTIONS = new Set([
+  'Adjust Brightness and Contrast',
+  'Magnifier',
+]);
+
 const FunctionButton = ({
   title,
   action,
@@ -20,11 +26,14 @@ const FunctionButton = ({
 }) => {
   const dispatch = useDispatch();
   const themeType = useAppSelector((state) => state.theme.themeType);
-  const currentAction = useAppSelector((state) => state.functionArea.action);
+  const activeTools = useAppSelector((state) => state.functionArea.activeTools);
   const { disabled } = useButtonAvailability(action);
-  const isActive = currentAction === action;
+  // 对于有状态按钮,基于工具激活状态;对于无状态按钮,基于action
+  const isActive = STATEFUL_BUTTON_ACTIONS.has(action)
+    ? activeTools[action] || false
+    : false; // 无状态按钮不显示激活状态
   const [isHovered, setIsHovered] = useState(false);
-  
+
   // 根据状态计算Icon的样式
   const getIconStyle = () => {
     if (disabled) {

+ 12 - 1
src/states/view/functionAreaSlice.ts

@@ -2,10 +2,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 
 interface FunctionAreaState {
   action: string | null;
+  actionTrigger: number;
+  activeTools: Record<string, boolean>; // 工具激活状态映射
 }
 
 const initialState: FunctionAreaState = {
   action: null,
+  actionTrigger: 0,
+  activeTools: {}, // 初始化为空对象
 };
 
 const functionAreaSlice = createSlice({
@@ -14,12 +18,19 @@ const functionAreaSlice = createSlice({
   reducers: {
     setAction: (state, action: PayloadAction<string>) => {
       state.action = action.payload;
+      state.actionTrigger += 1;
     },
     clearAction: (state) => {
       state.action = null;
     },
+    setToolActive: (state, action: PayloadAction<string>) => {
+      state.activeTools[action.payload] = true;
+    },
+    setToolInactive: (state, action: PayloadAction<string>) => {
+      state.activeTools[action.payload] = false;
+    },
   },
 });
 
-export const { setAction, clearAction } = functionAreaSlice.actions;
+export const { setAction, clearAction, setToolActive, setToolInactive } = functionAreaSlice.actions;
 export default functionAreaSlice.reducer;

+ 56 - 0
src/utils/toolStateListener.ts

@@ -0,0 +1,56 @@
+import { eventTarget } from '@cornerstonejs/core';
+import { Enums } from '@cornerstonejs/tools';
+import store from '@/states/store';
+import { setToolActive, setToolInactive } from '@/states/view/functionAreaSlice';
+
+// 工具名称到action的映射
+const TOOL_TO_ACTION_MAP: Record<string, string> = {
+  'WindowLevel': 'Adjust Brightness and Contrast',
+  'Magnify': 'Magnifier',
+  // 可以在这里添加其他工具映射
+};
+
+export class ToolStateListener {
+  private static instance: ToolStateListener;
+  private initialized = false;
+
+  static getInstance(): ToolStateListener {
+    if (!ToolStateListener.instance) {
+      ToolStateListener.instance = new ToolStateListener();
+    }
+    return ToolStateListener.instance;
+  }
+
+  init() {
+    if (this.initialized) return;
+
+    eventTarget.addEventListener(Enums.Events.TOOL_MODE_CHANGED, this.handleToolModeChanged);
+    this.initialized = true;
+    console.log('[ToolStateListener] Initialized and listening for tool mode changes');
+  }
+
+  private handleToolModeChanged = (event: any) => {
+    const { toolName, mode } = event.detail;
+
+    const action = TOOL_TO_ACTION_MAP[toolName];
+    if (!action) return;
+
+    const isActive = mode === Enums.ToolModes.Active;
+
+    console.log(`[ToolStateListener] Tool ${toolName} mode changed: ${isActive ? 'active' : 'inactive'}`);
+
+    if (isActive) {
+      store.dispatch(setToolActive(action));
+    } else {
+      store.dispatch(setToolInactive(action));
+    }
+  };
+
+  destroy() {
+    if (this.initialized) {
+      eventTarget.removeEventListener(Enums.Events.TOOL_MODE_CHANGED, this.handleToolModeChanged);
+      this.initialized = false;
+      console.log('[ToolStateListener] Destroyed');
+    }
+  }
+}