ImageStateControl.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import React, { useMemo } from 'react';
  2. import { Button, Flex, message, Modal } from 'antd';
  3. import { useSelector } from 'react-redux';
  4. import { RootState, useAppDispatch } from '@/states/store';
  5. import {
  6. selectGridLayout,
  7. selectSelectedViewers,
  8. } from '@/states/view/viewerContainerSlice';
  9. import {
  10. judgeImageThunk,
  11. saveImageAsThunk
  12. } from '@/states/exam/bodyPositionListSlice';
  13. import { getDcmImageUrl } from '@/API/bodyPosition';
  14. import Icon from '@/components/Icon';
  15. import { useButtonAvailability } from '@/utils/useButtonAvailability';
  16. /**
  17. * 图像状态控制组件
  18. *
  19. * 功能:
  20. * - 拒绝按钮:将图像标记为拒绝状态
  21. * - 恢复按钮:将已拒绝的图像恢复为接受状态
  22. * - 另存为按钮:预留功能(当前禁用)
  23. *
  24. * 业务规则:
  25. * - 仅在单分格模式(1x1)下显示拒绝/恢复按钮
  26. * - 拒绝和恢复按钮互斥显示,基于 judged_status
  27. * - 另存为按钮始终可见但禁用
  28. */
  29. const ImageStateControl: React.FC = () => {
  30. const dispatch = useAppDispatch();
  31. const { disabled:ofReject } = useButtonAvailability('拒绝');
  32. const { disabled:ofRecover } = useButtonAvailability('恢复');
  33. const { disabled:ofSaveAs } = useButtonAvailability('另存为');
  34. // 获取必要的状态
  35. const selectedViewers = useSelector(selectSelectedViewers);
  36. const gridLayout = useSelector(selectGridLayout);
  37. const bodyPositionList = useSelector(
  38. (state: RootState) => state.bodyPositionList.bodyPositions
  39. );
  40. const loading = useSelector(
  41. (state: RootState) => state.bodyPositionList.loading
  42. );
  43. // 查找当前选中的图像
  44. const selectedImage = useMemo(() => {
  45. if (selectedViewers.length === 0) return null;
  46. const imageUrl = selectedViewers[0];
  47. return bodyPositionList.find(
  48. (bp) => getDcmImageUrl(bp.sop_instance_uid) === imageUrl
  49. );
  50. }, [selectedViewers, bodyPositionList]);
  51. // 判断按钮可见性
  52. const isSingleGrid = gridLayout === '1x1';
  53. const judgedStatus = selectedImage?.dview?.judged_status || '';
  54. const showRejectButton = isSingleGrid && judgedStatus !== 'Reject';
  55. const showRestoreButton = isSingleGrid && judgedStatus === 'Reject';
  56. // 拒绝按钮处理函数
  57. const handleReject = async () => {
  58. if (!selectedImage) return;
  59. Modal.confirm({
  60. title: '确认拒绝',
  61. content: `确定要拒绝选中的图像吗?`,
  62. okText: '确认拒绝',
  63. cancelText: '取消',
  64. okButtonProps: {
  65. danger: true,
  66. 'data-testid': 'modal-confirm-delete'
  67. },
  68. cancelButtonProps: {
  69. 'data-testid': 'modal-cancel-delete'
  70. },
  71. centered: true,
  72. onOk: async () => {
  73. try {
  74. await dispatch(
  75. judgeImageThunk({
  76. sopInstanceUid: selectedImage.sop_instance_uid,
  77. accept: false,
  78. })
  79. ).unwrap();
  80. message.success('图像已拒绝');
  81. } catch (err) {
  82. console.error('拒绝图像失败:', err);
  83. message.error('拒绝图像失败');
  84. }
  85. },
  86. });
  87. };
  88. // 恢复按钮处理函数
  89. const handleRestore = async () => {
  90. if (!selectedImage) return;
  91. try {
  92. await dispatch(
  93. judgeImageThunk({
  94. sopInstanceUid: selectedImage.sop_instance_uid,
  95. accept: true,
  96. })
  97. ).unwrap();
  98. message.success('图像已恢复');
  99. } catch (err) {
  100. console.error('恢复图像失败:', err);
  101. message.error('恢复图像失败');
  102. }
  103. };
  104. // 另存为按钮处理函数
  105. const handleSaveAs = async () => {
  106. if (!selectedImage) return;
  107. Modal.confirm({
  108. title: '确认另存为',
  109. content: `确定要复制选中的图像吗?`,
  110. okText: '确认',
  111. cancelText: '取消',
  112. centered: true,
  113. onOk: async () => {
  114. try {
  115. await dispatch(
  116. saveImageAsThunk({
  117. sopInstanceUid: selectedImage.sop_instance_uid,
  118. studyId: selectedImage.study_id || '',
  119. })
  120. ).unwrap();
  121. message.success('图像另存为成功,体位列表已更新');
  122. } catch (err) {
  123. console.error('图像另存为失败:', err);
  124. message.error('图像另存为失败');
  125. }
  126. },
  127. });
  128. };
  129. // 另存为按钮显示条件:单分格 + 有选中图像 + 图像已曝光
  130. const showSaveAsButton =
  131. isSingleGrid &&
  132. selectedImage !== null &&
  133. selectedImage?.dview?.expose_status === 'Exposed';
  134. return (
  135. <Flex gap="small" align="center" justify="start">
  136. {showRejectButton && (
  137. <Button
  138. onClick={handleReject}
  139. loading={loading}
  140. icon={
  141. <Icon
  142. module="module-process"
  143. name="RejectImage"
  144. userId="base"
  145. theme="default"
  146. size="2x"
  147. state="normal"
  148. />
  149. }
  150. style={{
  151. width: '1.5rem',
  152. height: '1.5rem',
  153. padding: 0,
  154. }}
  155. disabled={ofReject}
  156. title="拒绝"
  157. />
  158. )}
  159. {showRestoreButton && (
  160. <Button
  161. onClick={handleRestore}
  162. loading={loading}
  163. icon={
  164. <Icon
  165. module="module-process"
  166. name="RestoreImage"
  167. userId="base"
  168. theme="default"
  169. size="2x"
  170. state="normal"
  171. />
  172. }
  173. style={{
  174. width: '1.5rem',
  175. height: '1.5rem',
  176. padding: 0,
  177. }}
  178. title="恢复"
  179. disabled={ofRecover}
  180. />
  181. )}
  182. {showSaveAsButton && (
  183. <Button
  184. onClick={handleSaveAs}
  185. loading={loading}
  186. icon={
  187. <Icon
  188. module="module-process"
  189. name="SaveAs"
  190. userId="base"
  191. theme="default"
  192. size="2x"
  193. state="normal"
  194. />
  195. }
  196. style={{
  197. width: '1.5rem',
  198. height: '1.5rem',
  199. padding: 0,
  200. }}
  201. title="另存为"
  202. disabled={ofSaveAs}
  203. />
  204. )}
  205. </Flex>
  206. );
  207. };
  208. export default ImageStateControl;