Ver Fonte

feat: 实现急诊患者拍照功能并更新文档

- 新增 cameraActions.ts API 接口,实现照片上传功能(智能处理 base64 格式)
- 实现 BrowserCameraService 摄像头服务,返回标准 data URL 格式
- 创建 cameraSlice Redux 状态管理,统一使用 studyId 字段命名
- 实现 CameraModal 组件,完整的拍照、预览、上传流程
- 集成到 ContentAreaLarge,添加摄像头按钮入口
- 修复 video 元素生命周期和事件绑定时机问题
- 更新 DR.md API 文档,补充患者拍照接口说明
- 更新急诊拍照功能实现文档,统一字段命名和数据格式说明

改动文件:
- src/API/patient/cameraActions.ts(新增)
- src/services/camera/BrowserCameraService.ts
- src/states/exam/cameraSlice.ts
- src/components/CameraModal.tsx
- src/pages/exam/ContentAreaLarge.tsx
- src/states/exam/bodyPositionListSlice.ts
- docs/DR.md
- docs/实现/急诊拍照功能.md
sw há 11 horas atrás
pai
commit
01484c63b1

+ 256 - 211
docs/DR.md

@@ -2052,28 +2052,31 @@ zh_CN
 	"code": "0x000000",
 	"data": [
 		{
-			"task_id": "0199cd46-82f0-76c5-b1d3-9399668a1a05",
-			"patient_name": "1update3",
-			"patient_id": "1update2",
+			"task_id": "0199d2af-c6ac-78fd-b869-ae6738e5ad52",
+			"patient_name": "Buddy (Dog)",
+			"patient_id": "PET007",
 			"priority": "Medium",
-			"status": "ARRIVED",
-			"destination": "pacs3"
+			"status": "COMPLETED",
+			"destination": "pacs3",
+			"start_time": "2025-10-11T09:52:39.084596Z"
 		},
 		{
-			"task_id": "0199cd46-460d-770e-ac8e-549939a4a7d4",
-			"patient_name": "1update3",
-			"patient_id": "1update2",
+			"task_id": "0199d2af-c54f-76c3-9b4b-d0fc3985fbaa",
+			"patient_name": "Buddy (Dog)",
+			"patient_id": "PET007",
 			"priority": "Medium",
-			"status": "ARRIVED",
-			"destination": "pacs3"
+			"status": "COMPLETED",
+			"destination": "pacs3",
+			"start_time": "2025-10-11T09:52:38.735453Z"
 		},
 		{
-			"task_id": "0199cd45-dff6-70e4-92e3-2339f0bee57a",
+			"task_id": "0199d1f7-1638-7c00-a580-c8e3234cdaa0",
 			"patient_name": "1update3",
 			"patient_id": "1update2",
 			"priority": "Medium",
-			"status": "ARRIVED",
-			"destination": "pacs3"
+			"status": "COMPLETED",
+			"destination": "pacs3",
+			"start_time": "2025-10-11T06:30:55.288Z"
 		}
 	],
 	"description": "Success",
@@ -2081,7 +2084,7 @@ zh_CN
 }
 ```
 请求示例
-![  ](http://f1-xyj.fangdeco.cn/attachment/2025/10/10/2M0ruPn3TXs/93dd3fd6fb9547379918d93652c9151e)
+![  ](http://f1-xyj.fangdeco.cn/attachment/2025/10/11/2M2RwohGYQy/4cebd746f7d349ac992e83d1656ca8b2)
 
 ## 15.4	删除发送任务(批量)
 
@@ -2192,7 +2195,7 @@ zh_CN
 ### 请求体(Request Body)
 | 参数名称 | 数据类型 | 默认值 | 不为空 | 描述 |
 | ------ | ------ | ------ | ------ | ------ |
-| instance_uid|string||true|图像id|
+| instance_uid|string||true|任务id|
 ### 响应体
 ● 200: OK 响应数据格式:JSON
 ```json
