Browse Source

实现注册提交

dengdx 2 months ago
parent
commit
3c61035df4

+ 202 - 0
src/API/patient/workAction.md

@@ -0,0 +1,202 @@
+# 说明
+
+注册一个work信息,即患者检查信息
+
+## 1. 按患者类型和身体部位从后端拉取procedure
+
+### HTTP Method: POST
+
+### Headers
+
+- **Authorization** : 默认值是 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTEyNzc5NzAsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.ooTGwBXaNhtunbKbpqteWbjDwJLjnRmSIl80r5dp1pY
+- **Language**: `en` 或 `zh`
+- **Product**: `DROS` 或 `VETDROS`
+- **Source**: `Electron` 或 `Browser` 或 `Android`
+
+### Endpoint URL: /dr/api/v1/auth/study
+
+### query parameters
+
+### request_body 请求body
+
+请求参数example:
+
+```json
+{
+  "accession_number": "ACC0012345",
+  "patient_id": "PET007",
+  "patient_name": "Buddy",
+  "patient_size": "Large",
+  "patient_age": "5Y",
+  "patient_dob": "2025-06-10T03:12:36.181739Z",
+  "patient_sex": "",
+  "sex_neutered": "",
+  "pregnancy_status": "",
+  "chip_number": "CHIP123456789",
+  "variety": "Golden Retriever",
+  "patient_type": "Human",
+  "ref_physician": "Dr. Smith (Vet)",
+  "operator_id": "OP987",
+  "modality": "DX",
+  "weight": 25,
+  "thickness": 15,
+  "length": 60,
+  "study_type": "Normal",
+  "comment": "Patient presented with limping in right hind leg. Sedation administered.",
+  "views": [
+    {
+      "view_id": "View_DX_T_A_SK_AP_00",
+      "procedure_id": "P0-0002"
+    }
+  ]
+}
+```
+
+说明:
+
+- 属性views是个数组
+- 非views的其他属性形成一个work类型
+
+### Response Status: 200 (OK)
+
+### Response Data: Returns a JSON
+
+example :
+
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "solution": "",
+  "data": {
+    "@type": "type.googleapis.com/dr.study.StudyReply",
+    "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+    "study_id": "20250610164933314",
+    "public_study_id": "",
+    "specific_character_set": "ISO_IR 192",
+    "accession_number": "ACC0012345",
+    "ref_physician": "Dr. Smith (Vet)",
+    "patient_id": "PET007",
+    "patient_name": "Buddy (Dog)",
+    "patient_size": "Large",
+    "other_patient_ids": "",
+    "other_patient_names": "",
+    "owner_name": "",
+    "patient_age": "5Y",
+    "patient_dob": "2025-06-10T03:12:36.181739Z",
+    "patient_sex": "Male",
+    "patient_state": "",
+    "admitting_time": null,
+    "priority": "",
+    "reg_source": "",
+    "study_status": "",
+    "study_description": "",
+    "study_start_datetime": null,
+    "study_end_datetime": null,
+    "scheduled_procedure_step_start_date": null,
+    "performed_physician": "",
+    "study_lock": "UNLOCKED",
+    "folder_path": "",
+    "operator_name": "",
+    "modality": "DR",
+    "weight": 25,
+    "thickness": 15,
+    "length": 60,
+    "patient_type": "",
+    "study_type": "",
+    "mwl": "",
+    "is_exported": false,
+    "is_edited": false,
+    "is_appended": false,
+    "department": "",
+    "mapped_status": false,
+    "qc_result": false,
+    "comment": "Patient presented with limping in right hind leg. Sedation administered.",
+    "sort": 0,
+    "product": "DROS",
+    "series": [
+      {
+        "series_instance_uid": "1.2.276.0.1000000.5.1.3.701601461.19649.1749545373.668669",
+        "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+        "study_id": "20250610164933314",
+        "procedure_id": "P0-0002",
+        "body_part": "Human_SKULL",
+        "performed_datetime": null,
+        "performed_protocol_code_meaning": "颅骨前后位 + 侧位",
+        "performed_protocol_code_value": "P0-0002",
+        "sort": 1,
+        "product": "DROS",
+        "is_pre_install": true,
+        "images": [
+          {
+            "sop_instance_id": "1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668670",
+            "series_instance_uid": "1.2.276.0.1000000.5.1.3.701601461.19649.1749545373.668669",
+            "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+            "secondary_sop_uid": "",
+            "study_id": "20250610164933314",
+            "view_id": "View_DX_T_A_SK_AP_00",
+            "view_description": "颅骨前后位",
+            "image_status": "QUEUEING",
+            "image_file_path": "",
+            "acquisition_mode": "RAD",
+            "acquisition_context": null,
+            "img_proc_context": null,
+            "sort": 1,
+            "product": "DROS",
+            "is_pre_install": true
+          },
+          {
+            "sop_instance_id": "1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671",
+            "series_instance_uid": "1.2.276.0.1000000.5.1.3.701601461.19649.1749545373.668669",
+            "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+            "secondary_sop_uid": "",
+            "study_id": "20250610164933314",
+            "view_id": "View_DX_T_A_SK_LAT_00",
+            "view_description": "颅骨左侧位",
+            "image_status": "QUEUEING",
+            "image_file_path": "",
+            "acquisition_mode": "RAD",
+            "acquisition_context": null,
+            "img_proc_context": null,
+            "sort": 2,
+            "product": "DROS",
+            "is_pre_install": true
+          }
+        ]
+      },
+      {
+        "series_instance_uid": "1.2.276.0.1000000.5.1.3.701601461.19649.1749545373.668672",
+        "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+        "study_id": "20250610164933314",
+        "procedure_id": "",
+        "body_part": "Human_SKULL",
+        "performed_datetime": null,
+        "performed_protocol_code_meaning": "",
+        "performed_protocol_code_value": "",
+        "sort": 2,
+        "product": "DROS",
+        "is_pre_install": true,
+        "images": [
+          {
+            "sop_instance_id": "1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668673",
+            "series_instance_uid": "1.2.276.0.1000000.5.1.3.701601461.19649.1749545373.668672",
+            "study_instance_uid": "1.2.276.0.1000000.5.1.2.701601461.19649.1749545373.668668",
+            "secondary_sop_uid": "",
+            "study_id": "20250610164933314",
+            "view_id": "View_DX_T_A_SK_Special_04",
+            "view_description": "颅骨汤氏位",
+            "image_status": "QUEUEING",
+            "image_file_path": "",
+            "acquisition_mode": "RAD",
+            "acquisition_context": null,
+            "img_proc_context": null,
+            "sort": 3,
+            "product": "DROS",
+            "is_pre_install": true
+          }
+        ]
+      }
+    ]
+  }
+}
+```

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

@@ -0,0 +1,132 @@
+import axios from 'axios';
+
+interface View {
+  view_id: string;
+  procedure_id: string;
+}
+interface Image {
+  sop_instance_id: string;
+  series_instance_uid: string;
+  study_instance_uid: string;
+  secondary_sop_uid: string;
+  study_id: string;
+  view_id: string;
+  view_description: string;
+  image_status: string;
+  image_file_path: string;
+  acquisition_mode: string;
+  acquisition_context: string | null;
+  img_proc_context: string | null;
+  sort: number;
+  product: string;
+  is_pre_install: boolean;
+}
+
+interface Series {
+  series_instance_uid: string;
+  study_instance_uid: string;
+  study_id: string;
+  procedure_id: string;
+  body_part: string;
+  performed_datetime: string | null;
+  performed_protocol_code_meaning: string;
+  performed_protocol_code_value: string;
+  sort: number;
+  product: string;
+  is_pre_install: boolean;
+  images: Image[];
+}
+
+interface Work {
+  accession_number: string;
+  patient_id: string;
+  patient_name: string;
+  patient_size: string;
+  patient_age: string;
+  patient_dob: string;
+  patient_sex: string;
+  sex_neutered: string;
+  pregnancy_status: string;
+  chip_number: string;
+  variety: string;
+  patient_type: string;
+  ref_physician: string;
+  operator_id: string;
+  modality: string;
+  weight: number;
+  thickness: number;
+  length: number;
+  study_type: string;
+  comment: string;
+  views: View[];
+}
+
+interface RegisterWorkResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: {
+    study_instance_uid: string;
+    study_id: string;
+    public_study_id: string;
+    specific_character_set: string;
+    accession_number: string;
+    ref_physician: string;
+    patient_id: string;
+    patient_name: string;
+    patient_size: string;
+    other_patient_ids: string;
+    other_patient_names: string;
+    owner_name: string;
+    patient_age: string;
+    patient_dob: string;
+    patient_sex: string;
+    patient_state: string;
+    admitting_time: string | null;
+    priority: string;
+    reg_source: string;
+    study_status: string;
+    study_description: string;
+    study_start_datetime: string | null;
+    study_end_datetime: string | null;
+    scheduled_procedure_step_start_date: string | null;
+    performed_physician: string;
+    study_lock: string;
+    folder_path: string;
+    operator_name: string;
+    modality: string;
+    weight: number;
+    thickness: number;
+    length: number;
+    patient_type: string;
+    study_type: string;
+    mwl: string;
+    is_exported: boolean;
+    is_edited: boolean;
+    is_appended: boolean;
+    department: string;
+    mapped_status: boolean;
+    qc_result: boolean;
+    comment: string;
+    sort: number;
+    product: string;
+    series: Series[];
+  };
+}
+
+const registerWork = async (work: Work): Promise<RegisterWorkResponse> => {
+  console.log('Work object:', JSON.stringify(work, null, 2));
+
+  const response = await axios.post('/dr/api/v1/auth/study', work, {
+    headers: {
+      Authorization:
+        'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTEyNzc5NzAsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.ooTGwBXaNhtunbKbpqteWbjDwJLjnRmSIl80r5dp1pY',
+      Language: 'en',
+      Product: 'DROS',
+      Source: 'Electron',
+    },
+  });
+  return response.data;
+};
+
+export { registerWork };

+ 3 - 1
src/assets/i18n/messages/en.js

@@ -68,6 +68,8 @@ export default {
   'register.bodyPart': 'Body Part',
   'register.bodyPart.head': 'Head',
   'register.bodyPart.chest': 'Chest',
+  'register.accessionNumber': 'Accession Number',
+  'register.accessionNumber.placeholder': 'Enter accession number',
   // 协议列表
   'register.protocol.A': 'Protocol A',
   'register.protocol.B': 'Protocol B',
@@ -124,4 +126,4 @@ export default {
   'actionPanel.burn': 'Burn',
   'actionPanel.export': 'Export',
   'actionPanel.import': 'Import',
-};
+};

+ 15 - 13
src/assets/i18n/messages/zh.js

@@ -1,18 +1,18 @@
 export default {
   greeting: '你好,世界!',
   name: '张三',
-  patient:'患者管理',
-  register:'注册',
-  tasklist:'任务清单',
-  historylist:'历史清单',
-  archivelist:'归档清单',
-  bin:'回收站',
-  outputlist:'传输清单',
-  exam:'检查',
-  examlist:'检查清单',
-  process:'处理',
-  print:'打印',
-  printlist:'打印清单',
+  patient: '患者管理',
+  register: '注册',
+  tasklist: '任务清单',
+  historylist: '历史清单',
+  archivelist: '归档清单',
+  bin: '回收站',
+  outputlist: '传输清单',
+  exam: '检查',
+  examlist: '检查清单',
+  process: '处理',
+  print: '打印',
+  printlist: '打印清单',
   worklist: '任务清单',
   'worklist.operationPanel': '操作面板',
   'register.basicInfoPanel': '基本信息表单区域',
@@ -68,6 +68,8 @@ export default {
   'register.bodyPart': '身体部位',
   'register.bodyPart.head': '头部',
   'register.bodyPart.chest': '胸部',
+  'register.accessionNumber': '登记号',
+  'register.accessionNumber.placeholder': '请输入登记号',
   // 协议列表
   'register.protocol.A': '协议A',
   'register.protocol.B': '协议B',
@@ -124,4 +126,4 @@ export default {
   'actionPanel.burn': '刻录',
   'actionPanel.export': '导出',
   'actionPanel.import': '导入',
-};
+};

+ 15 - 0
src/hooks/useRegisterState.ts

@@ -0,0 +1,15 @@
+import { useSelector } from 'react-redux';
+import { RootState } from '@/states/store';
+
+const useRegisterState = () => {
+  const selectedViews = useSelector(
+    (state: RootState) => state.viewSelection.selectedViews
+  );
+  const currentPatientType = useSelector(
+    (state: RootState) => state.viewSelection.currentPatientType
+  );
+
+  return { selectedViews, currentPatientType };
+};
+
+export default useRegisterState;

+ 38 - 13
src/pages/patient/components/register.form.tsx

@@ -1,5 +1,13 @@
 import React from 'react';
-import { Form, Input, DatePicker, InputNumber, Select, Radio } from 'antd';
+import {
+  Form,
+  Input,
+  DatePicker,
+  InputNumber,
+  Select,
+  Radio,
+  FormInstance,
+} from 'antd';
 import { useIntl, FormattedMessage } from 'react-intl';
 
 const genderOptions = [
@@ -43,7 +51,7 @@ const bodyTypeOptions = [
     ),
   },
   {
-    value: 'fat',
+    value: 'Large',
     label: (
       <FormattedMessage
         id="register.bodyType.fat"
@@ -105,11 +113,12 @@ const bodyPartOptions = [
 ];
 interface BasicInfoFormProps {
   style?: React.CSSProperties;
+  form?: FormInstance;
 }
-const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
+const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style, form }) => {
   const intl = useIntl();
   return (
-    <Form layout="vertical" style={style}>
+    <Form form={form} layout="vertical" style={style}>
       <Form.Item
         label={
           <FormattedMessage
@@ -117,7 +126,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.patientId"
           />
         }
-        name="patientId"
+        name="patient_id"
       >
         <Input
           placeholder={intl.formatMessage({
@@ -133,7 +142,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.patientName"
           />
         }
-        name="patientName"
+        name="patient_name"
       >
         <Input
           placeholder={intl.formatMessage({
@@ -197,7 +206,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.dateOfBirth"
           />
         }
-        name="dateOfBirth"
+        name="patient_dob"
       >
         <DatePicker format="YYYY-MM-DD" style={{ width: '100%' }} />
       </Form.Item>
@@ -205,7 +214,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
         label={
           <FormattedMessage id="register.age" defaultMessage="register.age" />
         }
-        name="age"
+        name="patient_age"
       >
         <InputNumber min={0} style={{ width: '100%' }} />
       </Form.Item>
@@ -216,7 +225,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.gender"
           />
         }
-        name="gender"
+        name="patient_sex"
       >
         <Select options={genderOptions} />
       </Form.Item>
@@ -227,7 +236,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.bodyType"
           />
         }
-        name="bodyType"
+        name="patient_size"
       >
         <Select options={bodyTypeOptions} />
       </Form.Item>
@@ -249,7 +258,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.height"
           />
         }
-        name="height"
+        name="length"
       >
         <InputNumber min={0} addonAfter="cm" style={{ width: '100%' }} />
       </Form.Item>
@@ -260,7 +269,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.pregnancyStatus"
           />
         }
-        name="pregnancyStatus"
+        name="pregnancy_status"
       >
         <Radio.Group
           options={pregnancyStatusOptions}
@@ -275,7 +284,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
             defaultMessage="register.referringPhysician"
           />
         }
-        name="referringPhysician"
+        name="ref_physician"
       >
         <Input
           placeholder={intl.formatMessage({
@@ -295,6 +304,22 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({ style }) => {
       >
         <Select options={bodyPartOptions} />
       </Form.Item>
+      <Form.Item
+        label={
+          <FormattedMessage
+            id="register.accessionNumber"
+            defaultMessage="register.accessionNumber"
+          />
+        }
+        name="accession_number"
+      >
+        <Input
+          placeholder={intl.formatMessage({
+            id: 'register.accessionNumber.placeholder',
+            defaultMessage: 'register.accessionNumber.placeholder',
+          })}
+        />
+      </Form.Item>
     </Form>
   );
 };

+ 33 - 6
src/pages/patient/register.tsx

@@ -1,16 +1,38 @@
 import React from 'react';
-import { Row, Col, Collapse, Grid, Divider, Button, Space } from 'antd';
+import { Row, Col, Collapse, Grid, Divider, Button, Space, Form } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import BasicInfoForm from './components/register.form';
-// import ProtocolList from './components/register.protocol.list';
 import SelectedProtocolList from './components/register.selected.view.list';
 import RegisterAvailableList from './components/register.available.list';
+import { registerWork } from '@/API/patient/workActions';
+import useRegisterState from '@/hooks/useRegisterState';
 
 const { useBreakpoint } = Grid;
 const { Panel } = Collapse;
 
 const RegisterPage: React.FC = () => {
   const screens = useBreakpoint();
+  const [form] = Form.useForm();
+  const { selectedViews, currentPatientType } = useRegisterState();
+
+  const handleRegister = async () => {
+    try {
+      const work = {
+        ...form.getFieldsValue(),
+        patient_type: currentPatientType?.patient_type_id,
+        modality: 'dx',
+        study_type: 'Normal',
+        views: selectedViews.map((view) => ({
+          view_id: view.view_id,
+          procedure_id: 'aaa',
+        })),
+      };
+      const response = await registerWork(work);
+      console.log('Work registered successfully:', response);
+    } catch (error) {
+      console.error('Error registering work:', error);
+    }
+  };
 
   return (
     <>
@@ -29,7 +51,7 @@ const RegisterPage: React.FC = () => {
             }}
             className="h-full"
           >
-            <BasicInfoForm style={{ overflow: 'auto' }} />
+            <BasicInfoForm form={form} style={{ overflow: 'auto' }} />
           </Col>
           <Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} className="h-full">
             <RegisterAvailableList />
@@ -47,7 +69,10 @@ const RegisterPage: React.FC = () => {
             lg={12}
             className="h-full flex flex-column"
           >
-            <BasicInfoForm style={{ overflow: 'auto', width: '100%' }} />
+            <BasicInfoForm
+              form={form}
+              style={{ overflow: 'auto', width: '100%' }}
+            />
           </Col>
           <Col xs={24} sm={24} md={12} lg={12}>
             <Row gutter={[0, 16]}>
@@ -76,7 +101,7 @@ const RegisterPage: React.FC = () => {
             }
             key="1"
           >
-            <BasicInfoForm />
+            <BasicInfoForm form={form} />
           </Panel>
           <Panel
             header={
@@ -110,7 +135,9 @@ const RegisterPage: React.FC = () => {
           zIndex: 1000,
         }}
       >
-        <Button type="primary">注册</Button>
+        <Button type="primary" onClick={handleRegister}>
+          注册
+        </Button>
         <Button type="default">检查</Button>
       </Space>
     </>