ImageStateControl.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 judgedStatus = selectedImage?.dview?.judged_status || '';
  53. const showRejectButton = judgedStatus !== 'Reject';
  54. const showRestoreButton = judgedStatus === 'Reject';
  55. // 拒绝按钮处理函数
  56. const handleReject = async () => {
  57. if (!selectedImage) return;
  58. Modal.confirm({
  59. title: '确认拒绝',
  60. content: `确定要拒绝选中的图像吗?`,
  61. okText: '确认拒绝',
  62. cancelText: '取消',
  63. okButtonProps: {
  64. danger: true,
  65. 'data-testid': 'modal-confirm-delete'
  66. },
  67. cancelButtonProps: {
  68. 'data-testid': 'modal-cancel-delete'
  69. },
  70. centered: true,
  71. onOk: async () => {
  72. try {
  73. await dispatch(
  74. judgeImageThunk({
  75. sopInstanceUid: selectedImage.sop_instance_uid,
  76. accept: false,
  77. })
  78. ).unwrap();
  79. message.success('图像已拒绝');
  80. } catch (err) {
  81. console.error('拒绝图像失败:', err);
  82. message.error('拒绝图像失败');
  83. }
  84. },
  85. });
  86. };
  87. // 恢复按钮处理函数
  88. const handleRestore = async () => {
  89. if (!selectedImage) return;
  90. try {
  91. await dispatch(
  92. judgeImageThunk({
  93. sopInstanceUid: selectedImage.sop_instance_uid,
  94. accept: true,
  95. })
  96. ).unwrap();
  97. message.success('图像已恢复');
  98. } catch (err) {
  99. console.error('恢复图像失败:', err);
  100. message.error('恢复图像失败');
  101. }
  102. };
  103. // 另存为按钮处理函数
  104. const handleSaveAs = async () => {
  105. if (!selectedImage) return;
  106. Modal.confirm({
  107. title: '确认另存为',
  108. content: `确定要复制选中的图像吗?`,
  109. okText: '确认',
  110. cancelText: '取消',
  111. centered: true,
  112. onOk: async () => {
  113. try {
  114. await dispatch(
  115. saveImageAsThunk({
  116. sopInstanceUid: selectedImage.sop_instance_uid,
  117. studyId: selectedImage.study_id || '',
  118. })
  119. ).unwrap();
  120. message.success('图像另存为成功,体位列表已更新');
  121. } catch (err) {
  122. console.error('图像另存为失败:', err);
  123. message.error('图像另存为失败');
  124. }
  125. },
  126. });
  127. };
  128. // 另存为按钮显示条件:单分格 + 有选中图像 + 图像已曝光
  129. const showSaveAsButton =
  130. selectedImage !== null &&
  131. selectedImage?.dview?.expose_status === 'Exposed';
  132. return (
  133. <Flex gap="small" align="center" justify="start">
  134. {showRejectButton && (
  135. <Button
  136. onClick={handleReject}
  137. loading={loading}
  138. icon={
  139. <Icon
  140. module="module-process"
  141. name="RejectImage"
  142. userId="base"
  143. theme="default"
  144. size="2x"
  145. state="normal"
  146. />
  147. }
  148. style={{
  149. width: '1.5rem',
  150. height: '1.5rem',
  151. padding: 0,
  152. }}
  153. disabled={ofReject}
  154. title="拒绝"
  155. />
  156. )}
  157. {showRestoreButton && (
  158. <Button
  159. onClick={handleRestore}
  160. loading={loading}
  161. icon={
  162. <Icon
  163. module="module-process"
  164. name="RestoreImage"
  165. userId="base"
  166. theme="default"
  167. size="2x"
  168. state="normal"
  169. />
  170. }
  171. style={{
  172. width: '1.5rem',
  173. height: '1.5rem',
  174. padding: 0,
  175. }}
  176. title="恢复"
  177. disabled={ofRecover}
  178. />
  179. )}
  180. {showSaveAsButton && (
  181. <Button
  182. onClick={handleSaveAs}
  183. loading={loading}
  184. icon={
  185. <Icon
  186. module="module-process"
  187. name="SaveAs"
  188. userId="base"
  189. theme="default"
  190. size="2x"
  191. state="normal"
  192. />
  193. }
  194. style={{
  195. width: '1.5rem',
  196. height: '1.5rem',
  197. padding: 0,
  198. }}
  199. title="另存为"
  200. disabled={ofSaveAs}
  201. />
  202. )}
  203. </Flex>
  204. );
  205. };
  206. export default ImageStateControl;