在图像处理页面中,用户可以点击"反色对比"按钮,系统会打开一个Modal弹窗,在弹窗内部显示1x2布局,左侧显示原始图像,右侧显示反色效果图,方便用户对比查看同一张图像的不同呈现效果。
FunctionArea.tsx:156-160 中的"反色对比"按钮invert 属性)viewport.setProperties({ invert: true }) 实现反色整个界面呈现为一个医学影像查看系统的布局。 主要区域为5列分屏结构:左侧垂直缩略图栏 + 中间左主影像 + 中间右主影像 + 底部工具栏 + 顶部标题栏。
InvertContrastModal (新建)
src/pages/view/components/InvertContrastModal.tsxuseEffect 中添加 case 处理applyInvert propfunctionAreaSlice
invertContrastSlice (新建)
src/states/view/invertContrastSlice.tsisModalOpen: boolean - Modal是否打开imageUrl: string - 要对比的图像URLinvertContrast()
原始实现使用了 getExposedImageUrl() 显示JPG图像,但Modal应该显示DCM文件。
需要修改:
getDcmImageUrl(bodyPosition.sop_instance_uid) 获取DCM URL创建 Modal 组件
src/pages/view/components/InvertContrastModal.tsx
创建缩略图列表组件
src/pages/view/components/InvertContrastThumbnailList.tsx
创建工具栏组件
src/pages/view/components/InvertContrastToolbar.tsx
创建 Redux Slice
src/states/view/invertContrastSlice.ts
isModalOpen, selectedPositions: string[]openInvertContrastModal, updateSelectedPositions, closeInvertContrastModalselectIsModalOpen, selectSelectedPositions修改 ViewerContainer
src/pages/view/components/ViewerContainer.tsx
useEffect 中添加 Invert Contrast 的 case 处理修改 Redux Store
src/states/store.ts
invertContrastSlice reducer修改文档
sequenceDiagram
participant U as 用户
participant FB as FunctionButton
participant FA as functionAreaSlice
participant VC as ViewerContainer
participant IC as invertContrastSlice
participant Modal as InvertContrastModal
participant SV1 as StackViewer左
participant SV2 as StackViewer右
U->>FB: 点击反色对比按钮
FB->>FA: dispatch setAction Invert Contrast
FA->>VC: action = Invert Contrast
VC->>IC: dispatch openInvertContrastModal imageUrl
IC->>IC: 设置 isModalOpen = true
IC->>IC: 保存 imageUrl
IC->>Modal: isModalOpen = true
Modal->>Modal: 打开Modal
Modal->>SV1: 创建左侧viewport 渲染原图
Modal->>SV2: 创建右侧viewport 渲染反色图
SV2->>SV2: 应用 invert=true
SV1->>U: 显示原图
SV2->>U: 显示反色图
U->>Modal: 点击关闭按钮
Modal->>IC: dispatch closeInvertContrastModal
IC->>IC: 设置 isModalOpen = false
Modal->>U: 关闭Modal
VC->>FA: dispatch clearAction
stateDiagram-v2
[*] --> Normal: 初始状态
Normal --> ModalOpen: 点击反色对比 保存 imageUrl
state ModalOpen {
[*] --> RenderModal: 打开Modal
RenderModal --> CreateViewports: 创建1x2布局
CreateViewports --> LeftOriginal: 左侧原图
CreateViewports --> RightInverted: 右侧反色
}
ModalOpen --> Normal: 点击关闭 关闭Modal
Normal --> [*]
// invertContrastSlice State
interface InvertContrastState {
isModalOpen: boolean; // Modal是否打开
imageUrl: string; // 要对比的图像URL
}
// ViewerContainer.tsx 中的处理逻辑
case 'Invert Contrast': {
const selectedUrl = selectedViewerUrls[0]; // 当前选中的图像URL
if (!selectedUrl) {
message.warning('请先选择一张图像');
break;
}
// 打开Modal
dispatch(openInvertContrastModal({ imageUrl: selectedUrl }));
break;
}
// src/pages/view/components/InvertContrastModal.tsx
import React, { useEffect } from 'react';
import { Modal } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
import { selectIsModalOpen, selectImageUrl, closeInvertContrastModal } from '@/states/view/invertContrastSlice';
import { StackViewerWithErrorBoundary } from './viewers/stack.image.viewer';
import { invertContrast } from './viewers/stack.image.viewer';
const InvertContrastModal: React.FC = () => {
const dispatch = useDispatch();
const isOpen = useSelector(selectIsModalOpen);
const imageUrl = useSelector(selectImageUrl);
const renderingEngineId = 'invertContrastEngine'; // 独立的渲染引擎
const handleClose = () => {
dispatch(closeInvertContrastModal());
};
useEffect(() => {
if (isOpen && imageUrl) {
// Modal打开后,等待右侧viewport创建完成,然后应用反色
setTimeout(() => {
const rightViewportId = 'invert-contrast-right';
try {
invertContrast(rightViewportId);
} catch (error) {
console.error('Failed to apply invert:', error);
}
}, 200);
}
}, [isOpen, imageUrl]);
return (
<Modal
title="反色对比"
open={isOpen}
onCancel={handleClose}
width="90vw"
style={{ top: 20 }}
bodyStyle={{ height: 'calc(90vh - 110px)' }}
footer={null}
destroyOnClose // 关闭时销毁内容,避免状态残留
>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', height: '100%' }}>
{/* 左侧:原图 */}
<div style={{ border: '1px solid #d9d9d9', position: 'relative' }}>
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 10, background: 'rgba(0,0,0,0.7)', color: '#fff', padding: '4px 8px', borderRadius: '4px' }}>
原图
</div>
<StackViewerWithErrorBoundary
imageIndex={0}
imageUrls={[imageUrl]}
viewportId="invert-contrast-left"
renderingEngineId={renderingEngineId}
selected={false}
/>
</div>
{/* 右侧:反色图 */}
<div style={{ border: '1px solid #d9d9d9', position: 'relative' }}>
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 10, background: 'rgba(0,0,0,0.7)', color: '#fff', padding: '4px 8px', borderRadius: '4px' }}>
反色图
</div>
<StackViewerWithErrorBoundary
imageIndex={0}
imageUrls={[imageUrl]}
viewportId="invert-contrast-right"
renderingEngineId={renderingEngineId}
selected={false}
/>
</div>
</div>
</Modal>
);
};
export default InvertContrastModal;
// src/states/view/invertContrastSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../store';
interface InvertContrastState {
isModalOpen: boolean;
imageUrl: string;
}
const initialState: InvertContrastState = {
isModalOpen: false,
imageUrl: '',
};
const invertContrastSlice = createSlice({
name: 'invertContrast',
initialState,
reducers: {
openInvertContrastModal: (
state,
action: PayloadAction<{ imageUrl: string }>
) => {
state.isModalOpen = true;
state.imageUrl = action.payload.imageUrl;
},
closeInvertContrastModal: (state) => {
state.isModalOpen = false;
state.imageUrl = '';
},
},
});
export const {
openInvertContrastModal,
closeInvertContrastModal,
} = invertContrastSlice.actions;
// Selectors
export const selectIsModalOpen = (state: RootState) => state.invertContrast.isModalOpen;
export const selectImageUrl = (state: RootState) => state.invertContrast.imageUrl;
export default invertContrastSlice.reducer;
flowchart TD
Start([用户点击反色对比按钮]) --> CheckImage{检查是否选中图像?}
CheckImage -->|未选中| ShowWarning[显示提示信息]
CheckImage -->|已选中| OpenModal[打开Modal]
ShowWarning --> End([结束])
OpenModal --> SaveUrl[保存图像URL到state]
SaveUrl --> RenderModal[渲染Modal组件]
RenderModal --> CreateLayout[创建1x2布局]
CreateLayout --> CreateLeft[创建左侧viewport 原图]
CreateLayout --> CreateRight[创建右侧viewport]
CreateRight --> ApplyInvert[应用invert属性到右侧]
ApplyInvert --> Display[显示对比效果]
Display --> UserClose{用户点击关闭?}
UserClose -->|是| CloseModal[关闭Modal]
UserClose -->|否| Display
CloseModal --> ClearState[清空state]
ClearState --> End
setAction("Invert Contrast")dispatch(openInvertContrastModal({ imageUrl }))isModalOpen = true,显示弹窗invert-contrast-left (invert=false)invert-contrast-right (invert=true)invertContrast('invert-contrast-right')dispatch(closeInvertContrastModal())isModalOpen = falsedestroyOnClose 自动清理viewportselectedViewerUrls.lengthmessage.warning('请先选择一张图像')selectedViewerUrls[0]invertContrastEngineinvertContrast() 失败解决方案:
useEffect(() => {
if (isOpen && imageUrl) {
setTimeout(() => {
invertContrast('invert-contrast-right');
}, 200); // 延迟200ms
}
}, [isOpen, imageUrl]);
destroyOnClose={true}imageUrls 数组imageIndex 状态typescript
width="90vw"
style={{ top: 20 }}
bodyStyle={{ height: 'calc(90vh - 110px)' }}
classDiagram
class FunctionButton {
+string title
+string action
+string iconName
+handleButtonClick()
}
class ViewerContainer {
+useEffect() handleAction
}
class InvertContrastModal {
+boolean isOpen
+string imageUrl
+handleClose()
+useEffect() applyInvert
}
class StackViewer {
+string viewportId
+string[] imageUrls
+number imageIndex
+useEffect() setup
}
class invertContrastSlice {
+boolean isModalOpen
+string imageUrl
+openInvertContrastModal()
+closeInvertContrastModal()
}
class functionAreaSlice {
+string action
+setAction()
+clearAction()
}
FunctionButton --> functionAreaSlice : dispatch setAction
ViewerContainer --> functionAreaSlice : useSelector action
ViewerContainer --> invertContrastSlice : dispatch open
InvertContrastModal --> invertContrastSlice : useSelector state
InvertContrastModal --> StackViewer : renders 2 instances
StackViewer --> invertContrast : applies to right
invertContrastSliceInvertContrastModal 组件(基础版)ViewerContainer 处理 "Invert Contrast" actionselectedPositions: string[]数组,支持多选体位selectImageUrl选择器返回第一个选中体位updateSelectedPositions用于更新选中数组InvertContrastThumbnailList组件InvertContrastToolbar组件(基础按钮结构)invertContrastEngine渲染引擎请测试当前实现是否正常工作。如需调整界面逻辑或实现具体工具功能,请提供反馈。
文档版本: v2.0 (Modal版本)
创建日期: 2025-12-10
更新日期: 2025-12-10
作者: Roo (Architect Mode)