| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- 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<boolean>) => {
- state.isModalOpen = action.payload;
- if (!action.payload) {
- // 关闭模态框时清空选中
- state.selectedViews = [];
- }
- },
- toggleViewSelection: (state, action: PayloadAction<View>) => {
- 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<string | null>) => {
- 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<View>) => {
- // 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<string>) => {
- // 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;
|