import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { View, fetchViews } from '@/API/patient/viewActions'; import { appendBodyPositionToStudy, AppendViewRequest, } from '@/API/exam/appendBodyPosition'; import { ExtendedBodyPosition, addBodyPosition } from './bodyPositionListSlice'; import { fetchViewDetail } from '@/API/patient/viewActions'; import { dview } from '@/domain/dview'; import { Work } from './examWorksCacheSlice'; import { addSelectedView, removeSelectedView, clearSelectedViews as clearViewSelectionViews, type ExtendedView, } from '@/states/patient/viewSelection'; import { v4 as uuidv4 } from 'uuid'; interface AppendViewState { availableViews: View[]; selectedViews: ExtendedView[]; isModalOpen: boolean; loading: boolean; error: string | null; currentSelectionType: 'protocol' | 'view'; currentBodyPartId: string | null; } const initialState: AppendViewState = { availableViews: [], selectedViews: [], isModalOpen: false, loading: false, error: null, currentSelectionType: 'view', currentBodyPartId: null, }; /** * 获取可用体位列表 */ export const fetchAvailableViewsThunk = createAsyncThunk( 'appendView/fetchAvailableViews', async ( params: { patient_type: string; body_part_id: string; }, { rejectWithValue } ) => { try { const response = await fetchViews( params.patient_type, params.body_part_id, true, null ); return response.data.views; } catch (error) { return rejectWithValue( error.message || 'Failed to fetch available views' ); } } ); /** * 追加体位到Study并更新bodyPositionList */ export const appendViewsThunk = createAsyncThunk( 'appendView/appendViews', async ( params: { study_id: string; views: View[]; currentWork: Work; }, { dispatch, rejectWithValue } ) => { try { // 1. 构建请求数据 const request: AppendViewRequest = { study_id: params.study_id, views: params.views.map((view) => ({ view_id: view.view_id, procedure_id: view.procedure_id || '', })), }; // 2. 调用API追加体位 const response = await appendBodyPositionToStudy(request); // 3. 将返回的 Series.images 转换为 ExtendedBodyPosition const newBodyPositions: ExtendedBodyPosition[] = []; for (const series of response.data.series) { for (const image of series.images) { // 获取完整的 view 详情 const viewDetail = await fetchViewDetail(image.view_id); const dviewData: dview = { view_id: image.view_id, series_instance_uid: image.series_instance_uid, study_instance_uid: image.study_instance_uid, study_id: image.study_id, procedure_id: series.procedure_id, view_description: image.view_description, view_type: image.view_id, PrimarySopUID: image.sop_instance_uid, thumbnail_file: image.image_file_path, image_file: image.image_file_path, image_file_path: image.image_file_path, expose_status: 'Unexposed', judged_status:image.judged_status, }; const extendedBodyPosition: ExtendedBodyPosition = { ...viewDetail, collimator_length: viewDetail.config_object.DX?.CollimatorSizeLength || viewDetail.config_object?.Common?.CollimatorSizeLength || 0, collimator_width: viewDetail.config_object.DX?.CollimatorSizeWidth || viewDetail.config_object?.Common?.CollimatorSizeWidth || 0, sid: '', patient_name: params.currentWork.PatientName, patient_id: params.currentWork.PatientID, registration_number: params.currentWork.AccessionNumber, study_description: params.currentWork.StudyDescription, body_position_image: viewDetail.view_icon_name, work: params.currentWork, study_instance_uid: params.currentWork.StudyInstanceUID, sop_instance_uid: image.sop_instance_uid, series_instance_uid: image.series_instance_uid, secondary_sop_uid: image.secondary_sop_uid, study_id: image.study_id, dview: dviewData, }; newBodyPositions.push(extendedBodyPosition); } } // 4. 将新体位添加到 bodyPositionList for (const bodyPosition of newBodyPositions) { dispatch(addBodyPosition(bodyPosition)); } return newBodyPositions; } catch (error) { return rejectWithValue( error.message || 'Failed to append body positions' ); } } ); const appendViewSlice = createSlice({ name: 'appendView', initialState, reducers: { setModalOpen: (state, action: PayloadAction) => { state.isModalOpen = action.payload; if (!action.payload) { // 关闭模态框时清空选中 state.selectedViews = []; } }, toggleViewSelection: (state, action: PayloadAction) => { const index = state.selectedViews.findIndex( (v) => v.view_id === action.payload.view_id ); if (index >= 0) { // 已选中,取消选中 state.selectedViews.splice(index, 1); } else { // 未选中,添加到选中列表,需要添加 guid const extendedView: ExtendedView = { ...action.payload, guid: uuidv4(), }; state.selectedViews.push(extendedView); } }, clearSelectedViews: (state) => { state.selectedViews = []; }, clearError: (state) => { state.error = null; }, setSelectionType: (state, action: PayloadAction<'protocol' | 'view'>) => { state.currentSelectionType = action.payload; }, setCurrentBodyPart: (state, action: PayloadAction) => { state.currentBodyPartId = action.payload; }, }, extraReducers: (builder) => { builder // fetchAvailableViewsThunk .addCase(fetchAvailableViewsThunk.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchAvailableViewsThunk.fulfilled, (state, action) => { state.loading = false; state.availableViews = action.payload; }) .addCase(fetchAvailableViewsThunk.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // appendViewsThunk .addCase(appendViewsThunk.pending, (state) => { state.loading = true; state.error = null; }) .addCase(appendViewsThunk.fulfilled, (state) => { state.loading = false; state.isModalOpen = false; state.selectedViews = []; }) .addCase(appendViewsThunk.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // 监听 viewSelection 的 actions,实现状态同步 .addCase(addSelectedView, (state, action: PayloadAction) => { // addSelectedView 接收的是 View 类型,viewSelection slice 会内部添加 guid // 这里也需要添加 guid const view = action.payload; // 检查是否已存在(通过 view_id 判断) const exists = state.selectedViews.some( (v) => v.view_id === view.view_id ); if (!exists) { const extendedView: ExtendedView = { ...view, guid: uuidv4(), }; state.selectedViews.push(extendedView); } }) .addCase(removeSelectedView, (state, action: PayloadAction) => { // action.payload 是 guid,通过 guid 删除 state.selectedViews = state.selectedViews.filter( (view) => view.guid !== action.payload ); }) .addCase(clearViewSelectionViews, (state) => { // 清空已选择的体位 state.selectedViews = []; }); }, }); export const { setModalOpen, toggleViewSelection, clearSelectedViews, clearError, setSelectionType, setCurrentBodyPart, } = appendViewSlice.actions; export default appendViewSlice.reducer;