# 急诊拍照功能实现方案 ## 一、功能概述 ### 应用场景 用户急诊进入检查,给患者拍照,避免出现患者离开后急诊得到的 study 对应不上哪个患者。 ### 业务流程 用户点击按钮 → 打开摄像头 → 拍照 → 预览确认 → 发送到服务器 ### UI 设计 - **入口按钮**:位于 ContentAreaLarge 组件的工具栏区域 - **摄像界面**:弹出式 Modal 显示 - **界面元素**: - 实时摄像头画面显示区域 - 拍照按钮 - 预览确认区域 - 重拍/发送按钮 --- ## 二、参与者列表(从粗到细) ### 1. UI 层 #### 主要组件 - **ContentAreaLarge.tsx** - 功能:入口组件,包含触发拍照的按钮 - 职责:触发打开摄像头模态框 - **CameraModal.tsx** *(新建)* - 功能:摄像头模态框主组件 - 职责: - 管理模态框的显示/隐藏 - 协调子组件的切换(预览 ↔ 拍照) - 处理摄像头生命周期 - **CameraPreview.tsx** *(新建)* - 功能:摄像头实时预览子组件 - 职责: - 显示实时摄像头画面 - 提供拍照按钮 - 调用拍照功能 - **PhotoPreview.tsx** *(新建)* - 功能:拍照后预览组件 - 职责: - 显示已拍摄的照片 - 提供重拍/发送按钮 - 处理照片发送 ### 2. State 管理层 #### Redux Slice - **cameraSlice.ts** *(新建)* - 位置:`src/states/exam/cameraSlice.ts` - State 结构: ```typescript interface CameraState { isOpen: boolean; // 摄像头是否开启 stream: MediaStream | null; // 媒体流对象 capturedImage: string | null; // 捕获的图片(base64) isLoading: boolean; // 加载状态 error: string | null; // 错误信息 currentStudyId: string | null; // 当前 Study ID } ``` - Actions: - `openCamera()` - 打开摄像头 - `closeCamera()` - 关闭摄像头 - `setStream()` - 设置媒体流 - `setCapturedImage()` - 设置捕获的图片 - `setLoading()` - 设置加载状态 - `setError()` - 设置错误信息 - `resetState()` - 重置状态 ### 3. API 层 #### 已实现: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 ``` **功能**:上传急诊患者照片到后端 **参数**: - `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 层(适配器模式) #### 抽象层 - **CameraService.ts** *(新建)* - 位置:`src/services/camera/CameraService.ts` - 接口定义: ```typescript interface ICameraService { requestPermission(): Promise; getMediaStream(constraints?: MediaStreamConstraints): Promise; capturePhoto(stream: MediaStream): Promise; stopStream(stream: MediaStream): void; } ``` #### 具体实现 - **BrowserCameraService.ts** *(新建)* - 位置:`src/services/camera/BrowserCameraService.ts` - 实现:浏览器环境下的摄像头访问 - 技术:`navigator.mediaDevices.getUserMedia()` - **ElectronCameraService.ts** *(新建)* - 位置:`src/services/camera/ElectronCameraService.ts` - 实现:Electron 环境下的摄像头访问 - 技术:Electron 特定 API + 窗口权限配置 - **CordovaCameraService.ts** *(新建)* - 位置:`src/services/camera/CordovaCameraService.ts` - 实现:Cordova 环境下的摄像头访问 - 技术:Cordova Camera Plugin - **CameraServiceFactory.ts** *(新建)* - 位置:`src/services/camera/CameraServiceFactory.ts` - 职责:根据运行环境创建对应的 CameraService 实例 - 逻辑: ```typescript class CameraServiceFactory { static create(): ICameraService { if (window.cordova) { return new CordovaCameraService(); } else if (window.electronAPI) { return new ElectronCameraService(); } else { return new BrowserCameraService(); } } } ``` ### 5. Domain 层 #### 领域模型 - **patientPhoto.ts** *(新建)* - 位置:`src/domain/patientPhoto.ts` - 模型定义: ```typescript interface PatientPhoto { id: string; studyInstanceUid: string; patientId: string; photoData: string; // base64 timestamp: Date; metadata: PhotoMetadata; } interface PhotoMetadata { width: number; height: number; format: string; size: number; deviceInfo?: string; } ``` ### 6. Utils 层 #### 工具函数 - **imageUtils.ts** *(新建)* - 位置:`src/utils/imageUtils.ts` - 方法: - `base64ToBlob(base64: string): Blob` - base64 转换为 Blob - `compressImage(base64: string, quality: number): Promise` - 图片压缩 - `resizeImage(base64: string, maxWidth: number, maxHeight: number): Promise` - 图片缩放 - `getImageDimensions(base64: string): Promise<{width: number, height: number}>` - 获取图片尺寸 - `validateImageSize(base64: string, maxSize: number): boolean` - 验证图片大小 --- ## 三、交互流程(泳道图) ``` 用户 ContentAreaLarge CameraModal CameraService Redux Store 后端API │ │ │ │ │ │ │──点击摄像头按钮────────>│ │ │ │ │ │ │─dispatch(openCamera)>│ │ │ │ │ │ │ │───────────────────>│ │ │ │ │ │ setIsOpen(true) │ │ │ │ │<────显示Modal──────│ │ │ │ │ │ │ │ │ │ │ │─requestPermission()>│ │ │ │ │ │<─────权限结果──────│ │ │ │ │ │ │ │ │ │ │ │─getMediaStream()──>│ │ │ │ │ │ │─getUserMedia()───>│ │ │ │ │ │ (navigator.mediaDevices) │ │ │ │<─返回MediaStream───│ │ │ │ │ │─dispatch(setStream)>│ │────────────────>│ │ │ │ │ │ stream saved │ │<─────看到实时视频──────────────────────────────│ │ │ │ │ │ │ │ │ │ │──点击拍照按钮────────────────────────────────>│ │ │ │ │ │ │─capturePhoto()────>│ │ │ │ │ │ │─canvas操作────────>│ │ │ │ │ │─toDataURL()───────>│ │ │ │ │<─返回base64────────│ │ │ │ │ │─dispatch(setCapturedImage)>│ │────────────────>│ │ │ │ │ │ image saved │ │<─────看到预览照片──────────────────────────────│ │ │ │ │ │ │ │ │ │ │──点击发送按钮────────────────────────────────>│ │ │ │ │ │ │─dispatch(setLoading)>│ │────────────────>│ │ │ │ │ │ loading=true │ │ │ │─uploadPhoto()──────────────────────────────────────────>│ │ │ │ │ │ │─保存照片 │ │ │ │ │ │─关联Study │ │ │<──────────────────────────────────────────────────────OK│ │ │ │─dispatch(closeCamera)>│ │────────────────>│ │ │ │ │ │ reset state │ │ │ │─stopStream()──────>│ │ │ │ │ │ │─停止MediaStream──>│ │ │<─────提示成功────────────────────────────────│ │ │ │ │ │ │─关闭Modal──────────│ │ │ ``` ### 异常流程 ``` 用户 CameraModal CameraService Redux Store │ │ │ │ │──点击摄像头按钮────>│ │ │ │ │─requestPermission()>│ │ │ │<─────拒绝──────────│ │ │ │─dispatch(setError)>│ │───────────> │<─显示错误提示────────│ │ │ error set │ │ │ │ │──点击重试/取消──────>│ │ │ │ │─关闭Modal或重试────│ │ ``` --- ## 四、数据流 ### 正向数据流(拍照发送) ``` 用户操作 ↓ UI 事件触发 (点击摄像头按钮) ↓ Redux Action (dispatch openCamera) ↓ Redux State 更新 (isOpen: true) ↓ CameraModal 组件渲染 ↓ useEffect 监听 isOpen 变化 ↓ 调用 CameraServiceFactory.create().getMediaStream() ↓ 根据环境选择对应的 Service ├─ 浏览器: BrowserCameraService ├─ Electron: ElectronCameraService └─ Cordova: CordovaCameraService ↓ 获取 MediaStream ↓ 设置到