@@ -2674,107 +2677,127 @@ Completed|
 ```json
 {
 	"code": "0x000000",
-	"description": "Success",
+	"description": "成功",
 	"solution": "",
 	"data": {
-		"@type": "type.googleapis.com/dr.protocol.ViewList",
-		"count": 149,
-		"views": [
+		"@type": "type.googleapis.com/dr.study.StudyList",
+		"count": 2,
+		"studies": [
 			{
-				"internal_id": "View_DX_T_A_SK_Axial_00",
-				"view_id": "View_DX_T_A_SK_Axial_00",
-				"view_name": "颅骨前后轴位",
-				"view_name_local": "",
-				"view_other_name": "Skull AP Axial",
-				"view_description": "颅骨前后轴位",
-				"view_position": "AP",
-				"application": "RAD",
-				"anatomic_region": "SKULL",
-				"patient_type": "Human",
-				"body_part_id": "Human_SKULL",
-				"view_icon_name": "/Image/Position/Human/m20-063.gif",
-				"view_big_icon_name": "/Image/Position/Human/m20-063.large.gif",
-				"view_coach_name": "/Image/Position/Human/m20-063.large.gif",
+				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.20101.1760359944.254055",
+				"study_id": "251013205224825",
+				"public_study_id": "",
+				"specific_character_set": "ISO_IR 192",
+				"accession_number": "accessnumber444",
+				"ref_physician": "",
+				"patient_id": "1113333",
+				"patient_name": "124",
+				"patient_english_name": "",
+				"patient_former_name": "",
+				"patient_size": "Medium",
+				"other_patient_ids": "",
+				"other_patient_names": "",
+				"patient_age": "000Y",
+				"patient_dob": "2025-10-13T12:52:15.210Z",
+				"patient_sex": "",
+				"sex_neutered": "",
+				"pregnancy_status": "",
+				"patient_state": "",
+				"admitting_time": null,
+				"priority": "",
+				"reg_source": "",
+				"study_description": "",
+				"study_start_datetime": "2025-10-13T12:52:24.255622Z",
+				"study_end_datetime": null,
+				"scheduled_procedure_step_start_date": null,
+				"performed_physician": "",
+				"study_lock": "Unlocked",
+				"folder_path": "",
+				"operator_name": "",
 				"modality": "DX",
-				"work_station_id": 0,
-				"apr_id": "View_DX_T_A_SK_Axial_00",
-				"img_proc_id": "View_DX_T_A_SK_Axial_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_Axial_00"
-					}
-				},
-				"sort": 1,
-				"is_enabled": true,
-				"product": "DROS",
-				"is_pre_install": true
+				"weight": 0,
+				"thickness": 0,
+				"length": 0,
+				"patient_type": "Dog",
+				"study_type": "Normal",
+				"owner_name": "主人孙某",
+				"chip_number": "",
+				"variety": "",
+				"is_anaesthesia": false,
+				"is_sedation": false,
+				"mwl": "",
+				"is_exported": false,
+				"is_edited": false,
+				"is_appended": false,
+				"department": "",
+				"mapped_status": false,
+				"qc_result": false,
+				"comment": "",
+				"study_status": "InProgress",
+				"portrait_status": "NotSaved",
+				"portrait_file": ".",
+				"sort": 0,
+				"product": "VETDROS",
+				"create_time": "2025-10-13T12:52:24.313859Z",
+				"series": []
 			},
 			{
-				"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",
+				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.4730.1760342536.600785",
+				"study_id": "251013160216342",
+				"public_study_id": "",
+				"specific_character_set": "ISO_IR 192",
+				"accession_number": "accessnumber444",
+				"ref_physician": "",
+				"patient_id": "1113333",
+				"patient_name": "124",
+				"patient_english_name": "",
+				"patient_former_name": "",
+				"patient_size": "Medium",
+				"other_patient_ids": "",
+				"other_patient_names": "",
+				"patient_age": "000Y",
+				"patient_dob": "2025-10-13T08:02:11.982Z",
+				"patient_sex": "",
+				"sex_neutered": "",
+				"pregnancy_status": "",
+				"patient_state": "",
+				"admitting_time": null,
+				"priority": "",
+				"reg_source": "",
+				"study_description": "",
+				"study_start_datetime": "2025-10-13T08:02:16.602164Z",
+				"study_end_datetime": null,
+				"scheduled_procedure_step_start_date": null,
+				"performed_physician": "",
+				"study_lock": "Unlocked",
+				"folder_path": "",
+				"operator_name": "",
 				"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
+				"weight": 0,
+				"thickness": 0,
+				"length": 0,
+				"patient_type": "Dog",
+				"study_type": "Normal",
+				"owner_name": "主人孙某",
+				"chip_number": "",
+				"variety": "",
+				"is_anaesthesia": false,
+				"is_sedation": false,
+				"mwl": "",
+				"is_exported": false,
+				"is_edited": false,
+				"is_appended": false,
+				"department": "",
+				"mapped_status": false,
+				"qc_result": false,
+				"comment": "",
+				"study_status": "Arrived",
+				"portrait_status": "",
+				"portrait_file": ".",
+				"sort": 0,
+				"product": "VETDROS",
+				"create_time": "2025-10-13T08:02:16.638166Z",
+				"series": []
 			}
 		]
 	}
@@ -2807,22 +2830,22 @@ Completed|
 	"solution": "",
 	"data": {
 		"@type": "type.googleapis.com/dr.study.Study",
-		"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
-		"study_id": "250929163817805",
+		"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
+		"study_id": "251013110602828",
 		"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_english_name": "Buddy en",
-		"patient_former_name": "Buddy f",
-		"patient_size": "Large",
+		"accession_number": "accessnumber444",
+		"ref_physician": "",
+		"patient_id": "1113333",
+		"patient_name": "124",
+		"patient_english_name": "",
+		"patient_former_name": "",
+		"patient_size": "Medium",
 		"other_patient_ids": "",
 		"other_patient_names": "",
-		"patient_age": "008Y",
-		"patient_dob": "2025-06-10T03:12:36.181739Z",
-		"patient_sex": "M",
+		"patient_age": "000Y",
+		"patient_dob": "2025-10-13T03:04:39.003Z",
+		"patient_sex": "",
 		"sex_neutered": "",
 		"pregnancy_status": "",
 		"patient_state": "",
@@ -2830,24 +2853,24 @@ Completed|
 		"priority": "",
 		"reg_source": "",
 		"study_description": "",
-		"study_start_datetime": "2025-09-29T08:38:17.283651Z",
+		"study_start_datetime": "2025-10-13T03:06:02.988664Z",
 		"study_end_datetime": null,
 		"scheduled_procedure_step_start_date": null,
 		"performed_physician": "",
 		"study_lock": "Unlocked",
 		"folder_path": "",
-		"operator_name": "OP987",
+		"operator_name": "",
 		"modality": "DX",
-		"weight": 25,
-		"thickness": 15,
-		"length": 60,
-		"patient_type": "Human",
-		"study_type": "Normal",
-		"owner_name": "owner1",
-		"chip_number": "CHIP123456789",
-		"variety": "Golden Retriever",
-		"is_anaesthesia": true,
-		"is_sedation": true,
+		"weight": 0,
+		"thickness": 0,
+		"length": 0,
+		"patient_type": "Dog",
+		"study_type": "Emergency",
+		"owner_name": "主人孙某",
+		"chip_number": "",
+		"variety": "",
+		"is_anaesthesia": false,
+		"is_sedation": false,
 		"mwl": "",
 		"is_exported": false,
 		"is_edited": false,
@@ -2855,136 +2878,158 @@ Completed|
 		"department": "",
 		"mapped_status": false,
 		"qc_result": false,
-		"comment": "一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四",
-		"study_status": "Arrived",
+		"comment": "",
+		"study_status": "Completed",
+		"portrait_status": "Saved",
+		"portrait_file": "2.25.156.999999.0000.1.5.8323328.289656.1760324762.914274.dcm",
 		"sort": 0,
-		"product": "DROS",
-		"create_time": "2025-09-29T08:38:17.353598Z",
+		"product": "VETDROS",
+		"create_time": "2025-10-13T03:06:03.016908Z",
 		"series": [
 			{
-				"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
-				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
-				"study_id": "250929163817805",
-				"procedure_id": "P0-0002",
-				"patient_type": "Human",
-				"body_part": "Human_SKULL",
+				"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324762.914275",
+				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
+				"study_id": "251013110602828",
+				"procedure_id": "",
+				"patient_type": "Dog",
+				"body_part": "Dog_Hip",
 				"performed_datetime": null,
-				"performed_protocol_code_meaning": "颅骨前后位 + 侧位",
-				"performed_protocol_code_value": "P0-0002",
+				"performed_protocol_code_meaning": "",
+				"performed_protocol_code_value": "",
 				"sort": 1,
-				"product": "DROS",
+				"product": "VETDROS",
 				"is_pre_install": true,
-				"create_time": "2025-09-29T08:38:17.359308Z",
+				"create_time": "2025-10-13T03:06:03.019906Z",
 				"images": [
 					{
-						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.269954.1759135097.323786",
-						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
-						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.289656.1760324762.914276",
+						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324762.914275",
+						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
 						"secondary_sop_uid": "",
-						"study_id": "250929163817805",
-						"view_id": "View_DX_T_A_SK_AP_00",
-						"view_description": "颅骨前后位",
-						"patient_type": "Human",
-						"body_part_id": "Human_SKULL",
-						"anatomic_region": "Skull",
+						"study_id": "251013110602828",
+						"view_id": "View_Dog_PV_LAT_02",
+						"view_description": "髋关节侧位",
+						"patient_type": "Dog",
+						"body_part_id": "Dog_Hip",
+						"anatomic_region": "Hip joint",
 						"image_type": "expose",
 						"image_file_path": "",
-						"image_file": "",
-						"thumbnail_file": "",
+						"image_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324762.914276.dcm",
+						"thumbnail_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324762.914276.webp",
 						"acquisition_mode": "RAD",
 						"acquisition_context": null,
 						"img_proc_context": null,
-						"comment": "",
-						"expose_status": "Unexposed",
-						"expose_time": null,
-						"judged_status": "NotJudged",
+						"comment": "task_version: 0.5.0-7-g3a9cf08",
+						"expose_status": "Exposed",
+						"expose_time": "2025-10-13T03:07:12.292005Z",
+						"judged_status": "Accept",
 						"send_status": "Unsent",
 						"export_status": "NotExported",
-						"storage_status": "NotSaved",
+						"storage_status": "Saved",
+						"raw_exists": true,
 						"ticket": "",
 						"sort": 1,
-						"product": "DROS",
+						"product": "VETDROS",
 						"is_pre_install": true,
-						"create_time": "2025-09-29T08:38:17.361002Z"
-					},
+						"create_time": "2025-10-13T03:06:03.022905Z"
+					}
+				]
+			},
+			{
+				"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324762.914277",
+				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
+				"study_id": "251013110602828",
+				"procedure_id": "",
+				"patient_type": "Dog",
+				"body_part": "Dog_Hip",
+				"performed_datetime": null,
+				"performed_protocol_code_meaning": "",
+				"performed_protocol_code_value": "",
+				"sort": 2,
+				"product": "VETDROS",
+				"is_pre_install": true,
+				"create_time": "2025-10-13T03:06:03.021321Z",
+				"images": [
 					{
-						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.269954.1759135097.323787",
-						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
-						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914278",
+						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324762.914277",
+						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
 						"secondary_sop_uid": "",
-						"study_id": "250929163817805",
-						"view_id": "View_DX_T_A_SK_LAT_00",
-						"view_description": "颅骨左侧位",
-						"patient_type": "Human",
-						"body_part_id": "Human_SKULL",
-						"anatomic_region": "Skull",
+						"study_id": "251013110602828",
+						"view_id": "View_Dog_PV_LAT_01",
+						"view_description": "骨盆侧位",
+						"patient_type": "Dog",
+						"body_part_id": "Dog_Hip",
+						"anatomic_region": "Pelvis",
 						"image_type": "expose",
 						"image_file_path": "",
-						"image_file": "",
-						"thumbnail_file": "",
+						"image_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914278.dcm",
+						"thumbnail_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914278.webp",
 						"acquisition_mode": "RAD",
 						"acquisition_context": null,
 						"img_proc_context": null,
-						"comment": "",
-						"expose_status": "Unexposed",
-						"expose_time": null,
-						"judged_status": "NotJudged",
+						"comment": "task_version: 0.5.0-7-g3a9cf08",
+						"expose_status": "Exposed",
+						"expose_time": "2025-10-13T03:30:24.191742Z",
+						"judged_status": "Accept",
 						"send_status": "Unsent",
 						"export_status": "NotExported",
-						"storage_status": "NotSaved",
+						"storage_status": "Saved",
+						"raw_exists": true,
 						"ticket": "",
 						"sort": 2,
-						"product": "DROS",
+						"product": "VETDROS",
 						"is_pre_install": true,
-						"create_time": "2025-09-29T08:38:17.362195Z"
+						"create_time": "2025-10-13T03:06:03.024446Z"
 					}
 				]
 			},
 			{
-				"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323788",
-				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
-				"study_id": "250929163817805",
+				"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324763.914279",
+				"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
+				"study_id": "251013110602828",
 				"procedure_id": "",
-				"patient_type": "Human",
-				"body_part": "Human_UPPER LIMB",
+				"patient_type": "Dog",
+				"body_part": "Dog_Hip",
 				"performed_datetime": null,
 				"performed_protocol_code_meaning": "",
 				"performed_protocol_code_value": "",
-				"sort": 2,
-				"product": "DROS",
+				"sort": 3,
+				"product": "VETDROS",
 				"is_pre_install": true,
-				"create_time": "2025-09-29T08:38:17.360412Z",
+				"create_time": "2025-10-13T03:06:03.022034Z",
 				"images": [
 					{
-						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.269954.1759135097.323789",
-						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323788",
-						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+						"sop_instance_uid": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914280",
+						"series_instance_uid": "2.25.156.999999.0000.1.3.8323328.289656.1760324763.914279",
+						"study_instance_uid": "2.25.156.999999.0000.1.2.8323328.289656.1760324762.914274",
 						"secondary_sop_uid": "",
-						"study_id": "250929163817805",
-						"view_id": "View_DX_T_A_HD_PA_02",
-						"view_description": "左手后前位",
-						"patient_type": "Human",
-						"body_part_id": "Human_UPPER LIMB",
-						"anatomic_region": "Hand",
+						"study_id": "251013110602828",
+						"view_id": "View_Dog_PV_VD_01",
+						"view_description": "骨盆伸腿腹背位",
+						"patient_type": "Dog",
+						"body_part_id": "Dog_Hip",
+						"anatomic_region": "Pelvis",
 						"image_type": "expose",
 						"image_file_path": "",
-						"image_file": "",
-						"thumbnail_file": "",
+						"image_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914280.dcm",
+						"thumbnail_file": "2.25.156.999999.0000.1.4.8323328.289656.1760324763.914280.webp",
 						"acquisition_mode": "RAD",
 						"acquisition_context": null,
 						"img_proc_context": null,
-						"comment": "",
-						"expose_status": "Unexposed",
-						"expose_time": null,
-						"judged_status": "NotJudged",
+						"comment": "task_version: 0.5.0-7-g3a9cf08",
+						"expose_status": "Exposed",
+						"expose_time": "2025-10-13T03:31:34.368028Z",
+						"judged_status": "Accept",
 						"send_status": "Unsent",
 						"export_status": "NotExported",
-						"storage_status": "NotSaved",
+						"storage_status": "Saved",
+						"raw_exists": false,
 						"ticket": "",
 						"sort": 3,
-						"product": "DROS",
+						"product": "VETDROS",
 						"is_pre_install": true,
-						"create_time": "2025-09-29T08:38:17.362846Z"
+						"create_time": "2025-10-13T03:06:03.025481Z"
 					}
 				]
 			}
@@ -3081,7 +3126,7 @@ Completed|
 | 参数名称 | 数据类型 | 默认值 | 不为空 | 描述 |
 | ------ | ------ | ------ | ------ | ------ |
 | instance_uid|string|1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671|true|study_id|
-| data|file||true|PNG图片文件|
+| data|string||true|PNG图片base64|
 ### 响应体
 ● 200: OK 响应数据格式:JSON
 ```json

