Просмотр исходного кода

feat: implement study lock/unlock functionality

- Add lockStudy API in src/API/patient/workActions.ts
- Implement lockWorkInWorklistThunk with handlers in src/states/patient/worklist/slices/workSlice.ts
- Add lock button handler in src/pages/patient/components/ActionPanel.tsx
- Support additionalThunks in createListSlices for lock functionality
- Update table column adapter configuration
sw 5 дней назад
Родитель
Сommit
650f13af0a

+ 20 - 0
src/API/patient/workActions.ts

@@ -514,3 +514,23 @@ const suspendOrCompleteStudy = async (studyId: string, studyStatus: 'InProgress'
 };
 
 export { suspendOrCompleteStudy };
+
+/**
+ * 锁定/解锁研究
+ * @param studyId 研究ID
+ * @param lock 锁定状态:'Locked' 或 'Unlocked'
+ */
+export const lockStudy = async (
+  studyId: string,
+  lock: 'Locked' | 'Unlocked'
+): Promise<{ code: string; description: string; solution: string }> => {
+  try {
+    const response = await axiosInstance.put(`/auth/study/${studyId}/lock`, {
+      lock,
+    });
+    return response.data;
+  } catch (error) {
+    console.error('Error locking/unlocking study:', error);
+    throw error;
+  }
+};

+ 1 - 1
src/config/tableColumns/adapters/LocalColumnConfigAdapter.ts

@@ -43,7 +43,7 @@ export class LocalColumnConfigAdapter implements IColumnConfigProvider {
               visible: false,
               order: 111,
             },
-            { key: 'StudyLock', visible: false, order: 112 },
+            { key: 'StudyLock', visible: true, order: 112 },
             { key: 'OperatorID', visible: false, order: 113 },
             { key: 'Views', visible: false, order: 114 },
             { key: 'Thickness', visible: false, order: 115 },

+ 48 - 1
src/pages/patient/components/ActionPanel.tsx

@@ -1,7 +1,10 @@
 import React from 'react';
 import { Button, Tooltip, Modal, message } from 'antd';
 import { useDispatch, useSelector } from 'react-redux';
-import { deleteWorkThunk } from '@/states/patient/worklist/slices/workSlice';
+import {
+  deleteWorkThunk,
+  lockWorkInWorklistThunk,
+} from '@/states/patient/worklist/slices/workSlice';
 import { switchToSendPanel } from '@/states/patient/worklist/slices/historyPanelSwitchSlice';
 import { FormattedMessage } from 'react-intl';
 import { AppDispatch, RootState, useAppSelector } from '@/states/store';
@@ -38,6 +41,15 @@ const ActionPanel: React.FC = () => {
     (state: RootState) => state.diagnosticReport.visible
   );
   const themeType = useAppSelector((state) => state.theme.themeType);
+  const currentKey = useSelector(
+    (state: RootState) => state.BusinessFlow.currentKey
+  );
+  const workEntities = useSelector(
+    (state: RootState) => state.workEntities.data
+  );
+  const workEntitiesFromHistory = useSelector(
+    (state: RootState) => state.historyEntities.data
+  );
   // 使用 worklist 或 history 的选中项,取决于哪个有值
   const selectedIds =
     workSelectedIds.length > 0 ? workSelectedIds : historySelectedIds;
