// 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(null); const [stackViewport, setStackViewport] = useState(null); const [hasImage, setHasImage] = useState(false); // 跟踪是否已加载图像 const [currentImageId, setCurrentImageId] = useState(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 => { 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 (
{ e.preventDefault(); // 允许放置 e.dataTransfer.dropEffect = 'copy'; }} onDrop={handleDrop} style={containerStyle} > {/* Cornerstone 渲染挂载点 */}
{/* 拖拽提示文字 - 基于Redux状态判断是否显示 */} {!currentCellImageId && !hasImage && (
拖拽序列到此处显示
)}
); }; export default ViewportContainer;