+ 135 - 45
docs/实现/急诊拍照功能.md

@@ -62,7 +62,7 @@
       capturedImage: string | null;       // 捕获的图片(base64)
       isLoading: boolean;                 // 加载状态
       error: string | null;               // 错误信息
-      currentStudyInstanceUid: string | null;  // 当前 Study UID
+      currentStudyId: string | null;      // 当前 Study ID
     }
     ```
   - Actions:
@@ -76,17 +76,85 @@
 
 ### 3. API 层
 
-#### API 操作
-- **cameraActions.ts** *(新建)*
-  - 位置:`src/API/patient/cameraActions.ts`
-  - 方法:
-    - `uploadPatientPhoto(photo: string, studyInstanceUid: string): Promise<UploadPhotoResponse>`
-      - 上传患者照片到后端
-      - 关联照片到当前 Study
-    - `getPatientPhoto(studyInstanceUid: string): Promise<string>`
-      - 获取已保存的患者照片
-    - `deletePatientPhoto(photoId: string): Promise<void>`
-      - 删除患者照片
+#### 已实现:cameraActions.ts
+- **位置**:`src/API/patient/cameraActions.ts`
+- **接口信息**:
+  - **URL**: `POST /api/v1/auth/study/portrait`
+  - **请求格式**: JSON
+  - **Content-Type**: application/json
+
+#### 核心方法
+
+##### uploadPatientPhoto
+```typescript
+uploadPatientPhoto(
+  studyId: string,
+  base64Image: string
+): Promise<UploadPatientPhotoResponse>
+```
+
+**功能**:上传急诊患者照片到后端
+
+**参数**:
+- `studyId`: Study Instance UID(检查实例UID)
+- `base64Image`: PNG 图片的 base64 字符串(必须包含 `data:image/png;base64,` 前缀)
+
+**返回**:
+```typescript
+{
+  code: "0x000000",
+  description: "Success",
+  solution: "",
+  data: {
+    "@type": "type.googleapis.com/dr.task.DcmPath",
+    path: "1.2.276.0.1000000.5.1.5.701601461.33458.1750830395.482043.dcm"
+  }
+}
+```
+
+**关键特性**:
+- ✅ 使用 JSON 格式(非 FormData)
+- ✅ 图片必须是 PNG 格式的 base64
+- ✅ 自动转换为 DICOM 格式保存
+- ✅ 完整的参数验证和错误处理
+- ✅ 返回 DCM 文件路径
+
+**使用示例**:
+```typescript
+import { uploadPatientPhoto } from '@/API/patient/cameraActions';
+
+// 从 Canvas 获取 PNG base64
+const base64Image = canvas.toDataURL('image/png');
+
+// 上传照片
+try {
+  const result = await uploadPatientPhoto(studyInstanceUid, base64Image);
+  console.log('照片已保存:', result.data.path);
+} catch (error) {
+  console.error('上传失败:', error.message);
+}
+```
+
+#### TypeScript 类型定义
+
+```typescript
+// 请求参数
+interface UploadPatientPhotoRequest {
+  instance_uid: string;  // Study Instance UID
+  data: string;          // PNG base64 字符串
+}
+
+// 响应数据
+interface UploadPatientPhotoResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: {
+    '@type': string;
+    path: string;  // DCM 文件路径
+  };
+}
+```
 
 ### 4. Service 层(适配器模式)
 
@@ -347,8 +415,8 @@ interface CameraState {
   // 错误信息
   error: string | null;
   
-  // 当前 Study Instance UID(用于关联照片)
-  currentStudyInstanceUid: string | null;
+  // 当前 Study ID(用于关联照片)
+  currentStudyId: string | null;
 }
 
 // 初始状态
@@ -358,7 +426,7 @@ const initialState: CameraState = {
   capturedImage: null,
   isLoading: false,
   error: null,
-  currentStudyInstanceUid: null,
+  currentStudyId: null,
 };
 ```
 
