Browse Source

可以在待选择列表中正常获取并显示protocols与views

dengdx 2 months ago
parent
commit
82bc9ae3af

+ 5 - 4
src/API/patient/procedureActions.ts

@@ -1,6 +1,6 @@
 import axios from 'axios';
 
-interface Procedure {
+export interface Procedure {
   id: string;
   procedure_id: string;
   procedure_code: string;
@@ -32,13 +32,14 @@ interface ProcedureResponse {
 }
 
 export const fetchProcedures = async (
-  patientType: string,
-  bodyPart: string,
+  patientType: string | null,
+  bodyPart: string | null,
   isEnabled: boolean
 ): Promise<ProcedureResponse> => {
   const response = await axios.get('dr/api/v1/auth/protocol/procedure', {
     headers: {
-      Authorization: 'Bearer YOUR_TOKEN_HERE',
+      Authorization:
+        'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTEyNzc5NzAsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.ooTGwBXaNhtunbKbpqteWbjDwJLjnRmSIl80r5dp1pY',
       Language: 'en',
       Product: 'DROS',
       Source: 'Electron',

+ 144 - 0
src/API/patient/viewAction.md

@@ -0,0 +1,144 @@
+# 说明
+
+和后端交互view信息
+
+## 1. fetch views
+
+### HTTP Method: GET
+
+### Headers
+
+- **Authorization**
+- **Language**: `en` 或 `zh`
+- **Product**: `DROS` 或 `VETDROS`
+- **Source**: `Electron` 或 `Browser` 或 `Android`
+
+### Endpoint URL: /dr/api/v1/auth/protocol/view
+
+### query parameters
+
+patient_type:从患者类型接口获取,string or null
+body_part:从身体部位接口获取,string or null
+is_enabled:true 或 false
+procedure_id:从协议列表接口获取,string,maybe null
+
+### Response Status: 200 (OK)
+
+### Response Data: Returns a JSON
+
+example :
+
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "solution": "",
+  "data": {
+    "@type": "type.googleapis.com/dr.protocol.ViewReply",
+    "view_list": [
+      {
+        "internal_id": "View_DX_T_A_SK_AP_00",
+        "view_id": "View_DX_T_A_SK_AP_00",
+        "view_name": "颅骨前后位",
+        "view_name_local": "",
+        "view_other_name": "Skull AP",
+        "view_description": "颅骨前后位",
+        "view_position": "AP",
+        "application": "RAD",
+        "anatomic_region": "SKULL",
+        "patient_type": "Human",
+        "body_part_id": "Human_SKULL",
+        "view_icon_name": "/Image/Position/Human/skull.ap.table.x.png",
+        "view_big_icon_name": "/Image/Position/Human/skull.ap.table.x.png",
+        "view_coach_name": "/Image/Position/Human/skull.ap.table.x.png",
+        "modality": "DX",
+        "work_station_id": 0,
+        "apr_id": "View_DX_T_A_SK_AP_00",
+        "img_proc_id": "View_DX_T_A_SK_AP_00",
+        "config_object": {
+          "DX": {
+            "CollimatorCenter": "1",
+            "CollimatorFilter": "0",
+            "CollimatorNoChange": false,
+            "CollimatorSize": "14IN(35CM)X17IN(43CM)",
+            "CollimatorSizeLength": "17IN",
+            "CollimatorSizeWidth": "14IN",
+            "ImageHorizontalFlip": "NO",
+            "ImageLaterality": "U",
+            "ImageRotate": "0",
+            "LabelPosition": "LEFT TOP",
+            "LabelStyle": "",
+            "PatientOrientationColumn": "R",
+            "PatientOrientationRow": "L",
+            "RatioFactorLength": 0,
+            "RatioFactorSize": 0,
+            "RatioFactorThickness": 0,
+            "RatioFactorWeight": 0,
+            "StandPos": "43",
+            "TargetEXI": 250,
+            "ViewID": "View_DX_T_A_SK_AP_00"
+          }
+        },
+        "sort": 1,
+        "is_enabled": true,
+        "product": "DROS",
+        "is_pre_install": true
+      },
+      {
+        "internal_id": "View_DX_T_A_SK_LAT_00",
+        "view_id": "View_DX_T_A_SK_LAT_00",
+        "view_name": "颅骨左侧位",
+        "view_name_local": "",
+        "view_other_name": "Skull LAT,left",
+        "view_description": "颅骨左侧位",
+        "view_position": "LAT",
+        "application": "RAD",
+        "anatomic_region": "SKULL",
+        "patient_type": "Human",
+        "body_part_id": "Human_SKULL",
+        "view_icon_name": "/Image/Position/Human/Skull_T_lat_left.gif",
+        "view_big_icon_name": "/Image/Position/Human/Skull_T_lat_left.x.gif",
+        "view_coach_name": "/Image/Position/Human/Skull_T_lat_left.x.gif",
+        "modality": "DX",
+        "work_station_id": 0,
+        "apr_id": "View_DX_T_A_SK_LAT_00",
+        "img_proc_id": "View_DX_T_A_SK_LAT_00",
+        "config_object": {
+          "DX": {
+            "CollimatorCenter": "1",
+            "CollimatorFilter": "0",
+            "CollimatorNoChange": false,
+            "CollimatorSize": "8IN(20CM)X10IN(25CM)",
+            "CollimatorSizeLength": "10IN",
+            "CollimatorSizeWidth": "8IN",
+            "ImageHorizontalFlip": "NO",
+            "ImageLaterality": "U",
+            "ImageRotate": "0",
+            "LabelPosition": "RIGHT TOP",
+            "LabelStyle": "",
+            "PatientOrientationColumn": "R",
+            "PatientOrientationRow": "L",
+            "RatioFactorLength": 0,
+            "RatioFactorSize": 0,
+            "RatioFactorThickness": 0,
+            "RatioFactorWeight": 0,
+            "StandPos": "43",
+            "TargetEXI": 250,
+            "ViewID": "View_DX_T_A_SK_LAT_00"
+          }
+        },
+        "sort": 1,
+        "is_enabled": true,
+        "product": "DROS",
+        "is_pre_install": true
+      }
+    ]
+  }
+}
+```
+
+### Dynamic Behavior: None
+
+### Delay: 500ms to simulate network latency
+
+### Error Handling: None

+ 67 - 0
src/API/patient/viewActions.ts

@@ -0,0 +1,67 @@
+import axios from 'axios';
+
+interface View {
+  internal_id: string;
+  view_id: string;
+  view_name: string;
+  view_name_local: string;
+  view_other_name: string;
+  view_description: string;
+  view_position: string;
+  application: string;
+  anatomic_region: string;
+  patient_type: string;
+  body_part_id: string;
+  view_icon_name: string;
+  view_big_icon_name: string;
+  view_coach_name: string;
+  modality: string;
+  work_station_id: number;
+  apr_id: string;
+  img_proc_id: string;
+  // config_object: any;
+  sort: number;
+  is_enabled: boolean;
+  product: string;
+  is_pre_install: boolean;
+}
+
+interface FetchViewsResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: {
+    '@type': string;
+    view_list: View[];
+  };
+}
+
+export const fetchViews = async (
+  patient_type: string | null,
+  body_part: string | null,
+  is_enabled: boolean,
+  procedure_id: string | null
+): Promise<FetchViewsResponse> => {
+  const response = await axios.get('/dr/api/v1/auth/protocol/view', {
+    //todo get header info
+    //   authorization: string,
+    //   language: 'en' | 'zh',
+    //   product: 'DROS' | 'VETDROS',
+    //   source: 'Electron' | 'Browser' | 'Android'
+    headers: {
+      Authorization:
+        'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTEyNzc5NzAsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.ooTGwBXaNhtunbKbpqteWbjDwJLjnRmSIl80r5dp1pY',
+      Language: 'en',
+      Product: 'DROS',
+      Source: 'Electron',
+    },
+    params: {
+      patient_type,
+      body_part,
+      is_enabled,
+      procedure_id,
+    },
+  });
+
+  return response.data;
+};

+ 3 - 4
src/pages/patient/components/ProcedureCard.tsx

@@ -9,7 +9,7 @@ interface ProcedureCardProps extends CardProps {
   procedure: Procedure;
 }
 
-const ProcedureCard: React.FC<ProcedureCardProps> = ({ procedure }) => {
+const ProcedureCard: React.FC<ProcedureCardProps> = ({ procedure, style }) => {
   const dispatch = useDispatch();
 
   const handleClick = () => {
@@ -21,8 +21,7 @@ const ProcedureCard: React.FC<ProcedureCardProps> = ({ procedure }) => {
       hoverable
       onClick={handleClick}
       style={{
-        width: '100%',
-        height: '100%',
+        ...style,
         display: 'flex',
         alignItems: 'center',
         justifyContent: 'center',
@@ -30,7 +29,7 @@ const ProcedureCard: React.FC<ProcedureCardProps> = ({ procedure }) => {
       }}
       bodyStyle={{ padding: 0, textAlign: 'center' }}
     >
-      {procedure.ProcedureName}
+      {procedure.procedure_name}
     </Card>
   );
 };

+ 19 - 7
src/pages/patient/components/RegisterAvailableFilterBar.tsx

@@ -1,16 +1,21 @@
 import React from 'react';
 import { Row, Col, Select, Segmented } from 'antd';
 import { useSelector, useDispatch } from 'react-redux';
+import { AppDispatch } from '@/states/store';
 import { setCurrentPatientType } from '@/states/patientTypeSlice';
-import { setSelected } from '@/states/patient/register/SelectionTypeSlice';
+import {
+  SelectionState,
+  setSelected,
+} from '@/states/patient/register/SelectionTypeSlice';
 import { RootState } from '@/states/store';
 import { PatientType } from '@/API/patientType';
 import { BodyPart } from '@/API/bodyPart';
 import { setCurrentBodyPart } from '@/states/bodyPartSlice';
+import { fetchViewsOrProtocols } from '@/states/patient/viewSelection';
 
 interface Props {
-  selected: 'protocol' | 'view';
-  setSelected: (val: 'protocol' | 'view') => void;
+  selected: SelectionState;
+  // setSelected: (val: 'protocol' | 'view') => void;
   bodyPart: string | undefined;
   setBodyPart: (val: string | undefined) => void;
   patientType: string | undefined;
@@ -24,7 +29,7 @@ const RegisterAvailableFilterBar: React.FC<Props> = ({
   enabled,
   setEnabled,
 }) => {
-  const dispatch = useDispatch();
+  const dispatch = useDispatch<AppDispatch>();
   const selected = useSelector((state: RootState) => state.selection.selected);
   const bodyParts = useSelector(
     (state: RootState) => state.bodyPart.byPatientType
@@ -54,9 +59,16 @@ const RegisterAvailableFilterBar: React.FC<Props> = ({
               { label: '体位', value: 'view' },
             ]}
             value={selected}
-            onChange={(val) =>
-              dispatch(setSelected(val as 'protocol' | 'view'))
-            }
+            onChange={(val) => {
+              dispatch(setSelected(val as 'protocol' | 'view'));
+              dispatch(
+                fetchViewsOrProtocols({
+                  selection: selected,
+                  patientType: currentPatientType?.patient_type_id ?? null,
+                  bodyPart: currentBodyPart?.body_part_id ?? null,
+                })
+              );
+            }}
             block
           />
         </Col>

+ 10 - 4
src/pages/patient/components/register.available.list.tsx

@@ -3,9 +3,13 @@ import { Card } from 'antd';
 import ProtocolSelectionList from './register.protocol.list';
 import RegisterViewList from './register.available.view.list';
 import RegisterAvailableFilterBar from './RegisterAvailableFilterBar';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/states/store';
 
 const RegisterAvailableList: React.FC = () => {
-  const [selected, setSelected] = useState<'protocol' | 'view'>('protocol');
+  const selected = useSelector(
+    (state: RootState) => state.viewSelection.currentSelectionType
+  );
   const [bodyPart, setBodyPart] = useState<string | undefined>(undefined);
   const [patientType, setPatientType] = useState<string | undefined>(undefined);
   const [enabled, setEnabled] = useState<string | undefined>(undefined);
@@ -23,7 +27,6 @@ const RegisterAvailableList: React.FC = () => {
       {/* 过滤区域 */}
       <RegisterAvailableFilterBar
         selected={selected}
-        setSelected={setSelected}
         bodyPart={bodyPart}
         setBodyPart={setBodyPart}
         patientType={patientType}
@@ -32,8 +35,11 @@ const RegisterAvailableList: React.FC = () => {
         setEnabled={setEnabled}
       />
       {/* 列表区域 */}
-      <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
-        {selected === 'protocol' ? (
+      <div
+        style={{ flex: 1, overflow: 'auto', padding: 16 }}
+        className="border border-red-300"
+      >
+        {selected.selected === 'protocol' ? (
           <ProtocolSelectionList />
         ) : (
           <RegisterViewList />

+ 7 - 15
src/pages/patient/components/register.protocol.list.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Grid } from 'antd';
+import { Grid, Row } from 'antd';
 import { useSelector } from 'react-redux';
 // import { FormattedMessage } from 'react-intl';
 
@@ -11,12 +11,12 @@ const { useBreakpoint } = Grid;
 // 卡片尺寸配置
 const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
   if (screens.xl || screens.xxl) {
-    return { width: 150, height: 100 };
+    return { width: 50, height: 100 };
   }
   if (screens.md || screens.lg) {
-    return { width: 90, height: 60 };
+    return { width: 50, height: 60 };
   }
-  return { width: 60, height: 45 };
+  return { width: 50, height: 45 };
 };
 
 const ProtocolSelectionList: React.FC = () => {
@@ -29,18 +29,10 @@ const ProtocolSelectionList: React.FC = () => {
   );
 
   return (
-    <div
-      style={{
-        display: 'flex',
-        flexWrap: 'wrap',
-        gap: 16,
-        height: '100%',
-        overflow: 'auto',
-      }}
-    >
+    <Row>
       {protocols.map((item) => (
         <ProcedureCard
-          key={item.ProcedureID}
+          key={item.procedure_id}
           procedure={item}
           style={{
             width: cardSize.width,
@@ -52,7 +44,7 @@ const ProtocolSelectionList: React.FC = () => {
           }}
         />
       ))}
-    </div>
+    </Row>
   );
 };
 

+ 89 - 30
src/states/patient/viewSelection/index.ts

@@ -1,4 +1,4 @@
-import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
 import { setCurrentPatientType } from '../../patientTypeSlice';
 import { BodyPart } from '@/API/bodyPart';
 import { PatientType } from '@/API/patientType';
@@ -7,6 +7,9 @@ import {
   setSelected,
 } from '@/states/patient/register/SelectionTypeSlice';
 import { setCurrentBodyPart } from '../../bodyPartSlice';
+import { fetchProcedures, Procedure } from '@/API/patient/procedureActions';
+import { fetchViews } from '@/API/patient/viewActions';
+export type { Procedure };
 
 // 体位类型
 export interface View {
@@ -35,32 +38,32 @@ export interface View {
 }
 
 // 协议类型
-export interface Procedure {
-  ProcedureID: string;
-  ProcedureCode: string;
-  ProcedureName: string;
-  ProcedureOtherName: string;
-  ProcedureDescription: string;
-  PatientType: string;
-  ProcedureGroupID: string;
-  ProcedureType: string;
-  FastSearch: boolean;
-  Enable: boolean;
-  Order: number;
-  UserGroupID: string;
-  ProcedureCategory: string;
-  Modality: string;
-  IsImplanted: boolean;
-  MagFactor: number;
-  // ProcedureViews: View[];
-  // ProcedureViewRelations: any[];
-  ClinicProtocol: boolean;
-  IsFactoryDefault: boolean;
-  MinBMI: number;
-  MaxBMI: number;
-  AutoDecompression: boolean;
-  ConfigObjectValue: string;
-}
+// export interface Procedure {
+//   ProcedureID: string;
+//   ProcedureCode: string;
+//   ProcedureName: string;
+//   ProcedureOtherName: string;
+//   ProcedureDescription: string;
+//   PatientType: string;
+//   ProcedureGroupID: string;
+//   ProcedureType: string;
+//   FastSearch: boolean;
+//   Enable: boolean;
+//   Order: number;
+//   UserGroupID: string;
+//   ProcedureCategory: string;
+//   Modality: string;
+//   IsImplanted: boolean;
+//   MagFactor: number;
+//   // ProcedureViews: View[];
+//   // ProcedureViewRelations: any[];
+//   ClinicProtocol: boolean;
+//   IsFactoryDefault: boolean;
+//   MinBMI: number;
+//   MaxBMI: number;
+//   AutoDecompression: boolean;
+//   ConfigObjectValue: string;
+// }
 
 interface ViewSelectionState {
   selectedViews: View[]; // 已选择体位列表
@@ -127,6 +130,39 @@ const initialState: ViewSelectionState = {
   currentSelectionType: { selected: 'protocol' },
 };
 
+export interface FilterCondition {
+  selection: string;
+  patientType: string | null;
+  bodyPart: string | null;
+}
+
+export const fetchViewsOrProtocols = createAsyncThunk(
+  'data/fetchData',
+  async (filter: FilterCondition, { rejectWithValue }) => {
+    try {
+      if (filter.selection === 'protocol') {
+        const response = await fetchProcedures(
+          filter.patientType,
+          filter.bodyPart,
+          true
+        );
+        return response.data.procedure_list;
+      }
+      if (filter.selection === 'view') {
+        const response = await fetchViews(
+          filter.patientType,
+          filter.bodyPart,
+          true,
+          null
+        );
+        return response.data.view_list;
+      }
+    } catch (error) {
+      return rejectWithValue(error.message);
+    }
+  }
+);
+
 const viewSelectionSlice = createSlice({
   name: 'viewSelection',
   initialState,
@@ -170,10 +206,33 @@ const viewSelectionSlice = createSlice({
         }
       })
       .addCase(setSelected, (state, action) => {
-        console.log(
-          `在view section中感知到 currentSelectionType : ${action.payload}`
-        );
+        console
+          .log
+          // `在view section中感知到 currentSelectionType : ${action.payload}`
+          ();
         state.currentSelectionType.selected = action.payload;
+      })
+      // eslint-disable-next-line
+      .addCase(fetchViewsOrProtocols.pending, (state, action) => {
+        console.log(`查询view或者protocals:pending`);
+      })
+      .addCase(fetchViewsOrProtocols.fulfilled, (state, action) => {
+        // console.log(`查询view或者protocals: fulfilled ${JSON.stringify(action.payload)}`);
+        if (action.meta.arg.selection === 'view') {
+          state.availableViews = action.payload as unknown as View[];
+          console.log(
+            `查询 Views : fulfilled ${JSON.stringify(state.availableViews)}`
+          );
+        } else {
+          state.protocols = action.payload as unknown as Procedure[];
+          console.log(
+            `查询protocals: fulfilled ${JSON.stringify(state.protocols)}`
+          );
+        }
+      })
+      // eslint-disable-next-line
+      .addCase(fetchViewsOrProtocols.rejected, (state, action) => {
+        console.log(`查询view或者protocals: rejected`);
       });
   },
 });