@@ -68,6 +80,40 @@ const ActionPanel: React.FC = () => {
   const handleShowReport = () => {
     dispatch(setVisible(true));
   };
+  const getSelectedWorkIds = () => {
+    const selectedIds =
+      currentKey === 'worklist' ? workSelectedIds : historySelectedIds;
+    return selectedIds;
+  };
+
+  const getWorksFromWorklistOrHistory = () => {
+    return currentKey === 'worklist' ? workEntities : workEntitiesFromHistory;
+  };
+  const handleLock = () => {
+    const selectedIds = getSelectedWorkIds();
+
+    // 2. 检查是否有选中项
+    if (selectedIds.length === 0) {
+      message.warning('请先选择要锁定/解锁的项目');
+      return;
+    }
+    // 3. 获取第一个选中项的锁定状态
+    const works = getWorksFromWorklistOrHistory();
+    const selectedItem = works.find((item) => item.StudyID === selectedIds[0]);
+    if (!selectedItem) return;
+
+    // 4. 根据当前状态切换
+    const newLockState =
+      selectedItem.StudyLock === 'Locked' ? 'Unlocked' : 'Locked';
+
+    // 为每个选中项执行锁定/解锁操作
+    selectedIds.forEach((studyId) => {
+      console.log(
+        `锁定,触发action ,目标 studyid是 ${studyId},新状态是 ${newLockState}`
+      );
+      dispatch(lockWorkInWorklistThunk({ studyId, lock: newLockState }));
+    });
+  };
 
   return (
     <div className="flex flex-wrap gap-2 w-full">
@@ -125,6 +171,7 @@ const ActionPanel: React.FC = () => {
             defaultMessage="actionPanel.lockTask"
           />
         }
+        onClick={handleLock}
       />
       <ActionButton
         icon={

+ 25 - 1
src/states/list_template/createListSlices.ts

@@ -1,3 +1,4 @@
+/* eslint-disable */
 import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit';
 import {
   EntitiesState,
@@ -13,7 +14,17 @@ export function createEntityListSlices<T, F extends object>(
   deleteThunk,
   idName: string,
   extraReducersForFilter?: (builder) => void,
-  initialFilter?: F
+  initialFilter?: F,
+  additionalThunks?: {
+    [key: string]: {
+      thunk: any;
+      handlers?: {
+        pending?: (state: Draft<EntitiesState<T>>, action: any) => void;
+        fulfilled?: (state: Draft<EntitiesState<T>>, action: any) => void;
+        rejected?: (state: Draft<EntitiesState<T>>, action: any) => void;
+      };
+    };
+  }
 ) {
   const entitiesSlice = createSlice({
     name: `${namespace}/entities`,
@@ -41,6 +52,19 @@ export function createEntityListSlices<T, F extends object>(
           );
         }
       );
+      if (additionalThunks) {
+        Object.entries(additionalThunks).forEach(([key, { thunk, handlers }]) => {
+          if (handlers?.pending) {
+            builder.addCase(thunk.pending, handlers.pending);
+          }
+          if (handlers?.fulfilled) {
+            builder.addCase(thunk.fulfilled, handlers.fulfilled);
+          }
+          if (handlers?.rejected) {
+            builder.addCase(thunk.rejected, handlers.rejected);
+          }
+        });
+      }
     },
   });
 

+ 36 - 2
src/states/patient/worklist/slices/workSlice.ts

@@ -1,3 +1,4 @@
+/* eslint-disable */
 import { createEntityListSlices } from '../../../list_template/createListSlices';
 import {
   createFetchThunk,
@@ -5,7 +6,7 @@ import {
 } from '../../../list_template/thunk.factory';
 import { work, workAnimal } from '../types/worklist';
 import { WorkFilter } from '../types/workfilter';
-import { PayloadAction } from '@reduxjs/toolkit';
+import { createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
 import {
   setId,
   setName,
@@ -21,8 +22,11 @@ import {
 import {
   deleteStudies,
   fetchTaskList,
+  lockStudy,
 } from '../../../../API/patient/workActions';
 import store from '@/states/store';
+import { Draft } from '@reduxjs/toolkit';
+import { EntitiesState } from '../../../list_template/type.model';
 // 可以被historylist查询共用,filter由外部传递 todo 提取
 export const fetchWorkThunk = createFetchThunk<WorkFilter, work | workAnimal>(
   'worklist',
@@ -92,6 +96,30 @@ const extraReducersForFilter = (builder) => {
   );
 };
 
+// 锁定/解锁研究的 thunk
+export const lockWorkInWorklistThunk = createAsyncThunk(
+  'worklist/lock',
+  async ({ studyId, lock }: { studyId: string; lock: 'Locked' | 'Unlocked' }) => {
+    console.log(`锁定,从thunk调用api,目标 studyid是 ${studyId},新状态是 ${lock}`);
+    const result = await lockStudy(studyId, lock);
+    return { studyId, lock, result };
+  }
+);
+
+// 创建锁定操作的 handlers
+const createLockHandlers = () => ({
+  fulfilled: (
+    state: Draft<EntitiesState<work | workAnimal>>,
+    action: PayloadAction<{ studyId: string; lock: 'Locked' | 'Unlocked'; result: any }>
+  ) => {
+    const { studyId, lock } = action.payload;
+    console.log(`锁定,thunk fulfilled,目标 studyid是 ${studyId},新状态是 ${lock}`);
+    const item = state.data.find((item) => item.StudyID === studyId);
+    if (item) {
+      item.StudyLock = lock;
+    }
+  },
+});
 // // 添加分页状态同步逻辑
 // const addPaginationSyncReducers = (filtersSlice, paginationSlice) => {
 //   filtersSlice.caseReducers = {
@@ -124,7 +152,13 @@ const {
     status: 'Arrived,InProgress',
     page: 1,
     page_size: 10,
-  } satisfies WorkFilter
+  } satisfies WorkFilter,
+  {
+    lock: {
+      thunk: lockWorkInWorklistThunk,
+      handlers: createLockHandlers(),
+    },
+  }
 );
 
 export const workEntitiesSlice = entitiesSlice;