@@ -518,8 +586,8 @@ interface CameraModalProps {
   // 关闭回调
   onClose: () => void;
   
-  // Study Instance UID
-  studyInstanceUid: string;
+  // Study ID
+  studyId: string;
 }
 
 /**
@@ -567,13 +635,13 @@ interface PhotoPreviewProps {
    - 触发 `handleOpenCamera` 事件处理函数
 
 2. **状态初始化**
-   - 获取当前 `studyInstanceUid`
-   - dispatch `openCamera(studyInstanceUid)` action
+   - 获取当前 `studyId`
+   - dispatch `openCamera(studyId)` action
    - Redux state 更新:
      ```typescript
      {
        isOpen: true,
-       currentStudyInstanceUid: 'xxx',
+       currentStudyId: 'xxx',
        error: null
      }
      ```
@@ -647,17 +715,25 @@ interface PhotoPreviewProps {
       ctx.drawImage(video, 0, 0);
       ```
 
-11. **转换为 base64**
-    - 使用 `canvas.toDataURL()` 转换
-    - 可选择压缩质量(如 0.8
+11. **转换为 base64(PNG 格式)**
+    - 使用 `canvas.toDataURL()` 转换为 PNG 格式
+    - ⚠️ **必须使用 PNG 格式**(接口要求
     ```typescript
-    const base64 = canvas.toDataURL('image/jpeg', 0.8);
+    // ✅ 正确:使用 PNG 格式
+    const base64 = canvas.toDataURL('image/png');
+    
+    // ❌ 错误:不能使用 JPEG
+    // const base64 = canvas.toDataURL('image/jpeg', 0.8);
     ```
 
-12. **可选:图片处理**
-    - 如果图片过大,进行压缩
-    - 如果尺寸不合适,进行缩放
-    - 调用 `imageUtils.compressImage()` 或 `resizeImage()`
+12. **图片验证**
+    - 验证 base64 格式是否正确
+    - 确认包含 `data:image/png;base64,` 前缀
+    ```typescript
+    if (!base64.startsWith('data:image/png;base64,')) {
+      throw new Error('图片必须是 PNG 格式');
+    }
+    ```
 
 13. **更新状态**
     - dispatch `setCapturedImage(base64)`
@@ -683,29 +759,43 @@ interface PhotoPreviewProps {
     - dispatch `setLoading(true)`
     - 显示加载指示器
 
-17. **调用 API**
-    - 调用 `uploadPatientPhoto(base64, studyInstanceUid)`
-    - 发送 HTTP POST 请求:
+17. **调用 API(JSON 格式)**
+    - 调用 `uploadPatientPhoto(studyId, base64Image)`
+    - 发送 HTTP POST 请求(**JSON 格式,非 FormData**)
       ```typescript
-      POST /api/patient/photo
+      POST /api/v1/auth/study/portrait
+      Content-Type: application/json
+      
       {
-        study_instance_uid: 'xxx',
-        photo_data: 'data:image/jpeg;base64,xxx',
-        timestamp: '2025-01-11T10:00:00Z',
-        metadata: {
-          width: 1280,
-          height: 720,
-          format: 'image/jpeg',
-          size: 123456
-        }
+        instance_uid: '20250625143339389',  // study_id
+        data: 'iVBORw0KGgoAAAANSUhEUgA...'  // 纯 base64,不带前缀
       }
       ```
+    
+    - ✅ **关键要点**:
+      - 使用 JSON 格式(不是 multipart/form-data)
+      - instance_uid 字段使用 study_id(不是 study_instance_uid)
+      - data 字段是纯 base64 字符串(**不带 data:image/png;base64, 前缀**)
+      - API 层会自动检测并去除前缀
+      - 后端会自动转换为 DICOM 格式保存
 
 18. **后端处理**
-    - 后端接收照片
-    - 保存到存储系统
-    - 在数据库中创建关联记录
-    - 返回成功响应
+    - 后端接收 PNG base64 数据
+    - 将 PNG 转换为 DICOM 格式
+    - 保存到存储系统(.dcm 文件)
+    - 关联到 Study
+    - 返回 DCM 文件路径:
+      ```typescript
+      {
+        code: "0x000000",
+        description: "Success",
+        solution: "",
+        data: {
+          "@type": "type.googleapis.com/dr.task.DcmPath",
+          path: "2.25.156.999999.0000.1.5.8323328.289656.1760324762.914274.dcm"
+        }
+      }
+      ```
 
 19. **处理响应**
     - 如果成功:

+ 119 - 0
src/API/patient/cameraActions.ts

@@ -0,0 +1,119 @@
+import axiosInstance from '../interceptor';
+
+/**
+ * 上传患者照片请求参数
+ */
+export interface UploadPatientPhotoRequest {
+  /** Study Instance UID */
+  instance_uid: string;
+  /** PNG图片的base64字符串(包含 data:image/png;base64, 前缀) */
+  data: string;
+}
+
+/**
+ * DCM路径响应数据
+ */
+export interface DcmPathData {
+  /** 类型标识 */
+  '@type': string;
+  /** DCM文件路径 */
+  path: string;
+}
+
+/**
+ * 上传患者照片响应
+ */
+export interface UploadPatientPhotoResponse {
+  /** 响应码 */
+  code: string;
+  /** 描述信息 */
+  description: string;
+  /** 解决方案 */
+  solution: string;
+  /** 数据内容 */
+  data: DcmPathData;
+}
+
+/**
+ * 上传急诊患者照片
+ * 
+ * 用于存储拍摄的急诊患者影像,避免患者离开后无法识别。
+ * 照片将被转换为 DICOM 格式保存,并返回文件路径。
+ * 
+ * @param studyId Study Instance UID(检查实例UID)
+ * @param base64Image PNG图片的base64字符串(必须包含 data:image/png;base64, 前缀)
+ * @returns Promise<UploadPatientPhotoResponse> 包含DCM文件路径的响应
+ * @throws 当上传失败时抛出错误
+ * 
+ * @example
+ * ```typescript
+ * // 从 Canvas 获取 base64 图片
+ * const base64Image = canvas.toDataURL('image/png');
+ * 
+ * // 上传患者照片
+ * const result = await uploadPatientPhoto(
+ *   '1.2.276.0.1000000.5.1.2.701601461.33458.1750833219.482097',
+ *   base64Image
+ * );
+ * 
+ * console.log('照片已保存:', result.data.path);
+ * // 输出: 照片已保存: 1.2.276.0.1000000.5.1.5.701601461.33458.1750830395.482043.dcm
+ * ```
+ */
+export const uploadPatientPhoto = async (
+  studyId: string,
+  base64Image: string
+): Promise<UploadPatientPhotoResponse> => {
+  try {
+    // 验证参数
+    if (!studyId || studyId.trim() === '') {
+      throw new Error('Study ID 不能为空');
+    }
+
+    if (!base64Image || base64Image.trim() === '') {
+      throw new Error('图片数据不能为空');
+    }
+
+    // 验证 base64 格式(支持带前缀和不带前缀)
+    const isPNG = base64Image.startsWith('data:image/png;base64,') || 
+                  base64Image.match(/^[A-Za-z0-9+/=]+$/);
+    
+    if (!isPNG) {
+      throw new Error('图片必须是 PNG 格式的 base64 字符串');
+    }
+
+    // 去除 data URL 前缀(如果存在)
+    const pureBase64 = base64Image.replace(/^data:image\/png;base64,/, '');
+
+    // 发送请求(只发送纯 base64,不带前缀)
+    const response = await axiosInstance.post<UploadPatientPhotoResponse>(
+      '/auth/study/portrait',
+      {
+        instance_uid: studyId,
+        data: pureBase64,
+      }
+    );
+
+    // 检查响应码
+    if (response.data.code !== '0x000000') {
+      throw new Error(
+        `上传患者照片失败: ${response.data.description || '未知错误'}`
+      );
+    }
+
+    return response.data;
+  } catch (error: any) {
+    // 统一错误处理
+    console.error('Error uploading patient photo:', error);
+
+    // 如果是 Axios 错误,提取更多信息
+    if (error.response) {
+      const status = error.response.status;
+      const message = error.response.data?.description || error.message;
+      throw new Error(`上传失败 (HTTP ${status}): ${message}`);
+    }
+
+    // 重新抛出错误
+    throw error;
+  }
+};

