bodyPositionSelection.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /**
  2. * 体位选择协调器 - 统一处理体位选中的完整逻辑
  3. * 确保自动选中和手动选中使用相同的业务流程
  4. */
  5. import { message } from 'antd';
  6. import { AppDispatch } from '@/states/store';
  7. import {
  8. setSelectedBodyPosition,
  9. ExtendedBodyPosition,
  10. } from '@/states/exam/bodyPositionListSlice';
  11. import { setBodyPositionDetail } from '@/states/exam/bodyPositionDetailSlice';
  12. import { changeBodyPosition } from '@/API/exam/changeBodyPosition';
  13. import { setExpEnable } from '@/API/exam/deviceActions';
  14. import { setBusinessFlow } from '@/states/BusinessFlowSlice';
  15. import emitter from '@/utils/eventEmitter';
  16. export const selectBodyPositionWithFullLogic = async (
  17. bodyPosition: ExtendedBodyPosition,
  18. dispatch: AppDispatch,
  19. currentKey: string,
  20. showMessage = false
  21. ): Promise<void> => {
  22. try {
  23. console.log(
  24. `[bodyPositionSelection] Selecting body position: ${bodyPosition.view_name}`
  25. );
  26. // 1. 更新选中状态
  27. dispatch(setSelectedBodyPosition(bodyPosition));
  28. // 2. 设置详情显示区域的数据
  29. dispatch(
  30. setBodyPositionDetail({
  31. view_name: bodyPosition.view_name,
  32. view_description: bodyPosition.view_description,
  33. view_icon_name: bodyPosition.view_icon_name,
  34. patient_name: bodyPosition.patient_name,
  35. patient_id: bodyPosition.patient_id,
  36. registration_number: bodyPosition.registration_number,
  37. study_description: bodyPosition.study_description,
  38. body_position_image: bodyPosition.view_big_icon_name,
  39. collimator_length: bodyPosition.collimator_length,
  40. collimator_width: bodyPosition.collimator_width,
  41. sid: bodyPosition.sid,
  42. // 🆕 添加新字段
  43. expose_status: bodyPosition.dview.expose_status,
  44. sop_instance_uid: bodyPosition.sop_instance_uid,
  45. })
  46. );
  47. // 3. 🆕 如果在exam模式,根据曝光状态决定是否同步设备
  48. if (currentKey === 'exam') {
  49. if (bodyPosition.dview.expose_status === 'Unexposed') {
  50. // 未曝光体位:同步设备,使能曝光
  51. await changeBodyPosition(bodyPosition.sop_instance_uid);
  52. // 切换体位成功后,使能发生器曝光
  53. await setExpEnable();
  54. const successMsg = `Body position changed successfully: ${bodyPosition.view_name}`;
  55. console.log(`[bodyPositionSelection] ${successMsg}`);
  56. if (showMessage) {
  57. message.success(successMsg);
  58. }
  59. } else {
  60. // 已曝光体位:跳过设备同步,仅更新显示
  61. console.log(
  62. `[bodyPositionSelection] Exposed position selected, skipping device sync: ${bodyPosition.view_name}`
  63. );
  64. if (showMessage) {
  65. message.info(`已选中已曝光体位: ${bodyPosition.view_name}`);
  66. }
  67. }
  68. } else {
  69. console.log(
  70. `[bodyPositionSelection] Current key is ${currentKey}, not executing changeBodyPosition.`
  71. );
  72. }
  73. } catch (error) {
  74. const errorMsg = 'Failed to change body position';
  75. console.error(`[bodyPositionSelection] ${errorMsg}:`, error);
  76. if (showMessage) {
  77. message.error(errorMsg);
  78. }
  79. throw error; // 向上传播错误,让调用者决定如何处理
  80. }
  81. };
  82. /**
  83. * 自动选中第一个体位(通常在进入exam时调用)
  84. * - exam 模式:自动选择第一个未曝光的体位
  85. * - process 模式:自动选择第一个已曝光的体位
  86. * - 其他模式:选择第一个体位
  87. */
  88. export const autoSelectFirstBodyPosition = async (
  89. bodyPositions: ExtendedBodyPosition[],
  90. dispatch: AppDispatch,
  91. currentKey: string
  92. ): Promise<void> => {
  93. if (bodyPositions.length === 0) {
  94. console.log('[bodyPositionSelection] No body positions available');
  95. return;
  96. }
  97. // 根据当前业务流程选择合适的体位
  98. let targetBodyPosition: ExtendedBodyPosition | undefined;
  99. if (currentKey === 'exam') {
  100. // exam 模式:选择第一个未曝光的体位
  101. targetBodyPosition = bodyPositions.find(
  102. (bp) => bp.dview.expose_status === 'Unexposed'
  103. );
  104. console.log(
  105. '[bodyPositionSelection] Auto-selecting first unexposed body position in exam mode'
  106. );
  107. } else if (currentKey === 'process') {
  108. // process 模式:选择第一个已曝光的体位
  109. targetBodyPosition = bodyPositions.find(
  110. (bp) => bp.dview.expose_status === 'Exposed'
  111. );
  112. console.log(
  113. '[bodyPositionSelection] Auto-selecting first exposed body position in process mode'
  114. );
  115. } else {
  116. // 其他模式:默认选择第一个
  117. targetBodyPosition = bodyPositions[0];
  118. console.log(
  119. `[bodyPositionSelection] Auto-selecting first body position in ${currentKey} mode`
  120. );
  121. }
  122. if (targetBodyPosition) {
  123. await selectBodyPositionWithFullLogic(
  124. targetBodyPosition,
  125. dispatch,
  126. currentKey,
  127. false // 自动选中时不显示用户消息
  128. );
  129. } else {
  130. console.warn(
  131. `[bodyPositionSelection] No suitable body position found for ${currentKey} mode`
  132. );
  133. }
  134. };
  135. /**
  136. * 手动选中体位(用户点击时调用)
  137. */
  138. export const manualSelectBodyPosition = async (
  139. bodyPosition: ExtendedBodyPosition,
  140. dispatch: AppDispatch,
  141. currentKey: string
  142. ): Promise<void> => {
  143. console.log(
  144. `[bodyPositionSelection] Manual selection: ${bodyPosition.view_name}`
  145. );
  146. await selectBodyPositionWithFullLogic(
  147. bodyPosition,
  148. dispatch,
  149. currentKey,
  150. true // 手动选中时显示用户消息
  151. );
  152. };
  153. /**
  154. * 带流程切换的体位选择(用户点击体位时调用,根据曝光状态自动切换流程)
  155. * @param bodyPosition 要选中的体位
  156. * @param dispatch Redux dispatch
  157. * @param currentKey 当前业务流程 key
  158. * @param allowFlowSwitch 是否允许自动切换流程(默认 true)
  159. */
  160. export const manualSelectBodyPositionWithFlowSwitch = async (
  161. bodyPosition: ExtendedBodyPosition,
  162. dispatch: AppDispatch,
  163. currentKey: string,
  164. allowFlowSwitch: boolean = true
  165. ): Promise<void> => {
  166. const isExposed = bodyPosition.dview.expose_status === 'Exposed';
  167. const isUnexposed = bodyPosition.dview.expose_status === 'Unexposed';
  168. let targetFlow = currentKey;
  169. let needSwitch = false;
  170. // 🆕 只有在允许流程切换时才判断是否需要切换
  171. if (allowFlowSwitch) {
  172. // 判断是否需要切换流程
  173. if (currentKey === 'exam' && isExposed) {
  174. targetFlow = 'process';
  175. needSwitch = true;
  176. console.log(
  177. `[bodyPositionSelection] Detected exposed position in exam mode, will switch to process`
  178. );
  179. } else if (currentKey === 'process' && isUnexposed) {
  180. targetFlow = 'exam';
  181. needSwitch = true;
  182. console.log(
  183. `[bodyPositionSelection] Detected unexposed position in process mode, will switch to exam`
  184. );
  185. }
  186. } else {
  187. console.log(
  188. `[bodyPositionSelection] Flow switch disabled, staying in ${currentKey} mode`
  189. );
  190. }
  191. if (needSwitch) {
  192. console.log(
  193. `[bodyPositionSelection] Switching flow from ${currentKey} to ${targetFlow}`
  194. );
  195. // 创建一个 Promise 来等待流程切换完成
  196. const flowSwitchPromise = new Promise<void>((resolve) => {
  197. // 设置一个一次性监听器
  198. const handler = (data: {
  199. from: string;
  200. to: string;
  201. timestamp: number;
  202. }) => {
  203. if (data.to === targetFlow) {
  204. console.log(
  205. `[bodyPositionSelection] Flow switch completed: ${data.from} -> ${data.to}`
  206. );
  207. // 移除监听器
  208. emitter.off('BUSINESS_FLOW_CHANGED', handler);
  209. resolve();
  210. }
  211. };
  212. emitter.on('BUSINESS_FLOW_CHANGED', handler);
  213. // 设置超时保护,防止事件永远不触发
  214. setTimeout(() => {
  215. emitter.off('BUSINESS_FLOW_CHANGED', handler);
  216. console.warn(
  217. '[bodyPositionSelection] Flow switch timeout, proceeding anyway'
  218. );
  219. resolve();
  220. }, 2000); // 2秒超时
  221. });
  222. // 触发流程切换
  223. dispatch(setBusinessFlow(targetFlow));
  224. // 等待流程切换完成
  225. await flowSwitchPromise;
  226. }
  227. // 使用正确的流程 key 选中体位
  228. await selectBodyPositionWithFullLogic(
  229. bodyPosition,
  230. dispatch,
  231. targetFlow,
  232. true
  233. );
  234. };