| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- // ViewportContainer.jsx
- import React, { useRef, useEffect, useState, useMemo } from 'react';
- import * as cornerstone from '@cornerstonejs/core';
- import * as cornerstoneTools from '@cornerstonejs/tools';
- import {
- RenderingEngine,
- Enums
- } from '@cornerstonejs/core';
- import { v4 as uuidv4 } from 'uuid';
- import { getDcmImageUrl } from '@/API/bodyPosition';
- import { useDispatch } from 'react-redux';
- import { addImageToCell, Film, toggleCellSelection } from '@/states/print/printSlice';
- import { useAppSelector } from '@/states/store';
- // import { dicomWebImageLoader } from '@cornerstonejs/dicom-web-image-loader';
- // // 初始化图像加载器(关键:必须提前注册)
- // const initImageLoader = () => {
- // // 注册 DICOM Web 加载器(根据实际场景选择加载器)
- // cornerstone.registerImageLoader('dicomweb', dicomWebImageLoader);
- // // 配置加载器(Worker 路径需指向实际文件)
- // dicomWebImageLoader.configure({
- // webWorkerPath: '/cornerstone-workers/',
- // maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
- // });
- // };
- type ViewportContainerProps = {
- imageId: string;
- currentFilm: Film; // 传入当前胶片对象
- indexOfCell: number; // 可选,格子索引
- className?: string; // 可选
- };
- const ViewportContainer = ({ imageId, className, currentFilm, indexOfCell }: ViewportContainerProps) => {
- console.log(`ViewportContainer 得到的属性 :${imageId}`)
- const viewportRef = useRef(null);
- const [renderingEngine, setRenderingEngine] = useState<any>(null);
- const [stackViewport, setStackViewport] = useState<any>(null);
- const [hasImage, setHasImage] = useState<boolean>(false); // 跟踪是否已加载图像
- const [currentImageId, setCurrentImageId] = useState<string | null>(null); // 跟踪当前已加载的图像ID
- // ✅ 用 useMemo 保证 id 只生成一次,包含胶片ID和格子索引确保唯一性
- const viewportId = useMemo(() =>
- `stackViewport-${currentFilm.id}-${indexOfCell}-${uuidv4()}`,
- [currentFilm.id, indexOfCell]
- );
- const dispatch = useDispatch();
-
- // 生成格子的唯一标识符
- const cellId = `${currentFilm.id}-${indexOfCell}`;
-
- // 检查当前格子是否被选中
- const isSelected = useAppSelector(state =>
- state.print.selectedCells.includes(cellId)
- );
- // 监听Redux中当前格子的图像ID状态
- const currentCellImageId = useAppSelector(state => {
- const film = state.print.films.find(f => f.id === currentFilm.id);
- return film?.images[indexOfCell] || null;
- });
- // 统一的图像加载函数,供两种场景使用
- const loadImage = async (targetImageId: string, source: 'props' | 'drag' = 'props'): Promise<boolean> => {
- if (!stackViewport || !targetImageId) return false;
-
- try {
- const fullImageUrl = targetImageId.startsWith('dicomweb:')
- ? targetImageId
- : getDcmImageUrl(targetImageId);
-
- await stackViewport.setStack([fullImageUrl], 0);
- stackViewport.render();
- setHasImage(true);
-
- console.log(`[DcmCell] 图像加载成功 (${source}): ${targetImageId}`);
- return true;
- } catch (error) {
- console.error(`[DcmCell] 图像加载失败 (${source}):`, error);
- setHasImage(false);
- return false;
- }
- };
- // 初始化 Cornerstone 渲染引擎
- useEffect(() => {
- // initImageLoader();
- // 创建胶片特定的渲染引擎实例,确保状态隔离
- const engineId = `renderingEngine-${currentFilm.id}`;
- let engine: RenderingEngine | null = cornerstone.getRenderingEngines()?.find(en => en.id === engineId) || null;
- // 不存在则创建
- if (!engine) {
- engine = new RenderingEngine(engineId);
- }
- setRenderingEngine(engine);
- // 配置 Viewport
- if (viewportRef.current) {
- const viewportInput: cornerstone.Types.PublicViewportInput = {
- viewportId: viewportId,
- type: Enums.ViewportType.STACK, // 序列视图类型
- element: viewportRef.current,
- defaultOptions: {
- background: [0.1, 0.1, 0.1], // 深灰背景
- },
- };
- // 绑定 Viewport 到引擎
- engine.enableElement(viewportInput);
- // 获取 Viewport 实例
- const viewport = engine.getViewport(viewportId);
- setStackViewport(viewport);
- // ========== 添加工具支持 ==========
- const toolGroupId = `PRINT_TOOL_GROUP_${viewportId}`;
- // 创建工具组
- const toolGroup = cornerstoneTools.ToolGroupManager.createToolGroup(toolGroupId);
- if (toolGroup) {
- // 添加基础工具
- toolGroup.addTool(cornerstoneTools.StackScrollTool.toolName);
- toolGroup.addTool(cornerstoneTools.ZoomTool.toolName);
- toolGroup.addTool(cornerstoneTools.PanTool.toolName);
- toolGroup.addTool(cornerstoneTools.WindowLevelTool.toolName);
- // 激活滚轮滚动工具(解决滚轮错误)
- toolGroup.setToolActive(cornerstoneTools.StackScrollTool.toolName, {
- bindings: [{
- mouseButton: cornerstoneTools.Enums.MouseBindings.Wheel
- }]
- });
- // 激活缩放工具(右键)
- toolGroup.setToolActive(cornerstoneTools.ZoomTool.toolName, {
- bindings: [{
- mouseButton: cornerstoneTools.Enums.MouseBindings.Secondary
- }]
- });
- // 激活平移工具(中键)
- toolGroup.setToolActive(cornerstoneTools.PanTool.toolName, {
- bindings: [{
- mouseButton: cornerstoneTools.Enums.MouseBindings.Auxiliary
- }]
- });
- // 其他工具设置为被动状态
- toolGroup.setToolPassive(cornerstoneTools.WindowLevelTool.toolName);
- // 将 viewport 添加到工具组
- toolGroup.addViewport(viewportId, engineId);
- console.log(`[DcmCell] 工具组已创建并配置: ${toolGroupId}`);
- }
- }
- // 组件卸载时清理
- return () => {
- // 销毁工具组
- const toolGroupId = `PRINT_TOOL_GROUP_${viewportId}`;
- const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
- if (toolGroup) {
- try {
- // 先从工具组中移除 viewport
- toolGroup.removeViewports(`renderingEngine-${currentFilm.id}`, viewportId);
- // 然后销毁工具组
- cornerstoneTools.ToolGroupManager.destroyToolGroup(toolGroupId);
- console.log(`[DcmCell] 工具组已销毁: ${toolGroupId}`);
- } catch (error) {
- console.warn(`[DcmCell] 清理工具组时出错: ${error}`);
- }
- }
- // 清理 viewport
- if (engine) {
- try {
- engine.disableElement(viewportId);
- console.log(`[DcmCell] viewport 已禁用: ${viewportId}`);
- } catch (error) {
- console.warn(`[DcmCell] 禁用 viewport 时出错: ${error}`);
- }
- }
- };
- }, []);
- // 监听 imageId 变化,自动加载图像
- useEffect(() => {
- const loadImageFromProps = async () => {
- if (!stackViewport) return;
-
- // 避免重复加载相同图像
- if (imageId === currentImageId) return;
-
- if (imageId && imageId.trim() !== '') {
- console.log(`[DcmCell] 开始从 props 加载图像: ${imageId}`);
- const success = await loadImage(imageId, 'props');
- if (success) {
- setCurrentImageId(imageId);
- }
- } else {
- // imageId 为空时清空显示
- if (stackViewport) {
- try {
- // 清空 viewport 中的图像堆栈
- // await stackViewport.setStack([]);
- // stackViewport.render();
- renderingEngine.disableElement(viewportId);
- } catch (error) {
- console.warn(`[DcmCell] 清空 viewport 时出错: ${error}`);
- }
- }
- setHasImage(false);
- setCurrentImageId(null);
- console.log(`[DcmCell] imageId 为空,清空显示`);
- }
- };
- loadImageFromProps();
- }, [imageId, stackViewport]);
- // 监听Redux状态变化,当图像被删除时清理viewport
- useEffect(() => {
- if (!stackViewport) return;
-
- // 如果Redux中的图像ID为null,但viewport还有图像,则清理
- if (!currentCellImageId && hasImage) {
- try {
- // 清空viewport的图像栈
- // stackViewport.setStack([]);
- // stackViewport.render();
- renderingEngine.disableElement(viewportId);
- setHasImage(false);
- setCurrentImageId(null);
- console.log(`[DcmCell] 清理viewport: ${cellId} (图像已被删除)`);
- } catch (error) {
- console.warn(`[DcmCell] 清理viewport时出错:`, error);
- }
- }
- }, [currentCellImageId, stackViewport, hasImage, cellId]);
- // 处理拖放事件(核心逻辑)
- const handleDrop = async (e) => {
- e.preventDefault();
- e.stopPropagation();
- // 1. 获取拖拽传递的序列标识
- const seriesInstanceUID = e.dataTransfer.getData('seriesInstanceUID');
- if (!seriesInstanceUID) return;
- try {
- // 2. 获取该序列的所有图像 ID(需替换为实际接口)
- const imageIds = getDcmImageUrl(seriesInstanceUID);
- console.log(`拖拽后要加载图像 是 ${imageIds}`);
-
- // 3. 使用统一的加载函数
- const success = await loadImage(imageIds, 'drag');
-
- if (success) {
- setCurrentImageId(imageIds);
- // 4. 通知到slice
- dispatch(addImageToCell({ index: indexOfCell, imageId: imageIds }));
- }
- } catch (error) {
- console.error('[DcmCell] 拖拽加载图像失败:', error);
- }
- };
- // 处理格子点击事件
- const handleCellClick = (e: React.MouseEvent) => {
- e.stopPropagation();
- dispatch(toggleCellSelection(cellId));
- };
- // 动态样式
- const containerStyle = {
- width: '100%',
- height: '600px',
- border: isSelected ? '3px solid #0066cc' : '2px dashed #666',
- borderRadius: '8px',
- position: 'relative' as const,
- cursor: 'pointer',
- backgroundColor: isSelected ? 'rgba(0, 102, 204, 0.1)' : 'transparent',
- boxShadow: isSelected ? '0 0 10px rgba(0, 102, 204, 0.3)' : 'none',
- transition: 'all 0.2s ease-in-out'
- };
- return (
- <div
- className="viewport-container"
- onClick={handleCellClick}
- onDragOver={(e) => {
- e.preventDefault(); // 允许放置
- e.dataTransfer.dropEffect = 'copy';
- }}
- onDrop={handleDrop}
- style={containerStyle}
- >
- {/* Cornerstone 渲染挂载点 */}
- <div ref={viewportRef} style={{ width: '100%', height: '100%' }} />
- {/* 拖拽提示文字 - 基于Redux状态判断是否显示 */}
- {!currentCellImageId && !hasImage && (
- <div style={{
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- color: '#999',
- pointerEvents: 'none' // 防止遮挡鼠标事件
- }}>
- 拖拽序列到此处显示
- </div>
- )}
- </div>
- );
- };
- export default ViewportContainer;
|