InvertContrastThumbnailList.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import React from 'react';
  2. import { useSelector, useDispatch } from 'react-redux';
  3. import { Radio } from 'antd';
  4. import { RootState, AppDispatch } from '../../../states/store';
  5. import { updateSelectedPositions } from '../../../states/view/invertContrastSlice';
  6. import { getExposedImageUrl, getViewIconUrl, getDcmImageUrl } from '../../../API/bodyPosition';
  7. import { ExtendedBodyPosition } from '../../../states/exam/bodyPositionListSlice';
  8. import { selectSelectedPositions } from '../../../states/view/invertContrastSlice';
  9. /**
  10. * InvertContrastThumbnailList - 反色对比缩略图列表组件
  11. *
  12. * 功能:显示当前检查的所有体位缩略图,支持单选用于反色对比
  13. * 选择一个体位:左侧显示原图,右侧显示反色图
  14. */
  15. const InvertContrastThumbnailList: React.FC = () => {
  16. const dispatch = useDispatch<AppDispatch>();
  17. // 获取所有体位
  18. const bodyPositions = useSelector(
  19. (state: RootState) => state.bodyPositionList.bodyPositions
  20. );
  21. // 获取当前选中的体位URLs
  22. const selectedPositions = useSelector(selectSelectedPositions);
  23. /**
  24. * 处理体位选择变更 - 单选模式,每次选择替换当前选择
  25. */
  26. const handlePositionSelect = (bodyPosition: ExtendedBodyPosition) => {
  27. const dcmUrl = getDcmImageUrl(bodyPosition.sop_instance_uid);
  28. // 单选模式:直接替换为当前选择的体位
  29. dispatch(updateSelectedPositions({ positions: [dcmUrl] }));
  30. };
  31. /**
  32. * 获取体位对应的图像URL
  33. */
  34. const getPositionImageUrl = (bodyPosition: ExtendedBodyPosition): string => {
  35. return bodyPosition.dview.expose_status === 'Exposed'
  36. ? getExposedImageUrl(bodyPosition.sop_instance_uid)
  37. : getViewIconUrl(bodyPosition.view_icon_name);
  38. };
  39. /**
  40. * 检查体位是否被选中
  41. */
  42. const isPositionSelected = (bodyPosition: ExtendedBodyPosition): boolean => {
  43. const dcmUrl = getDcmImageUrl(bodyPosition.sop_instance_uid);
  44. return selectedPositions.includes(dcmUrl);
  45. };
  46. return (
  47. <div
  48. className="flex flex-col gap-2 overflow-y-auto"
  49. style={{
  50. height: '100%',
  51. padding: '8px',
  52. backgroundColor: '#f5f5f5',
  53. borderRadius: '4px'
  54. }}
  55. >
  56. <div className="text-sm font-medium text-gray-700 mb-2 px-2">
  57. 选择对比体位
  58. </div>
  59. {bodyPositions.map((bodyPosition, index) => {
  60. const imageUrl = getPositionImageUrl(bodyPosition);
  61. const isSelected = isPositionSelected(bodyPosition);
  62. return (
  63. <div
  64. key={bodyPosition.sop_instance_uid || index}
  65. className={`relative border rounded-md overflow-hidden transition-all ${
  66. isSelected ? 'border-blue-500 shadow-md' : 'border-gray-300'
  67. } cursor-pointer hover:shadow-sm`}
  68. style={{
  69. width: '100%',
  70. aspectRatio: '1',
  71. backgroundColor: '#fff'
  72. }}
  73. onClick={() => handlePositionSelect(bodyPosition)}
  74. >
  75. {/* 单选按钮 */}
  76. <div className="absolute top-1 left-1 z-20">
  77. <Radio
  78. checked={isSelected}
  79. style={{
  80. backgroundColor: 'rgba(255,255,255,0.9)',
  81. borderRadius: '2px',
  82. padding: '2px'
  83. }}
  84. />
  85. </div>
  86. {/* 体位名称标签 */}
  87. <div className="absolute bottom-1 left-1 text-xs bg-black bg-opacity-70 text-white px-1 py-0.5 rounded z-10">
  88. {bodyPosition.view_name}
  89. </div>
  90. {/* 缩略图 */}
  91. <div className={`${isSelected ? 'ring-2 ring-blue-500' : ''} w-full h-full overflow-hidden`}>
  92. <img
  93. src={imageUrl}
  94. alt={bodyPosition.view_name}
  95. className="w-full h-full object-cover cursor-pointer"
  96. draggable={false}
  97. />
  98. </div>
  99. {/* 状态标签 */}
  100. {bodyPosition.dview.expose_status === 'Exposed' && (
  101. <div className="absolute top-1 right-1 z-10 flex gap-1">
  102. {bodyPosition.dview.judged_status && (
  103. <span
  104. className={`text-xs px-1 py-0.5 rounded text-white ${
  105. bodyPosition.dview.judged_status === 'Accept'
  106. ? 'bg-green-500'
  107. : bodyPosition.dview.judged_status === 'Reject'
  108. ? 'bg-red-500'
  109. : 'bg-yellow-500'
  110. }`}
  111. >
  112. {bodyPosition.dview.judged_status === 'Accept' ? '✓' :
  113. bodyPosition.dview.judged_status === 'Reject' ? '✗' : '?'}
  114. </span>
  115. )}
  116. </div>
  117. )}
  118. </div>
  119. );
  120. })}
  121. </div>
  122. );
  123. };
  124. export default InvertContrastThumbnailList;