worklist.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import React, { useState, useEffect } from 'react';
  2. import { Row, Col, Button, Drawer, Grid } from 'antd';
  3. import { SettingOutlined } from '@ant-design/icons';
  4. import { FormattedMessage } from 'react-intl';
  5. import { useSelector, useDispatch } from 'react-redux';
  6. import { useRisAutoSync } from '@/hooks/useRisAutoSync';
  7. import {
  8. fetchWorkThunk,
  9. workSelectionSlice,
  10. workPaginationSlice,
  11. workFiltersSlice,
  12. } from '../../states/patient/worklist/slices/workSlice';
  13. import { updateThumbnailsFromWorkSelection } from '../../states/patient/worklist/slices/thumbnailListSlice';
  14. import WorklistTable from './components/WorklistTable';
  15. import OperationPanel from './components/OperationPanel';
  16. import ThumbnailList from './components/ThumbnailList';
  17. import GenericPagination from '../../components/GenericPagination';
  18. import PatientPortraitFloat from './components/PatientPortraitFloat';
  19. import { RootState, AppDispatch } from '../../states/store';
  20. import { Task } from '@/domain/work';
  21. import worklistToExam from '../../domain/patient/worklistToExam';
  22. import { columnConfigService } from '@/config/tableColumns';
  23. import { ColumnConfig } from '@/config/tableColumns/types/columnConfig';
  24. import { useMultiSelection } from '@/hooks/useMultiSelection';
  25. const { useBreakpoint } = Grid;
  26. const WorklistPage: React.FC = () => {
  27. const screens = useBreakpoint();
  28. const [drawerVisible, setDrawerVisible] = useState(false);
  29. const [columnConfig, setColumnConfig] = useState<ColumnConfig[]>([]); // 新增:列配置状态
  30. const [selectedPatientForPortrait, setSelectedPatientForPortrait] = useState<Task | null>(null); // 照片显示用的选中患者
  31. // 启用RIS自动同步(在后台静默运行)
  32. useRisAutoSync();
  33. const dispatch: AppDispatch = useDispatch();
  34. const filters = useSelector((state: RootState) => state.workFilters);
  35. const page = useSelector((state: RootState) => state.workPagination.page);
  36. const pageSize = useSelector(
  37. (state: RootState) => state.workPagination.pageSize
  38. );
  39. const selectedIds = useSelector(
  40. (state: RootState) => state.workSelection.selectedIds
  41. );
  42. const selectedSecondaryIds = useSelector( // 获取次要选中ID列表
  43. (state: RootState) => state.workSelection.selectedSecondaryIds
  44. );
  45. const worklistData = useSelector(
  46. (state: RootState) => state.workEntities.data
  47. );
  48. // 新增:获取列配置
  49. useEffect(() => {
  50. columnConfigService
  51. .getColumnConfig('worklist')
  52. .then((config) => {
  53. setColumnConfig(config.columns);
  54. })
  55. .catch((error) => {
  56. console.error('Failed to load worklist column config:', error);
  57. // 失败时使用空配置,表格会显示所有列
  58. setColumnConfig([]);
  59. });
  60. }, []);
  61. // 同步分页状态到过滤器
  62. useEffect(() => {
  63. console.log('[worklist] Syncing pagination to filters:', {
  64. page,
  65. pageSize,
  66. });
  67. dispatch(
  68. workFiltersSlice.actions.setFilters({
  69. page,
  70. page_size: pageSize,
  71. })
  72. );
  73. }, [dispatch, page, pageSize]);
  74. useEffect(() => {
  75. console.log(
  76. '[worklist] Fetching worklist data with filters:',
  77. filters,
  78. 'page:',
  79. page,
  80. 'pageSize:',
  81. pageSize
  82. );
  83. dispatch(fetchWorkThunk({ page, pageSize, filters }));
  84. }, [dispatch, page, pageSize, filters]);
  85. // 使用多选 Hook
  86. const { handleRowClick } = useMultiSelection({
  87. selectedIds,
  88. selectedSecondaryIds,
  89. onSelectionChange: (newIds,secondNewIds) => {
  90. console.log('Selected IDs changed:', newIds,secondNewIds);
  91. // 如果只有一个选中项,设置选中患者用于显示照片
  92. if (newIds.length === 1) {
  93. const selectedRecord = worklistData.find(item => item.StudyID === newIds[0]&&item.entry_id===secondNewIds?.[0]);
  94. if (selectedRecord) {
  95. setSelectedPatientForPortrait(selectedRecord);
  96. }
  97. } else {
  98. // 多选时清空患者照片
  99. setSelectedPatientForPortrait(null);
  100. }
  101. // 更新 Redux 状态(用于其他功能)
  102. dispatch(workSelectionSlice.actions.setSelectedIds(newIds));
  103. dispatch(workSelectionSlice.actions.setSelectedSecondaryIds(secondNewIds??[]));
  104. dispatch(updateThumbnailsFromWorkSelection(newIds));
  105. },
  106. enableMultiSelect: true,
  107. });
  108. // 处理行点击事件(兼容现有功能)
  109. const handleRowClickInternal = (record: Task, event?: React.MouseEvent) => {
  110. handleRowClick(record, event || {} as React.MouseEvent);
  111. };
  112. const handleRowDoubleClick = (record: Task) => {
  113. console.log(
  114. '[WorklistTable] Row double-clicked:',
  115. JSON.stringify(record, null, 2)
  116. );
  117. worklistToExam(record);
  118. };
  119. return (
  120. <div className="h-full">
  121. {/* 患者照片浮动组件 */}
  122. <PatientPortraitFloat
  123. patient={selectedPatientForPortrait}
  124. onClose={() => setSelectedPatientForPortrait(null)}
  125. />
  126. {screens.xs ? (
  127. <>
  128. <div className="flex-1 overflow-auto">
  129. <WorklistTable
  130. columnConfig={columnConfig}
  131. worklistData={worklistData}
  132. filters={filters}
  133. page={page}
  134. pageSize={pageSize}
  135. selectedIds={selectedIds}
  136. selectedSecondaryIds={selectedSecondaryIds} // 新增
  137. handleRowClick={handleRowClickInternal}
  138. handleRowDoubleClick={handleRowDoubleClick}
  139. />
  140. </div>
  141. <GenericPagination
  142. paginationSelector={(state) => state.workPagination}
  143. entitiesSelector={(state) => state.workEntities}
  144. paginationActions={workPaginationSlice.actions}
  145. className="border-t"
  146. />
  147. <Button
  148. type="primary"
  149. shape="circle"
  150. icon={<SettingOutlined />}
  151. className="fixed bottom-6 right-6 z-50"
  152. onClick={() => setDrawerVisible(true)}
  153. />
  154. <Drawer
  155. title={
  156. <FormattedMessage
  157. id="worklist.operationPanel"
  158. defaultMessage="worklist.operationPanel"
  159. />
  160. }
  161. placement="left"
  162. onClose={() => setDrawerVisible(false)}
  163. open={drawerVisible}
  164. width={300}
  165. >
  166. <OperationPanel />
  167. </Drawer>
  168. </>
  169. ) : (
  170. <Row className="h-full">
  171. <Col
  172. span={screens.lg ? 18 : screens.md ? 20 : 24}
  173. className="h-full flex flex-col"
  174. >
  175. <div className="flex-1 flex flex-col">
  176. <div className="flex-1 overflow-auto">
  177. <WorklistTable
  178. columnConfig={columnConfig}
  179. worklistData={worklistData}
  180. filters={filters}
  181. page={page}
  182. pageSize={pageSize}
  183. selectedIds={selectedIds}
  184. selectedSecondaryIds={selectedSecondaryIds} // 新增
  185. handleRowClick={handleRowClickInternal}
  186. handleRowDoubleClick={handleRowDoubleClick}
  187. />
  188. </div>
  189. <GenericPagination
  190. paginationSelector={(state) => state.workPagination}
  191. entitiesSelector={(state) => state.workEntities}
  192. paginationActions={workPaginationSlice.actions}
  193. className="border-t"
  194. />
  195. </div>
  196. <div className="h-60 border-t border-gray-200">
  197. <ThumbnailList />
  198. </div>
  199. </Col>
  200. <Col
  201. span={screens.lg ? 6 : screens.md ? 4 : 0}
  202. className="h-full overflow-auto"
  203. >
  204. <OperationPanel />
  205. </Col>
  206. </Row>
  207. )}
  208. </div>
  209. );
  210. };
  211. export default WorklistPage;