bodyPositionSelection.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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. try {
  52. await changeBodyPosition(bodyPosition.sop_instance_uid);
  53. } catch (error) {
  54. message.error('同步新体位到服务时失败了',error);
  55. }
  56. // 切换体位成功后,使能发生器曝光
  57. try {
  58. await setExpEnable();
  59. } catch (error) {
  60. console.error(
  61. `[bodyPositionSelection] 使能曝光失败,目标体位是 ${bodyPosition.view_name}:`,
  62. error
  63. );
  64. message.error('使能曝光失败,请检查设备连接或重试');
  65. }
  66. const successMsg = `Body position changed successfully: ${bodyPosition.view_name}`;
  67. console.log(`[bodyPositionSelection] ${successMsg}`);
  68. if (showMessage) {
  69. message.success(successMsg);
  70. }
  71. } else {
  72. // 已曝光体位:跳过设备同步,仅更新显示
  73. console.log(
  74. `[bodyPositionSelection] Exposed position selected, skipping device sync: ${bodyPosition.view_name}`
  75. );
  76. if (showMessage) {
  77. message.info(`已选中已曝光体位: ${bodyPosition.view_name}`);
  78. }
  79. }
  80. } else {
  81. console.log(
  82. `[bodyPositionSelection] Current key is ${currentKey}, not executing changeBodyPosition.`
  83. );
  84. }
  85. } catch (error) {
  86. const errorMsg = 'Failed to change body position';
  87. console.error(`[bodyPositionSelection] ${errorMsg}:`, error);
  88. if (showMessage) {
  89. message.error(errorMsg);
  90. }
  91. throw error; // 向上传播错误,让调用者决定如何处理
  92. }
  93. };
  94. /**
  95. * 自动选中第一个体位(通常在进入exam时调用)
  96. * - exam 模式:自动选择第一个未曝光的体位
  97. * - process 模式:自动选择第一个已曝光的体位
  98. * - 其他模式:选择第一个体位
  99. */
  100. export const autoSelectFirstBodyPosition = async (
  101. bodyPositions: ExtendedBodyPosition[],
  102. dispatch: AppDispatch,
  103. currentKey: string
  104. ): Promise<void> => {
  105. if (bodyPositions.length === 0) {
  106. console.log('[bodyPositionSelection] No body positions available');
  107. return;
  108. }
  109. // 根据当前业务流程选择合适的体位
  110. let targetBodyPosition: ExtendedBodyPosition | undefined;
  111. if (currentKey === 'exam') {
  112. // exam 模式:选择第一个未曝光的体位
  113. targetBodyPosition = bodyPositions.find(
  114. (bp) => bp.dview.expose_status === 'Unexposed'
  115. );
  116. console.log(
  117. '[bodyPositionSelection] Auto-selecting first unexposed body position in exam mode'
  118. );
  119. } else if (currentKey === 'process') {
  120. // process 模式:选择第一个已曝光的体位
  121. targetBodyPosition = bodyPositions.find(
  122. (bp) => bp.dview.expose_status === 'Exposed'
  123. );
  124. console.log(
  125. '[bodyPositionSelection] Auto-selecting first exposed body position in process mode'
  126. );
  127. } else {
  128. // 其他模式:默认选择第一个
  129. targetBodyPosition = bodyPositions[0];
  130. console.log(
  131. `[bodyPositionSelection] Auto-selecting first body position in ${currentKey} mode`
  132. );
  133. }
  134. if (targetBodyPosition) {
  135. await selectBodyPositionWithFullLogic(
  136. targetBodyPosition,
  137. dispatch,
  138. currentKey,
  139. false // 自动选中时不显示用户消息
  140. );
  141. } else {
  142. console.warn(
  143. `[bodyPositionSelection] No suitable body position found for ${currentKey} mode`
  144. );
  145. }
  146. };
  147. /**
  148. * 手动选中体位(用户点击时调用)
  149. */
  150. export const manualSelectBodyPosition = async (
  151. bodyPosition: ExtendedBodyPosition,
  152. dispatch: AppDispatch,
  153. currentKey: string
  154. ): Promise<void> => {
  155. console.log(
  156. `[bodyPositionSelection] Manual selection: ${bodyPosition.view_name}`
  157. );
  158. await selectBodyPositionWithFullLogic(
  159. bodyPosition,
  160. dispatch,
  161. currentKey,
  162. true // 手动选中时显示用户消息
  163. );
  164. };
  165. /**
  166. * 带流程切换的体位选择(用户点击体位时调用,根据曝光状态自动切换流程)
  167. * @param bodyPosition 要选中的体位
  168. * @param dispatch Redux dispatch
  169. * @param currentKey 当前业务流程 key
  170. * @param allowFlowSwitch 是否允许自动切换流程(默认 true)
  171. */
  172. export const manualSelectBodyPositionWithFlowSwitch = async (
  173. bodyPosition: ExtendedBodyPosition,
  174. dispatch: AppDispatch,
  175. currentKey: string,
  176. allowFlowSwitch: boolean = true
  177. ): Promise<void> => {
  178. const isExposed = bodyPosition.dview.expose_status === 'Exposed';
  179. const isUnexposed = bodyPosition.dview.expose_status === 'Unexposed';
  180. let targetFlow = currentKey;
  181. let needSwitch = false;
  182. // 🆕 只有在允许流程切换时才判断是否需要切换
  183. if (allowFlowSwitch) {
  184. // 判断是否需要切换流程
  185. if (currentKey === 'exam' && isExposed) {
  186. targetFlow = 'process';
  187. needSwitch = true;
  188. console.log(
  189. `[bodyPositionSelection] Detected exposed position in exam mode, will switch to process`
  190. );
  191. } else if (currentKey === 'process' && isUnexposed) {
  192. targetFlow = 'exam';
  193. needSwitch = true;
  194. console.log(
  195. `[bodyPositionSelection] Detected unexposed position in process mode, will switch to exam`
  196. );
  197. }
  198. } else {
  199. console.log(
  200. `[bodyPositionSelection] Flow switch disabled, staying in ${currentKey} mode`
  201. );
  202. }
  203. if (needSwitch) {
  204. console.log(
  205. `[bodyPositionSelection] Switching flow from ${currentKey} to ${targetFlow}`
  206. );
  207. // 创建一个 Promise 来等待流程切换完成
  208. const flowSwitchPromise = new Promise<void>((resolve) => {
  209. // 设置一个一次性监听器
  210. const handler = (data: {
  211. from: string;
  212. to: string;
  213. timestamp: number;
  214. }) => {
  215. if (data.to === targetFlow) {
  216. console.log(
  217. `[bodyPositionSelection] Flow switch completed: ${data.from} -> ${data.to}`
  218. );
  219. // 移除监听器
  220. emitter.off('BUSINESS_FLOW_CHANGED', handler);
  221. resolve();
  222. }
  223. };
  224. emitter.on('BUSINESS_FLOW_CHANGED', handler);
  225. // 设置超时保护,防止事件永远不触发
  226. setTimeout(() => {
  227. emitter.off('BUSINESS_FLOW_CHANGED', handler);
  228. console.warn(
  229. '[bodyPositionSelection] Flow switch timeout, proceeding anyway'
  230. );
  231. resolve();
  232. }, 2000); // 2秒超时
  233. });
  234. // 触发流程切换
  235. dispatch(setBusinessFlow(targetFlow));
  236. // 等待流程切换完成
  237. await flowSwitchPromise;
  238. }
  239. // 使用正确的流程 key 选中体位
  240. await selectBodyPositionWithFullLogic(
  241. bodyPosition,
  242. dispatch,
  243. targetFlow,
  244. true
  245. );
  246. };