+ 10 - 8
src/components/CameraModal.tsx

@@ -5,10 +5,11 @@ import { useDispatch, useSelector } from 'react-redux';
 import { RootState } from '@/states/store';
 import { closeCamera, setCapturedImage, setError, setLoading } from '@/states/exam/cameraSlice';
 import { CameraServiceFactory } from '@/services/camera/CameraServiceFactory';
+import { uploadPatientPhoto } from '@/API/patient/cameraActions';
 
 interface CameraModalProps {
   visible: boolean;
-  studyInstanceUid: string;
+  studyId: string;
   onClose: () => void;
 }
 
@@ -18,7 +19,7 @@ interface CameraModalProps {
  */
 const CameraModal: React.FC<CameraModalProps> = ({
   visible,
-  studyInstanceUid,
+  studyId,
   onClose,
 }) => {
   const dispatch = useDispatch();
@@ -204,15 +205,16 @@ const CameraModal: React.FC<CameraModalProps> = ({
       dispatch(setLoading(true));
       
       console.log('[CameraModal] 发送照片到服务器...');
-      console.log('[CameraModal] Study UID:', studyInstanceUid);
+      console.log('[CameraModal] Study ID:', studyId);
+      console.log('[CameraModal] 图片格式:', capturedImage.substring(0, 30) + '...');
       
-      // TODO: 调用 API 上传照片
-      // await uploadPatientPhoto(capturedImage, studyInstanceUid);
+      // 调用 API 上传照片
+      const result = await uploadPatientPhoto(studyId, capturedImage);
       
-      // 模拟上传
-      await new Promise((resolve) => setTimeout(resolve, 1000));
+      console.log('[CameraModal] 照片上传成功');
+      console.log('[CameraModal] DCM 文件路径:', result.data.path);
       
-      message.success('照片发送成功');
+      message.success('照片已保存为 DICOM 格式');
       
       // 关闭 Modal
       handleClose();

+ 9 - 9
src/pages/exam/ContentAreaLarge.tsx

@@ -97,18 +97,18 @@ const ContentAreaLarge = () => {
    * 处理打开摄像头按钮点击事件
    */
   const handleOpenCamera = () => {
-    // 获取当前选中体位的 study_instance_uid
-    const currentStudyInstanceUid = selectedBodyPosition?.study_instance_uid;
+    // 获取当前选中体位的 study_id
+    const currentStudyId = selectedBodyPosition?.study_id;
 
-    if (!currentStudyInstanceUid) {
+    if (!currentStudyId) {
       message.warning('请先选择一个体位');
       return;
     }
 
-    console.log('[handleOpenCamera] 打开摄像头,Study UID:', currentStudyInstanceUid);
+    console.log('[handleOpenCamera] 打开摄像头,Study ID:', currentStudyId);
     
     // dispatch openCamera action,打开摄像头
-    dispatch(openCamera(currentStudyInstanceUid));
+    dispatch(openCamera(currentStudyId));
   };
 
   // 1. 正常在顶层用 useSelector 订阅
@@ -122,8 +122,8 @@ const ContentAreaLarge = () => {
 
   // 3. 订阅 camera 状态
   const cameraIsOpen = useSelector((state: RootState) => state.camera.isOpen);
-  const cameraStudyInstanceUid = useSelector(
-    (state: RootState) => state.camera.currentStudyInstanceUid
+  const cameraStudyId = useSelector(
+    (state: RootState) => state.camera.currentStudyId
   );
 
   return (
@@ -528,10 +528,10 @@ const ContentAreaLarge = () => {
       </Col>
 
       {/* 摄像头 Modal */}
-      {cameraIsOpen && cameraStudyInstanceUid && (
+      {cameraIsOpen && cameraStudyId && (
         <CameraModal
           visible={cameraIsOpen}
-          studyInstanceUid={cameraStudyInstanceUid}
+          studyId={cameraStudyId}
           onClose={() => dispatch(closeCamera())}
         />
       )}

+ 2 - 2
src/services/camera/BrowserCameraService.ts

@@ -114,8 +114,8 @@ export class BrowserCameraService implements ICameraService {
 
                     ctx.drawImage(video, 0, 0);
 
-                    // 转换为 base64
-                    const base64 = canvas.toDataURL('image/jpeg', 0.8);
+                    // 转换为 base64(PNG 格式,符合后端接口要求)
+                    const base64 = canvas.toDataURL('image/png');
 
                     // 清理
                     video.srcObject = null;

+ 1 - 0
src/states/exam/bodyPositionListSlice.ts

@@ -83,6 +83,7 @@ export const transformWorksToBodyPositions = async (
           work: work,
           study_instance_uid: work.StudyInstanceUID,
           sop_instance_uid: view.PrimarySopUID,
+          study_id:work.StudyID,
           dview: view,
         };
       })

+ 6 - 6
src/states/exam/cameraSlice.ts

@@ -14,8 +14,8 @@ export interface CameraState {
   isLoading: boolean;
   /** 错误信息 */
   error: string | null;
-  /** 当前 Study Instance UID */
-  currentStudyInstanceUid: string | null;
+  /** 当前 Study ID */
+  currentStudyId: string | null;
 }
 
 /**
@@ -27,7 +27,7 @@ const initialState: CameraState = {
   capturedImage: null,
   isLoading: false,
   error: null,
-  currentStudyInstanceUid: null,
+  currentStudyId: null,
 };
 
 /**
@@ -40,11 +40,11 @@ const cameraSlice = createSlice({
     /**
      * 打开摄像头
      * @param state 当前状态
-     * @param action 包含 studyInstanceUid 的 action
+     * @param action 包含 studyId 的 action
      */
     openCamera: (state, action: PayloadAction<string>) => {
       state.isOpen = true;
-      state.currentStudyInstanceUid = action.payload;
+      state.currentStudyId = action.payload;
       state.error = null;
     },
 
@@ -110,7 +110,7 @@ const cameraSlice = createSlice({
       state.capturedImage = null;
       state.isLoading = false;
       state.error = null;
-      state.currentStudyInstanceUid = null;
+      state.currentStudyId = null;
     },
   },
 });