ContentAreaLarge.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import {
  2. Row,
  3. Col,
  4. Select,
  5. InputNumber,
  6. Button,
  7. Switch,
  8. Divider,
  9. Tooltip,
  10. message,
  11. Flex,
  12. } from 'antd';
  13. import ErrorMessage from '@/components/ErrorMessage';
  14. import { patientSizes, PatientSize } from '../../states/patientSize';
  15. import { WorkstationTypeLabels } from '../../states/workstation';
  16. import { FormattedMessage } from 'react-intl';
  17. import { useSelector, useDispatch, useStore } from 'react-redux';
  18. import { deleteBodyPosition } from '../../API/patient/viewActions';
  19. import { copyPositionThunk } from '../../states/exam/examWorksCacheSlice';
  20. import {
  21. removeBodyPositionBySopInstanceUid,
  22. setByIndex,
  23. } from '../../states/exam/bodyPositionListSlice';
  24. import { RootState } from '../../states/store';
  25. import {
  26. setAprConfig,
  27. setBodysize,
  28. setWorkstation,
  29. setIsAECEnabled,
  30. setCurrentExposureMode,
  31. } from '../../states/exam/aprSlice';
  32. import BodyPositionList from './components/BodyPositionList';
  33. import BodyPositionDetail from './components/BodyPositionDetail';
  34. import { AppDispatch } from '@/states/store';
  35. import { useRef } from 'react';
  36. import Icon from '@/components/Icon';
  37. import ParaSettingCoordinator from '@/domain/exam/paraSettingCoordinator';
  38. import { resetDevices } from '@/states/device/deviceSlice';
  39. const ContentAreaLarge = () => {
  40. const dispatch = useDispatch<AppDispatch>();
  41. const isResetting = useSelector(
  42. (state: RootState) => state.device.status === 'loading'
  43. );
  44. const store = useStore<RootState>();
  45. const aprConfig = useSelector((state: RootState) => state.apr.aprConfig);
  46. const bodysize = useSelector((state: RootState) => state.apr.bodysize);
  47. const workstation = useSelector((state: RootState) => state.apr.workstation);
  48. const isAECEnabled = useSelector(
  49. (state: RootState) => state.apr.isAECEnabled
  50. );
  51. const currentExposureMode = useSelector(
  52. (state: RootState) => state.apr.currentExposureMode
  53. );
  54. const productName = useSelector(
  55. (state: RootState) => state.product.productName
  56. );
  57. const handleBodysizeChange = (key: string) => {
  58. const value = patientSizes[key as PatientSize]; // 获取对应的显示文本
  59. console.log('体型 key:', key); // 例如: 'small'
  60. console.log('体型 value:', value); // 例如: 'Small'
  61. dispatch(setBodysize(key));
  62. };
  63. const handleWorkstationChange = (value: string) => {
  64. dispatch(setWorkstation(value));
  65. };
  66. const handleAECChange = (checked: boolean) => {
  67. dispatch(setIsAECEnabled(checked));
  68. };
  69. const handleExposureModeChange = (value: string) => {
  70. dispatch(setCurrentExposureMode(value));
  71. };
  72. const handleResetParameters = async () => {
  73. try {
  74. await dispatch(resetDevices());
  75. } catch (error) {
  76. console.error('Error resetting devices:', error);
  77. }
  78. };
  79. // 1. 正常在顶层用 useSelector 订阅
  80. const selectedBodyPosition = useSelector(
  81. (state: RootState) => state.bodyPositionList.selectedBodyPosition
  82. );
  83. // 2. 用 ref 保存最新值(每次渲染都会更新)
  84. const positionRef = useRef(selectedBodyPosition);
  85. positionRef.current = selectedBodyPosition;
  86. return (
  87. <Row className="w-full p-1" style={{ height: '100%', display: 'flex' }}>
  88. <Col span={20} style={{ display: 'flex', flexDirection: 'column' }}>
  89. <Row gutter={16} style={{ flex: 1, minHeight: 0, display: 'flex' }}>
  90. <Col span={4} className="flex flex-col">
  91. <BodyPositionList layout="vertical"></BodyPositionList>
  92. </Col>
  93. <Col span={20}>
  94. <BodyPositionDetail />
  95. </Col>
  96. </Row>
  97. </Col>
  98. <Col
  99. span={4}
  100. style={{ height: '100%', overflowY: 'auto', flexShrink: 0 }}
  101. >
  102. <Row gutter={16} align="middle">
  103. <Col span={productName === 'DROS' ? 9 : 24}>
  104. <Select
  105. placeholder="选择体型"
  106. style={{ width: '100%', marginBottom: 8 }}
  107. value={bodysize}
  108. onChange={handleBodysizeChange}
  109. >
  110. {Object.entries(patientSizes).map(
  111. ([key, value]: [string, string]) => (
  112. <Select.Option key={key} value={key}>
  113. <FormattedMessage id={value} />
  114. </Select.Option>
  115. )
  116. )}
  117. </Select>
  118. </Col>
  119. {productName === 'DROS' && (
  120. <Col span={15}>
  121. <Select
  122. placeholder="选择工作位"
  123. style={{ width: '100%', marginBottom: 8 }}
  124. value={workstation}
  125. onChange={handleWorkstationChange}
  126. >
  127. {Object.entries(WorkstationTypeLabels).map(
  128. ([key, value]: [string, string]) => (
  129. <Select.Option key={key} value={value}>
  130. <FormattedMessage
  131. id={`workstation.${key.toLowerCase()}`}
  132. />
  133. </Select.Option>
  134. )
  135. )}
  136. </Select>
  137. </Col>
  138. )}
  139. </Row>
  140. <div>
  141. <div>
  142. <label
  143. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  144. >
  145. mA
  146. </label>
  147. <InputNumber
  148. placeholder="mA"
  149. style={{ width: '100%', marginBottom: 8 }}
  150. value={aprConfig.mA ?? undefined}
  151. onChange={(value) =>
  152. dispatch(setAprConfig({ ...aprConfig, mA: value ?? 0 }))
  153. }
  154. onStep={(value, info) => {
  155. if (info.type === 'up') {
  156. ParaSettingCoordinator.increaseMA();
  157. } else {
  158. ParaSettingCoordinator.decreaseMA();
  159. }
  160. }}
  161. />
  162. </div>
  163. <div>
  164. <label
  165. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  166. >
  167. ms
  168. </label>
  169. <InputNumber
  170. placeholder="ms"
  171. style={{ width: '100%', marginBottom: 8 }}
  172. value={aprConfig.ms ?? undefined}
  173. onChange={(value) =>
  174. dispatch(setAprConfig({ ...aprConfig, ms: value ?? 0 }))
  175. }
  176. onStep={(value, info) => {
  177. if (info.type === 'up') {
  178. ParaSettingCoordinator.increaseMS();
  179. } else {
  180. ParaSettingCoordinator.decreaseMS();
  181. }
  182. }}
  183. />
  184. </div>
  185. <div>
  186. <label
  187. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  188. >
  189. mAs
  190. </label>
  191. <InputNumber
  192. placeholder="mAs"
  193. style={{ width: '100%', marginBottom: 8 }}
  194. value={aprConfig.mAs ?? undefined}
  195. onChange={(value) =>
  196. dispatch(setAprConfig({ ...aprConfig, mAs: value ?? 0 }))
  197. }
  198. onStep={(value, info) => {
  199. if (info.type === 'up') {
  200. ParaSettingCoordinator.increaseMAS();
  201. } else {
  202. ParaSettingCoordinator.decreaseMAS();
  203. }
  204. }}
  205. />
  206. </div>
  207. <div>
  208. <label
  209. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  210. >
  211. KV
  212. </label>
  213. <InputNumber
  214. placeholder="KV"
  215. style={{ width: '100%', marginBottom: 8 }}
  216. value={aprConfig.kV ?? undefined}
  217. // onChange={(value) =>
  218. // dispatch(setAprConfig({ ...aprConfig, kV: value ?? 0 }))
  219. // }
  220. onStep={(value, info) => {
  221. if (info.type === 'up') {
  222. ParaSettingCoordinator.increaseKV();
  223. } else {
  224. ParaSettingCoordinator.decreaseKV();
  225. }
  226. }}
  227. />
  228. </div>
  229. <div>
  230. <label
  231. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  232. >
  233. density
  234. </label>
  235. <InputNumber
  236. placeholder="density"
  237. style={{ width: '100%', marginBottom: 8 }}
  238. value={aprConfig.AECDensity ?? undefined}
  239. onChange={(value) =>
  240. dispatch(setAprConfig({ ...aprConfig, AECDensity: value ?? 0 }))
  241. }
  242. onStep={(value, info) => {
  243. if (info.type === 'up') {
  244. ParaSettingCoordinator.increaseDensity();
  245. } else {
  246. ParaSettingCoordinator.decreaseDensity();
  247. }
  248. }}
  249. />
  250. </div>
  251. </div>
  252. <div>
  253. <label
  254. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  255. >
  256. 曝光模式
  257. </label>
  258. <Select
  259. placeholder="选择曝光模式"
  260. value={currentExposureMode}
  261. onChange={handleExposureModeChange}
  262. style={{ width: '100%', marginBottom: 8 }}
  263. >
  264. <Select.Option value="mAs">mAs</Select.Option>
  265. <Select.Option value="time">time</Select.Option>
  266. </Select>
  267. </div>
  268. <div>
  269. <label
  270. style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
  271. >
  272. AEC
  273. </label>
  274. <Switch
  275. checkedChildren="开启AEC"
  276. unCheckedChildren="关闭AEC"
  277. checked={isAECEnabled}
  278. onChange={handleAECChange}
  279. style={{ marginBottom: 8 }}
  280. />
  281. </div>
  282. <Flex align="center" justify="start" gap="middle" wrap>
  283. <Button
  284. style={{ width: '1.5rem', height: '1.5rem' }}
  285. icon={
  286. <Icon
  287. module="module-exam"
  288. name="btn_ResetGenerator"
  289. userId="base"
  290. theme="default"
  291. size="2x"
  292. state="normal"
  293. />
  294. }
  295. title="重置参数"
  296. onClick={handleResetParameters}
  297. disabled={isResetting}
  298. />
  299. </Flex>
  300. <Divider />
  301. <Flex wrap gap="small">
  302. <Tooltip title="删除选择的体位">
  303. <Button
  304. style={{ width: '1.5rem', height: '1.5rem' }}
  305. icon={
  306. <Icon
  307. module="module-exam"
  308. name="btn_DeleteView"
  309. userId="base"
  310. theme="default"
  311. size="2x"
  312. state="normal"
  313. />
  314. }
  315. onClick={async () => {
  316. const selectedBodyPosition =
  317. store.getState().bodyPositionList.selectedBodyPosition;
  318. console.log(
  319. `选中的体位:${JSON.stringify(selectedBodyPosition)}`
  320. );
  321. if (
  322. selectedBodyPosition &&
  323. selectedBodyPosition.sop_instance_uid
  324. ) {
  325. try {
  326. await deleteBodyPosition(
  327. selectedBodyPosition.sop_instance_uid
  328. );
  329. dispatch(
  330. removeBodyPositionBySopInstanceUid(
  331. selectedBodyPosition.sop_instance_uid
  332. )
  333. );
  334. dispatch(setByIndex(0));
  335. } catch (error) {
  336. console.error('Error deleting body position:', error);
  337. message.error('Failed to delete body position');
  338. }
  339. }
  340. }}
  341. />
  342. </Tooltip>
  343. <Tooltip title="复制选择的体位">
  344. <Button
  345. style={{ width: '1.5rem', height: '1.5rem' }}
  346. icon={
  347. <Icon
  348. module="module-exam"
  349. name="btn_Copy"
  350. userId="base"
  351. theme="default"
  352. size="2x"
  353. state="normal"
  354. />
  355. }
  356. onClick={() => {
  357. const instanceUid =
  358. store.getState().bodyPositionList.selectedBodyPosition
  359. ?.study_instance_uid ?? '';
  360. console.log('Copying position for instance UID:', instanceUid);
  361. console.log(
  362. `${store.getState().bodyPositionList.selectedBodyPosition}`
  363. );
  364. dispatch(copyPositionThunk({ instanceUid }));
  365. }}
  366. />
  367. </Tooltip>
  368. <Tooltip title="保存参数">
  369. <Button
  370. style={{ width: '1.5rem', height: '1.5rem' }}
  371. icon={
  372. <Icon
  373. module="module-exam"
  374. name="btn_Save"
  375. userId="base"
  376. theme="default"
  377. size="2x"
  378. state="normal"
  379. />
  380. }
  381. />
  382. </Tooltip>
  383. <Tooltip title="打开/关闭摄像头">
  384. <Button
  385. style={{ width: '1.5rem', height: '1.5rem' }}
  386. icon={
  387. <Icon
  388. module="module-exam"
  389. name="btn_OpenCamera"
  390. userId="base"
  391. theme="default"
  392. size="2x"
  393. state="normal"
  394. />
  395. }
  396. />
  397. </Tooltip>
  398. <Tooltip title="拒绝">
  399. <Button
  400. style={{ width: '1.5rem', height: '1.5rem' }}
  401. icon={
  402. <Icon
  403. module="module-exam"
  404. name="btn_RejectImage"
  405. userId="base"
  406. theme="default"
  407. size="2x"
  408. state="normal"
  409. />
  410. }
  411. />
  412. </Tooltip>
  413. </Flex>
  414. <ErrorMessage />
  415. </Col>
  416. </Row>
  417. );
  418. };
  419. export default ContentAreaLarge;