|
@@ -0,0 +1,960 @@
|
|
|
+# 检查流程中的图像状态控制功能实现文档
|
|
|
+
|
|
|
+## 📋 功能概述
|
|
|
+
|
|
|
+在检查流程(exam 模块)中实现"恢复/拒绝/保存参数"功能,允许用户对已曝光的图像进行判断操作,对未曝光的体位进行参数保存。
|
|
|
+
|
|
|
+**实现日期**: 2025-01-14
|
|
|
+**版本**: v1.0.0
|
|
|
+
|
|
|
+## 🎯 需求分析
|
|
|
+
|
|
|
+### 功能需求
|
|
|
+
|
|
|
+1. **拒绝按钮**
|
|
|
+ - 将已曝光且状态为接受的图像标记为拒绝状态
|
|
|
+ - 仅在选中体位已曝光且 judged_status 为 'Accept' 或 '' 时显示
|
|
|
+
|
|
|
+2. **恢复按钮**
|
|
|
+ - 将已拒绝的图像恢复为接受状态
|
|
|
+ - 仅在选中体位已曝光且 judged_status 为 'Reject' 时显示
|
|
|
+ - 与拒绝按钮占据同一位置(互斥显示)
|
|
|
+
|
|
|
+3. **保存参数按钮**
|
|
|
+ - 保存当前 APR 参数到未曝光的体位
|
|
|
+ - 仅在选中体位未曝光时显示
|
|
|
+ - **注意**: 当前 API 暂未提供,使用 TODO 标记
|
|
|
+
|
|
|
+4. **删除按钮可见性调整**
|
|
|
+ - 当选中体位已曝光时,隐藏"删除选择的体位"按钮
|
|
|
+ - 防止误删已曝光的图像
|
|
|
+
|
|
|
+### 显示逻辑表
|
|
|
+
|
|
|
+| 体位状态 | expose_status | judged_status | 拒绝按钮 | 恢复按钮 | 保存参数 | 删除按钮 |
|
|
|
+|---------|---------------|---------------|---------|---------|---------|---------|
|
|
|
+| 未曝光 | Unexposed | - | 隐藏 | 隐藏 | **显示** | 显示 |
|
|
|
+| 已曝光-接受 | Exposed | Accept | **显示** | 隐藏 | 隐藏 | **隐藏** |
|
|
|
+| 已曝光-拒绝 | Exposed | Reject | 隐藏 | **显示** | 隐藏 | **隐藏** |
|
|
|
+| 已曝光-未判断 | Exposed | '' | **显示** | 隐藏 | 隐藏 | **隐藏** |
|
|
|
+
|
|
|
+## 🏗️ 架构设计
|
|
|
+
|
|
|
+### 参与者列表
|
|
|
+
|
|
|
+#### 1. UI 层
|
|
|
+- **ContentAreaLarge.tsx** - 主容器组件,包含所有控制按钮
|
|
|
+ - 添加处理函数:`handleReject()`, `handleRestore()`, `handleSaveParams()`
|
|
|
+ - 计算按钮可见性逻辑
|
|
|
+ - 管理按钮的显示/隐藏
|
|
|
+
|
|
|
+- **BodyPositionList.tsx** - 体位列表组件
|
|
|
+ - 提供选中体位信息
|
|
|
+ - 显示体位的曝光和判断状态徽章
|
|
|
+
|
|
|
+#### 2. 状态管理层
|
|
|
+- **bodyPositionListSlice.ts** - 体位列表状态管理
|
|
|
+ - `selectedBodyPosition` - 当前选中的体位
|
|
|
+ - `judgeImageThunk` - 判断图像的异步 Thunk
|
|
|
+ - `loading` - 加载状态
|
|
|
+
|
|
|
+ 关键 Reducer:
|
|
|
+ - `judgeImageThunk.fulfilled` - 更新 judged_status
|
|
|
+ - `judgeImageThunk.rejected` - 处理错误
|
|
|
+
|
|
|
+- **aprSlice.ts** - APR 参数状态管理
|
|
|
+ - `aprConfig` - 当前 APR 参数配置(用于保存参数功能)
|
|
|
+
|
|
|
+#### 3. 业务逻辑层
|
|
|
+- **paraSettingCoordinator.ts** - APR 参数协调器
|
|
|
+ - 协调设备参数的增减操作
|
|
|
+ - (保存参数功能待实现时会用到)
|
|
|
+
|
|
|
+#### 4. API 层
|
|
|
+- **judgeImage.ts** - 图像判断 API
|
|
|
+ - `judgeImage(instanceUid, accept)` - 接受/拒绝图像
|
|
|
+ - 端点: `POST /auth/task/inspection/judge`
|
|
|
+ - 请求参数:
|
|
|
+ ```typescript
|
|
|
+ {
|
|
|
+ instance_uid: string; // SOP 实例 UID
|
|
|
+ accept: boolean; // true: 接受, false: 拒绝
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+#### 5. 数据模型
|
|
|
+- **ExtendedBodyPosition** - 扩展体位信息
|
|
|
+ ```typescript
|
|
|
+ interface ExtendedBodyPosition {
|
|
|
+ sop_instance_uid: string;
|
|
|
+ dview: {
|
|
|
+ expose_status: 'Exposed' | 'Unexposed';
|
|
|
+ judged_status: 'Accept' | 'Reject' | '';
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+ // ... 其他字段
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+### 组件交互序列图
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant User as 👤 用户
|
|
|
+ participant UI as ContentAreaLarge
|
|
|
+ participant Redux as Redux Store
|
|
|
+ participant API as judgeImage API
|
|
|
+
|
|
|
+ Note over User,API: 场景1: 拒绝图像
|
|
|
+ User->>UI: 点击拒绝按钮
|
|
|
+ UI->>UI: 检查 selectedBodyPosition 是否存在
|
|
|
+ UI->>Redux: dispatch(judgeImageThunk({sopInstanceUid, accept: false}))
|
|
|
+ Redux->>Redux: judgeImageThunk.pending
|
|
|
+ Redux->>UI: loading = true
|
|
|
+ UI->>User: 按钮显示 loading 状态
|
|
|
+ Redux->>API: POST /auth/task/inspection/judge
|
|
|
+ API-->>Redux: 返回成功 {code: '0x000000'}
|
|
|
+ Redux->>Redux: judgeImageThunk.fulfilled
|
|
|
+ Redux->>Redux: 更新 bodyPositions[i].dview.judged_status = 'Reject'
|
|
|
+ Redux->>Redux: 更新 selectedBodyPosition.dview.judged_status = 'Reject'
|
|
|
+ Redux-->>UI: 状态更新, loading = false
|
|
|
+ UI->>UI: 重新计算按钮可见性
|
|
|
+ UI-->>User: 显示恢复按钮(隐藏拒绝按钮)
|
|
|
+ UI-->>User: 显示成功消息
|
|
|
+
|
|
|
+ Note over User,API: 场景2: 恢复图像
|
|
|
+ User->>UI: 点击恢复按钮
|
|
|
+ UI->>Redux: dispatch(judgeImageThunk({sopInstanceUid, accept: true}))
|
|
|
+ Redux->>API: POST /auth/task/inspection/judge
|
|
|
+ API-->>Redux: 返回成功
|
|
|
+ Redux->>Redux: 更新 judged_status = 'Accept'
|
|
|
+ Redux-->>UI: 状态更新
|
|
|
+ UI-->>User: 显示拒绝按钮(隐藏恢复按钮)
|
|
|
+ UI-->>User: 显示成功消息
|
|
|
+
|
|
|
+ Note over User,API: 场景3: 保存参数(待实现)
|
|
|
+ User->>UI: 点击保存参数按钮
|
|
|
+ UI->>UI: 读取当前 APR 参数
|
|
|
+ UI->>UI: TODO: 调用保存参数 API
|
|
|
+ UI-->>User: 显示"保存参数功能开发中"
|
|
|
+```
|
|
|
+
|
|
|
+### 数据流图
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Start[用户操作] --> Check{检查操作类型}
|
|
|
+
|
|
|
+ Check -->|拒绝| Reject[handleReject]
|
|
|
+ Check -->|恢复| Restore[handleRestore]
|
|
|
+ Check -->|保存参数| Save[handleSaveParams]
|
|
|
+
|
|
|
+ Reject --> ValidateReject{检查体位存在?}
|
|
|
+ ValidateReject -->|否| WarnReject[显示警告: 请先选择一个体位]
|
|
|
+ ValidateReject -->|是| DispatchReject[dispatch judgeImageThunk accept=false]
|
|
|
+
|
|
|
+ Restore --> ValidateRestore{检查体位存在?}
|
|
|
+ ValidateRestore -->|否| WarnRestore[显示警告: 请先选择一个体位]
|
|
|
+ ValidateRestore -->|是| DispatchRestore[dispatch judgeImageThunk accept=true]
|
|
|
+
|
|
|
+ DispatchReject --> APICall[调用 judgeImage API]
|
|
|
+ DispatchRestore --> APICall
|
|
|
+
|
|
|
+ APICall --> APIResult{API 返回}
|
|
|
+ APIResult -->|成功| UpdateState[更新 Redux 状态]
|
|
|
+ APIResult -->|失败| ShowError[显示错误消息]
|
|
|
+
|
|
|
+ UpdateState --> UpdatePositions[更新 bodyPositions 中的 judged_status]
|
|
|
+ UpdatePositions --> UpdateSelected[更新 selectedBodyPosition.judged_status]
|
|
|
+ UpdateSelected --> Rerender[触发 UI 重新渲染]
|
|
|
+
|
|
|
+ Rerender --> CalcVisibility[重新计算按钮可见性]
|
|
|
+ CalcVisibility --> UpdateButtons[更新按钮显示/隐藏]
|
|
|
+ UpdateButtons --> ShowSuccess[显示成功消息]
|
|
|
+
|
|
|
+ Save --> ValidateSave{检查体位存在?}
|
|
|
+ ValidateSave -->|否| WarnSave[显示警告: 请先选择一个体位]
|
|
|
+ ValidateSave -->|是| TODOSave[TODO: 调用保存参数 API]
|
|
|
+ TODOSave --> InfoSave[显示: 保存参数功能开发中]
|
|
|
+
|
|
|
+ WarnReject --> End[结束]
|
|
|
+ WarnRestore --> End
|
|
|
+ WarnSave --> End
|
|
|
+ ShowError --> End
|
|
|
+ ShowSuccess --> End
|
|
|
+ InfoSave --> End
|
|
|
+```
|
|
|
+
|
|
|
+### 按钮可见性计算流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Start[获取 selectedBodyPosition] --> CheckNull{体位是否存在?}
|
|
|
+
|
|
|
+ CheckNull -->|否| HideAll[隐藏所有按钮]
|
|
|
+ CheckNull -->|是| GetStatus[获取 expose_status 和 judged_status]
|
|
|
+
|
|
|
+ GetStatus --> CheckExpose{expose_status?}
|
|
|
+
|
|
|
+ CheckExpose -->|Unexposed| ShowUnexposed[显示: 保存参数, 删除]
|
|
|
+ ShowUnexposed --> HideUnexposed[隐藏: 拒绝, 恢复]
|
|
|
+
|
|
|
+ CheckExpose -->|Exposed| CheckJudged{judged_status?}
|
|
|
+
|
|
|
+ CheckJudged -->|'Reject'| ShowRestore[显示: 恢复]
|
|
|
+ ShowRestore --> HideRestore[隐藏: 拒绝, 保存参数, 删除]
|
|
|
+
|
|
|
+ CheckJudged -->|'Accept' 或 ''| ShowReject[显示: 拒绝]
|
|
|
+ ShowReject --> HideReject[隐藏: 恢复, 保存参数, 删除]
|
|
|
+
|
|
|
+ HideAll --> End[结束]
|
|
|
+ HideUnexposed --> End
|
|
|
+ HideRestore --> End
|
|
|
+ HideReject --> End
|
|
|
+```
|
|
|
+
|
|
|
+## 📊 数据结构
|
|
|
+
|
|
|
+### ExtendedBodyPosition 接口
|
|
|
+
|
|
|
+```typescript
|
|
|
+interface ExtendedBodyPosition {
|
|
|
+ // 基本信息
|
|
|
+ patient_name: string;
|
|
|
+ patient_id: string;
|
|
|
+ registration_number: string;
|
|
|
+ study_description: string;
|
|
|
+
|
|
|
+ // 体位信息
|
|
|
+ view_name: string;
|
|
|
+ view_description: string;
|
|
|
+ view_icon_name: string;
|
|
|
+ body_position_image: string;
|
|
|
+
|
|
|
+ // 唯一标识符
|
|
|
+ sop_instance_uid: string;
|
|
|
+ series_instance_uid?: string;
|
|
|
+ study_instance_uid?: string;
|
|
|
+ study_id?: string;
|
|
|
+
|
|
|
+ // 核心状态对象
|
|
|
+ dview: {
|
|
|
+ expose_status: 'Exposed' | 'Unexposed'; // 曝光状态
|
|
|
+ judged_status: 'Accept' | 'Reject' | ''; // 判断状态
|
|
|
+ PrimarySopUID: string;
|
|
|
+ thumbnail_file: string;
|
|
|
+ image_file: string;
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+
|
|
|
+ // 其他配置
|
|
|
+ collimator_length: number | string;
|
|
|
+ collimator_width: number | string;
|
|
|
+ sid: string;
|
|
|
+ work: Work;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 按钮可见性状态
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在 ContentAreaLarge 组件中计算
|
|
|
+const exposeStatus = selectedBodyPosition?.dview.expose_status;
|
|
|
+const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
|
|
|
+
|
|
|
+const isExposed = exposeStatus === 'Exposed';
|
|
|
+const isUnexposed = exposeStatus === 'Unexposed';
|
|
|
+
|
|
|
+// 按钮可见性
|
|
|
+const showRejectButton = isExposed && judgedStatus !== 'Reject';
|
|
|
+const showRestoreButton = isExposed && judgedStatus === 'Reject';
|
|
|
+const showSaveParamsButton = isUnexposed;
|
|
|
+const showDeleteButton = isUnexposed;
|
|
|
+```
|
|
|
+
|
|
|
+### API 请求/响应结构
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 请求
|
|
|
+interface JudgeImageRequest {
|
|
|
+ instance_uid: string; // SOP 实例 UID
|
|
|
+ accept: boolean; // true: 接受, false: 拒绝
|
|
|
+}
|
|
|
+
|
|
|
+// 响应
|
|
|
+interface JudgeImageResponse {
|
|
|
+ code: string; // '0x000000' 表示成功
|
|
|
+ description: string;
|
|
|
+ solution: string;
|
|
|
+ data: {
|
|
|
+ '@type': string;
|
|
|
+ value: object;
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 🔄 执行流程
|
|
|
+
|
|
|
+### 用户操作起点
|
|
|
+
|
|
|
+#### 1. 进入检查流程
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Start([用户进入检查流程]) --> LoadPositions[加载体位列表]
|
|
|
+ LoadPositions --> AutoSelect[自动选中第一个未曝光体位]
|
|
|
+ AutoSelect --> GetStatus[获取体位状态信息]
|
|
|
+ GetStatus --> CalcVisibility[计算按钮可见性]
|
|
|
+ CalcVisibility --> RenderUI[渲染 UI]
|
|
|
+ RenderUI --> WaitUser[等待用户操作]
|
|
|
+```
|
|
|
+
|
|
|
+#### 2. 切换体位
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Click([用户点击体位]) --> UpdateSelected[更新 selectedBodyPosition]
|
|
|
+ UpdateSelected --> GetNewStatus[获取新体位状态]
|
|
|
+ GetNewStatus --> RecalcVisibility[重新计算按钮可见性]
|
|
|
+ RecalcVisibility --> ReRender[重新渲染按钮区域]
|
|
|
+ ReRender --> Complete([完成])
|
|
|
+```
|
|
|
+
|
|
|
+#### 3. 拒绝图像完整流程
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Start([用户点击拒绝按钮]) --> Check{selectedBodyPosition 存在?}
|
|
|
+ Check -->|否| Warning[显示警告消息]
|
|
|
+ Check -->|是| Dispatch[dispatch judgeImageThunk]
|
|
|
+
|
|
|
+ Dispatch --> Pending[judgeImageThunk.pending]
|
|
|
+ Pending --> SetLoading[设置 loading = true]
|
|
|
+ SetLoading --> ShowLoading[按钮显示 loading 状态]
|
|
|
+
|
|
|
+ ShowLoading --> CallAPI[调用 POST /auth/task/inspection/judge]
|
|
|
+ CallAPI --> CheckResponse{API 响应}
|
|
|
+
|
|
|
+ CheckResponse -->|成功| Fulfilled[judgeImageThunk.fulfilled]
|
|
|
+ CheckResponse -->|失败| Rejected[judgeImageThunk.rejected]
|
|
|
+
|
|
|
+ Fulfilled --> UpdateJudged[更新 judged_status = 'Reject']
|
|
|
+ UpdateJudged --> UpdateList[更新 bodyPositions 列表]
|
|
|
+ UpdateList --> UpdateSelection[更新 selectedBodyPosition]
|
|
|
+ UpdateSelection --> ClearLoading[设置 loading = false]
|
|
|
+ ClearLoading --> Rerender[触发 UI 重新渲染]
|
|
|
+ Rerender --> HideReject[隐藏拒绝按钮]
|
|
|
+ HideReject --> ShowRestore[显示恢复按钮]
|
|
|
+ ShowRestore --> Success[显示成功消息]
|
|
|
+
|
|
|
+ Rejected --> SetError[设置 error 信息]
|
|
|
+ SetError --> ClearLoadingError[设置 loading = false]
|
|
|
+ ClearLoadingError --> ErrorMsg[显示错误消息]
|
|
|
+
|
|
|
+ Warning --> End([结束])
|
|
|
+ Success --> End
|
|
|
+ ErrorMsg --> End
|
|
|
+```
|
|
|
+
|
|
|
+#### 4. 恢复图像流程
|
|
|
+与拒绝流程类似,但参数 `accept: true`,结果是 `judged_status = 'Accept'`
|
|
|
+
|
|
|
+#### 5. 保存参数流程(待实现)
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Start([用户点击保存参数按钮]) --> Check{selectedBodyPosition 存在?}
|
|
|
+ Check -->|否| Warning[显示警告消息]
|
|
|
+ Check -->|是| ReadAPR[读取当前 APR 配置]
|
|
|
+ ReadAPR --> LogInfo[记录日志]
|
|
|
+ LogInfo --> TODO[TODO: 调用保存参数 API]
|
|
|
+ TODO --> ShowInfo[显示: 保存参数功能开发中]
|
|
|
+
|
|
|
+ Warning --> End([结束])
|
|
|
+ ShowInfo --> End
|
|
|
+```
|
|
|
+
|
|
|
+## 📝 实现清单
|
|
|
+
|
|
|
+### 已修改的文件
|
|
|
+
|
|
|
+#### 1. src/pages/exam/ContentAreaLarge.tsx
|
|
|
+
|
|
|
+**新增导入**:
|
|
|
+```typescript
|
|
|
+import {
|
|
|
+ removeBodyPositionBySopInstanceUid,
|
|
|
+ setByIndex,
|
|
|
+ judgeImageThunk, // 新增
|
|
|
+} from '../../states/exam/bodyPositionListSlice';
|
|
|
+```
|
|
|
+
|
|
|
+**新增处理函数**:
|
|
|
+
|
|
|
+1. **handleReject()** - 拒绝图像
|
|
|
+ ```typescript
|
|
|
+ const handleReject = async () => {
|
|
|
+ if (!selectedBodyPosition) {
|
|
|
+ message.warning('请先选择一个体位');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await dispatch(
|
|
|
+ judgeImageThunk({
|
|
|
+ sopInstanceUid: selectedBodyPosition.sop_instance_uid,
|
|
|
+ accept: false,
|
|
|
+ })
|
|
|
+ ).unwrap();
|
|
|
+
|
|
|
+ message.success('图像已拒绝');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('拒绝图像失败:', err);
|
|
|
+ message.error('拒绝图像失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **handleRestore()** - 恢复图像
|
|
|
+ ```typescript
|
|
|
+ const handleRestore = async () => {
|
|
|
+ if (!selectedBodyPosition) {
|
|
|
+ message.warning('请先选择一个体位');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await dispatch(
|
|
|
+ judgeImageThunk({
|
|
|
+ sopInstanceUid: selectedBodyPosition.sop_instance_uid,
|
|
|
+ accept: true,
|
|
|
+ })
|
|
|
+ ).unwrap();
|
|
|
+
|
|
|
+ message.success('图像已恢复');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('恢复图像失败:', err);
|
|
|
+ message.error('恢复图像失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+3. **handleSaveParams()** - 保存参数(待实现)
|
|
|
+ ```typescript
|
|
|
+ const handleSaveParams = async () => {
|
|
|
+ if (!selectedBodyPosition) {
|
|
|
+ message.warning('请先选择一个体位');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // TODO: 实现保存参数 API 调用
|
|
|
+ console.log('保存参数功能待实现,当前 APR 配置:', aprConfig);
|
|
|
+ console.log('选中体位:', selectedBodyPosition.sop_instance_uid);
|
|
|
+
|
|
|
+ message.info('保存参数功能开发中');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('保存参数失败:', err);
|
|
|
+ message.error('保存参数失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+**新增状态订阅**:
|
|
|
+```typescript
|
|
|
+// 4. 计算按钮可见性
|
|
|
+const loading = useSelector(
|
|
|
+ (state: RootState) => state.bodyPositionList.loading
|
|
|
+);
|
|
|
+const exposeStatus = selectedBodyPosition?.dview.expose_status;
|
|
|
+const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
|
|
|
+
|
|
|
+const isExposed = exposeStatus === 'Exposed';
|
|
|
+const isUnexposed = exposeStatus === 'Unexposed';
|
|
|
+
|
|
|
+// 按钮可见性逻辑
|
|
|
+const showRejectButton = isExposed && judgedStatus !== 'Reject';
|
|
|
+const showRestoreButton = isExposed && judgedStatus === 'Reject';
|
|
|
+const showSaveParamsButton = isUnexposed;
|
|
|
+const showDeleteButton = isUnexposed;
|
|
|
+```
|
|
|
+
|
|
|
+**修改的按钮渲染**:
|
|
|
+
|
|
|
+1. 删除按钮 - 添加条件渲染
|
|
|
+ ```typescript
|
|
|
+ {showDeleteButton && (
|
|
|
+ <Tooltip title="删除选择的体位">
|
|
|
+ <Button ... />
|
|
|
+ </Tooltip>
|
|
|
+ )}
|
|
|
+ ```
|
|
|
+
|
|
|
+2. 保存参数按钮 - 添加条件渲染和点击处理
|
|
|
+ ```typescript
|
|
|
+ {showSaveParamsButton && (
|
|
|
+ <Tooltip title="保存参数">
|
|
|
+ <Button onClick={handleSaveParams} ... />
|
|
|
+ </Tooltip>
|
|
|
+ )}
|
|
|
+ ```
|
|
|
+
|
|
|
+3. 拒绝按钮 - 添加条件渲染、loading 状态和点击处理
|
|
|
+ ```typescript
|
|
|
+ {showRejectButton && (
|
|
|
+ <Tooltip title="拒绝">
|
|
|
+ <Button
|
|
|
+ loading={loading}
|
|
|
+ onClick={handleReject}
|
|
|
+ ...
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ )}
|
|
|
+ ```
|
|
|
+
|
|
|
+4. 恢复按钮 - 新增(与拒绝按钮互斥)
|
|
|
+ ```typescript
|
|
|
+ {showRestoreButton && (
|
|
|
+ <Tooltip title="恢复">
|
|
|
+ <Button
|
|
|
+ loading={loading}
|
|
|
+ onClick={handleRestore}
|
|
|
+ icon={<Icon name="btn_RestoreImage" ... />}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ )}
|
|
|
+ ```
|
|
|
+
|
|
|
+### 未修改的文件
|
|
|
+
|
|
|
+以下文件已经具备所需功能,无需修改:
|
|
|
+
|
|
|
+- **src/states/exam/bodyPositionListSlice.ts** - `judgeImageThunk` 已存在
|
|
|
+- **src/API/exam/judgeImage.ts** - API 已就绪
|
|
|
+- **src/pages/exam/components/BodyPositionList.tsx** - 体位列表无需修改
|
|
|
+- **src/domain/exam/bodyPositionSelection.ts** - 体位选择逻辑无需修改
|
|
|
+
|
|
|
+## 🧪 测试方案
|
|
|
+
|
|
|
+### 功能测试用例
|
|
|
+
|
|
|
+#### 1. 按钮显示逻辑测试
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-001 | 未曝光体位 | 选中未曝光体位 | 查看按钮区域 | 显示:保存参数、删除<br>隐藏:拒绝、恢复 |
|
|
|
+| TC-002 | 已曝光-接受 | 选中已曝光且接受的体位 | 查看按钮区域 | 显示:拒绝<br>隐藏:恢复、保存参数、删除 |
|
|
|
+| TC-003 | 已曝光-拒绝 | 选中已曝光且拒绝的体位 | 查看按钮区域 | 显示:恢复<br>隐藏:拒绝、保存参数、删除 |
|
|
|
+| TC-004 | 已曝光-未判断 | 选中已曝光但未判断的体位 | 查看按钮区域 | 显示:拒绝<br>隐藏:恢复、保存参数、删除 |
|
|
|
+| TC-005 | 无选中体位 | 清空选中状态 | 查看按钮区域 | 所有判断相关按钮隐藏 |
|
|
|
+
|
|
|
+#### 2. 拒绝功能测试
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-101 | 拒绝成功 | 选中已曝光-接受体位 | 1. 点击拒绝按钮<br>2. 等待响应 | 1. 按钮显示 loading<br>2. API 调用成功<br>3. judged_status 更新为 'Reject'<br>4. 显示恢复按钮<br>5. 显示"图像已拒绝" |
|
|
|
+| TC-102 | 拒绝失败 | 选中已曝光体位,模拟 API 错误 | 点击拒绝按钮 | 1. 显示错误消息<br>2. 状态不变<br>3. 按钮显示不变 |
|
|
|
+| TC-103 | 无选中体位 | 未选中任何体位 | 点击拒绝按钮(如果可见) | 显示警告"请先选择一个体位" |
|
|
|
+| TC-104 | 加载状态 | 选中已曝光体位 | 点击拒绝按钮,立即再次点击 | 第二次点击无效(按钮 disabled) |
|
|
|
+
|
|
|
+#### 3. 恢复功能测试
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-201 | 恢复成功 | 选中已曝光-拒绝体位 | 1. 点击恢复按钮<br>2. 等待响应 | 1. 按钮显示 loading<br>2. API 调用成功<br>3. judged_status 更新为 'Accept'<br>4. 显示拒绝按钮<br>5. 显示"图像已恢复" |
|
|
|
+| TC-202 | 恢复失败 | 选中已拒绝体位,模拟 API 错误 | 点击恢复按钮 | 1. 显示错误消息<br>2. 状态不变 |
|
|
|
+| TC-203 | 拒绝后恢复 | 选中已曝光-接受体位 | 1. 点击拒绝<br>2. 等待成功<br>3. 点击恢复 | 最终状态恢复为接受 |
|
|
|
+
|
|
|
+#### 4. 保存参数功能测试(当前版本)
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-301 | 点击保存参数 | 选中未曝光体位 | 点击保存参数按钮 | 1. 控制台输出 APR 配置<br>2. 显示"保存参数功能开发中" |
|
|
|
+| TC-302 | 无选中体位 | 未选中任何体位 | 点击保存参数按钮(如果可见) | 显示警告"请先选择一个体位" |
|
|
|
+
|
|
|
+#### 5. 删除按钮可见性测试
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-401 | 未曝光体位 | 选中未曝光体位 | 查看删除按钮 | 删除按钮可见 |
|
|
|
+| TC-402 | 已曝光体位 | 选中已曝光体位 | 查看删除按钮 | 删除按钮隐藏 |
|
|
|
+
|
|
|
+#### 6. 体位切换测试
|
|
|
+
|
|
|
+| 用例编号 | 测试场景 | 前置条件 | 操作步骤 | 期望结果 |
|
|
|
+|---------|---------|---------|---------|---------|
|
|
|
+| TC-501 | 未曝光→已曝光 | 当前选中未曝光体位 | 点击已曝光-接受体位 | 按钮更新:显示拒绝,隐藏保存参数和删除 |
|
|
|
+| TC-502 | 已曝光接受→已曝光拒绝 | 当前选中已曝光-接受体位 | 点击已曝光-拒绝体位 | 按钮更新:显示恢复,隐藏拒绝 |
|
|
|
+| TC-503 | 已曝光拒绝→未曝光 | 当前选中已曝光-拒绝体位 | 点击未曝光体位 | 按钮更新:显示保存参数和删除,隐藏恢复 |
|
|
|
+
|
|
|
+### 集成测试场景
|
|
|
+
|
|
|
+#### 场景 1: 完整工作流
|
|
|
+```
|
|
|
+进入检查流程
|
|
|
+→ 选中未曝光体位(显示保存参数、删除)
|
|
|
+→ 进行曝光(MQTT 更新状态)
|
|
|
+→ 体位变为已曝光-未判断(显示拒绝,隐藏保存参数、删除)
|
|
|
+→ 点击拒绝(显示恢复,隐藏拒绝)
|
|
|
+→ 点击恢复(显示拒绝,隐藏恢复)
|
|
|
+→ 退出
|
|
|
+```
|
|
|
+
|
|
|
+#### 场景 2: 多体位混合状态
|
|
|
+```
|
|
|
+进入检查流程
|
|
|
+→ 体位列表包含:2个未曝光,2个已曝光-接受,1个已曝光-拒绝
|
|
|
+→ 依次点击每个体位
|
|
|
+→ 验证每个体位的按钮显示正确
|
|
|
+→ 对已曝光体位进行拒绝/恢复操作
|
|
|
+→ 验证状态更新正确
|
|
|
+```
|
|
|
+
|
|
|
+#### 场景 3: 边界条件
|
|
|
+- 只有1个体位(未曝光)
|
|
|
+- 只有1个体位(已曝光)
|
|
|
+- 所有体位已曝光
|
|
|
+- 所有体位未曝光
|
|
|
+
|
|
|
+### 性能测试
|
|
|
+
|
|
|
+| 测试场景 | 操作 | 性能指标 |
|
|
|
+|---------|------|---------|
|
|
|
+| 按钮切换响应 | 快速点击不同体位 | UI 更新延迟 < 100ms |
|
|
|
+| API 响应时间 | 执行拒绝/恢复操作 | 总耗时 < 2s |
|
|
|
+| 并发操作 | 在 loading 期间尝试其他操作 | 按钮正确禁用,无重复请求 |
|
|
|
+
|
|
|
+## 🐛 潜在问题与异常处理
|
|
|
+
|
|
|
+### 1. 边界情况处理
|
|
|
+
|
|
|
+#### 问题 1: selectedBodyPosition 为 null
|
|
|
+- **场景**: 体位列表为空或未选中任何体位
|
|
|
+- **影响**: 访问 `selectedBodyPosition.dview` 会导致运行时错误
|
|
|
+- **解决方案**:
|
|
|
+ ```typescript
|
|
|
+ const exposeStatus = selectedBodyPosition?.dview.expose_status;
|
|
|
+ const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
|
|
|
+ ```
|
|
|
+ 使用可选链操作符 `?.` 和默认值 `|| ''`
|
|
|
+
|
|
|
+#### 问题 2: judged_status 未定义
|
|
|
+- **场景**: 旧数据或新创建的体位可能没有 `judged_status` 字段
|
|
|
+- **影响**: 按钮可见性判断可能出错
|
|
|
+- **解决方案**:
|
|
|
+ ```typescript
|
|
|
+ const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
|
|
|
+ ```
|
|
|
+ 提供空字符串作为默认值
|
|
|
+
|
|
|
+#### 问题 3: 只有一个体位且已曝光
|
|
|
+- **场景**: 列表中只有一个已曝光的体位
|
|
|
+- **影响**: 用户无法删除该体位(删除按钮被隐藏)
|
|
|
+- **处理**: 这是预期行为,已曝光的体位不应被删除,保护已采集的数据
|
|
|
+
|
|
|
+#### 问题 4: expose_status 状态不一致
|
|
|
+- **场景**: 前端状态与后端状态不同步
|
|
|
+- **影响**: 按钮显示可能不正确
|
|
|
+- **解决方案**: 依赖 MQTT 事件自动更新状态,确保状态同步
|
|
|
+
|
|
|
+### 2. API 调用异常处理
|
|
|
+
|
|
|
+#### 网络错误
|
|
|
+- **错误类型**:
|
|
|
+ - 连接超时
|
|
|
+ - 网络中断
|
|
|
+ - DNS 解析失败
|
|
|
+- **处理方案**:
|
|
|
+ ```typescript
|
|
|
+ try {
|
|
|
+ await dispatch(judgeImageThunk(...)).unwrap();
|
|
|
+ message.success('操作成功');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('操作失败:', err);
|
|
|
+ message.error('操作失败,请检查网络连接');
|
|
|
+ }
|
|
|
+ ```
|
|
|
+- **用户反馈**: 显示友好的错误消息,建议用户检查网络
|
|
|
+
|
|
|
+#### 服务器错误
|
|
|
+- **错误类型**:
|
|
|
+ - 500 Internal Server Error
|
|
|
+ - 503 Service Unavailable
|
|
|
+- **处理方案**:
|
|
|
+ - 捕获错误并显示通用错误消息
|
|
|
+ - 记录详细错误日志供调试
|
|
|
+ - 不修改当前 UI 状态
|
|
|
+
|
|
|
+#### API 返回非成功码
|
|
|
+- **场景**: `response.code !== '0x000000'`
|
|
|
+- **处理**: judgeImage API 内部已处理,会抛出错误
|
|
|
+- **结果**: 在 catch 块中捕获并显示错误消息
|
|
|
+
|
|
|
+### 3. 状态一致性问题
|
|
|
+
|
|
|
+#### Redux 状态与后端不同步
|
|
|
+- **场景**:
|
|
|
+ 1. API 调用成功但 Redux 状态未更新
|
|
|
+ 2. 多个客户端同时操作同一体位
|
|
|
+- **解决方案**:
|
|
|
+ 1. 使用 `judgeImageThunk.fulfilled` 确保 API 成功后才更新状态
|
|
|
+ 2. 失败时保持原状态不变
|
|
|
+ 3. 考虑添加刷新功能重新拉取最新数据
|
|
|
+
|
|
|
+#### 并发操作冲突
|
|
|
+- **场景**: 用户在 loading 期间快速点击其他按钮
|
|
|
+- **解决方案**:
|
|
|
+ ```typescript
|
|
|
+ <Button loading={loading} ... />
|
|
|
+ ```
|
|
|
+ - 使用 `loading` 属性禁用按钮
|
|
|
+ - 防止重复提交请求
|
|
|
+
|
|
|
+### 4. 用户体验优化
|
|
|
+
|
|
|
+#### Loading 状态管理
|
|
|
+- **实现**:
|
|
|
+ ```typescript
|
|
|
+ const loading = useSelector(
|
|
|
+ (state: RootState) => state.bodyPositionList.loading
|
|
|
+ );
|
|
|
+ ```
|
|
|
+- **效果**: 按钮显示加载动画,用户清楚操作正在进行
|
|
|
+
|
|
|
+#### 操作反馈
|
|
|
+- **成功**: `message.success('图像已拒绝')`
|
|
|
+- **失败**: `message.error('拒绝图像失败')`
|
|
|
+- **警告**: `message.warning('请先选择一个体位')`
|
|
|
+
|
|
|
+#### 按钮互斥显示
|
|
|
+- **设计**: 拒绝和恢复按钮同一时间只显示一个
|
|
|
+- **好处**: 界面简洁,避免用户混淆
|
|
|
+
|
|
|
+### 5. 性能考虑
|
|
|
+
|
|
|
+#### 状态更新优化
|
|
|
+- **当前实现**: React 自动优化,使用 `useSelector` 订阅特定状态
|
|
|
+- **优化点**:
|
|
|
+ - 按钮可见性计算逻辑简单,不需要额外的 `useMemo`
|
|
|
+ - Redux 的 selector 已经做了浅比较优化
|
|
|
+
|
|
|
+#### 图标加载
|
|
|
+- **处理**: Icon 组件内部处理图标缓存
|
|
|
+- **性能**: 首次加载后图标被缓存,后续渲染快速
|
|
|
+
|
|
|
+#### 频繁切换体位
|
|
|
+- **场景**: 用户快速点击不同体位
|
|
|
+- **影响**: 触发多次重渲染
|
|
|
+- **优化**: React 的批量更新机制自动优化,实际性能影响小
|
|
|
+
|
|
|
+### 6. 数据完整性
|
|
|
+
|
|
|
+#### SOP Instance UID 验证
|
|
|
+- **处理**: 在调用 API 前检查 `selectedBodyPosition` 存在性
|
|
|
+- **代码**:
|
|
|
+ ```typescript
|
|
|
+ if (!selectedBodyPosition) {
|
|
|
+ message.warning('请先选择一个体位');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+#### 状态字段缺失
|
|
|
+- **处理**: 使用可选链和默认值
|
|
|
+- **示例**: `judgedStatus || ''`
|
|
|
+
|
|
|
+## 🔧 后续优化建议
|
|
|
+
|
|
|
+### 1. 保存参数功能完整实现
|
|
|
+
|
|
|
+**待实现内容**:
|
|
|
+1. 定义保存参数 API
|
|
|
+ ```typescript
|
|
|
+ // src/API/exam/saveParams.ts
|
|
|
+ const saveBodyPositionParams = async (
|
|
|
+ sopInstanceUid: string,
|
|
|
+ params: AprConfig
|
|
|
+ ): Promise<void> => {
|
|
|
+ await axiosInstance.post('/auth/exam/save-params', {
|
|
|
+ sop_instance_uid: sopInstanceUid,
|
|
|
+ apr_config: params
|
|
|
+ });
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+2. 创建 Redux Thunk
|
|
|
+ ```typescript
|
|
|
+ // src/states/exam/bodyPositionListSlice.ts
|
|
|
+ export const saveParamsThunk = createAsyncThunk(
|
|
|
+ 'bodyPositionList/saveParams',
|
|
|
+ async (payload: { sopInstanceUid: string; aprConfig: AprConfig }) => {
|
|
|
+ await saveBodyPositionParams(
|
|
|
+ payload.sopInstanceUid,
|
|
|
+ payload.aprConfig
|
|
|
+ );
|
|
|
+ return payload;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ ```
|
|
|
+
|
|
|
+3. 更新 handleSaveParams
|
|
|
+ ```typescript
|
|
|
+ const handleSaveParams = async () => {
|
|
|
+ if (!selectedBodyPosition) {
|
|
|
+ message.warning('请先选择一个体位');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await dispatch(
|
|
|
+ saveParamsThunk({
|
|
|
+ sopInstanceUid: selectedBodyPosition.sop_instance_uid,
|
|
|
+ aprConfig: aprConfig
|
|
|
+ })
|
|
|
+ ).unwrap();
|
|
|
+
|
|
|
+ message.success('参数保存成功');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('保存参数失败:', err);
|
|
|
+ message.error('保存参数失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+### 2. 添加操作确认对话框
|
|
|
+
|
|
|
+对于某些关键操作,可以考虑添加确认对话框:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 拒绝操作确认(可选)
|
|
|
+const handleRejectWithConfirm = () => {
|
|
|
+ Modal.confirm({
|
|
|
+ title: '确认拒绝',
|
|
|
+ content: '确定要拒绝这张图像吗?',
|
|
|
+ onOk: handleReject,
|
|
|
+ });
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 批量操作支持
|
|
|
+
|
|
|
+未来可能需要支持批量拒绝/恢复:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 批量拒绝
|
|
|
+const handleBatchReject = async (sopInstanceUids: string[]) => {
|
|
|
+ for (const uid of sopInstanceUids) {
|
|
|
+ await dispatch(judgeImageThunk({ sopInstanceUid: uid, accept: false }));
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 4. 操作历史记录
|
|
|
+
|
|
|
+记录用户的判断操作历史:
|
|
|
+
|
|
|
+```typescript
|
|
|
+interface JudgeHistory {
|
|
|
+ sopInstanceUid: string;
|
|
|
+ action: 'reject' | 'restore';
|
|
|
+ timestamp: number;
|
|
|
+ operator: string;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5. 快捷键支持
|
|
|
+
|
|
|
+为常用操作添加快捷键:
|
|
|
+- `R` - 拒绝图像
|
|
|
+- `A` - 恢复图像(Accept)
|
|
|
+- `S` - 保存参数
|
|
|
+
|
|
|
+### 6. 状态同步机制
|
|
|
+
|
|
|
+考虑实现 WebSocket 或轮询机制,确保多客户端状态同步:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 定期检查状态
|
|
|
+useEffect(() => {
|
|
|
+ const interval = setInterval(async () => {
|
|
|
+ // 重新获取选中体位的最新状态
|
|
|
+ await refreshBodyPositionStatus(selectedBodyPosition?.sop_instance_uid);
|
|
|
+ }, 30000); // 每30秒
|
|
|
+
|
|
|
+ return () => clearInterval(interval);
|
|
|
+}, [selectedBodyPosition]);
|
|
|
+```
|
|
|
+
|
|
|
+## 📚 参考资料
|
|
|
+
|
|
|
+### 相关文件
|
|
|
+- `src/pages/view/components/ImageStateControl.tsx` - 类似功能的参考实现
|
|
|
+- `src/states/exam/bodyPositionListSlice.ts` - 状态管理
|
|
|
+- `src/API/exam/judgeImage.ts` - API 定义
|
|
|
+- `src/domain/exam/bodyPositionSelection.ts` - 体位选择逻辑
|
|
|
+
|
|
|
+### 技术栈
|
|
|
+- React + TypeScript
|
|
|
+- Redux Toolkit (状态管理)
|
|
|
+- Ant Design (UI 组件)
|
|
|
+- Axios (HTTP 请求)
|
|
|
+
|
|
|
+### API 端点
|
|
|
+- `POST /auth/task/inspection/judge` - 接受/拒绝图像
|
|
|
+- `POST /auth/exam/save-params` - 保存参数(待实现)
|
|
|
+
|
|
|
+## 📋 检查清单
|
|
|
+
|
|
|
+实施前检查:
|
|
|
+- [x] 理解业务需求和显示逻辑
|
|
|
+- [x] 确认 API 可用性(judgeImage 已就绪)
|
|
|
+- [x] 设计组件交互流程
|
|
|
+- [x] 规划错误处理策略
|
|
|
+
|
|
|
+实施中检查:
|
|
|
+- [x] 导入必要的依赖
|
|
|
+- [x] 实现处理函数
|
|
|
+- [x] 添加状态订阅
|
|
|
+- [x] 更新按钮渲染逻辑
|
|
|
+- [x] 添加 loading 状态
|
|
|
+- [x] 实现错误处理
|
|
|
+
|
|
|
+实施后检查:
|
|
|
+- [ ] 手动测试所有按钮显示逻辑
|
|
|
+- [ ] 测试拒绝/恢复功能
|
|
|
+- [ ] 测试边界情况
|
|
|
+- [ ] 测试错误处理
|
|
|
+- [ ] 性能测试
|
|
|
+- [ ] 代码审查
|
|
|
+- [ ] 文档更新
|
|
|
+
|
|
|
+## 🎯 总结
|
|
|
+
|
|
|
+本次实现成功为检查流程添加了图像状态控制功能,包括:
|
|
|
+
|
|
|
+1. **拒绝按钮** - 将已曝光图像标记为拒绝状态
|
|
|
+2. **恢复按钮** - 将已拒绝图像恢复为接受状态
|
|
|
+3. **保存参数按钮** - 预留保存 APR 参数功能(待 API 就绪)
|
|
|
+4. **删除按钮控制** - 根据曝光状态控制删除按钮的显示
|
|
|
+
|
|
|
+### 关键特性
|
|
|
+- ✅ 按钮互斥显示(拒绝/恢复)
|
|
|
+- ✅ 基于体位状态动态显示
|
|
|
+- ✅ 完整的错误处理
|
|
|
+- ✅ Loading 状态反馈
|
|
|
+- ✅ 用户友好的提示信息
|
|
|
+- ✅ 边界情况处理
|
|
|
+
|
|
|
+### 技术亮点
|
|
|
+- 使用 Redux Toolkit 的 `createAsyncThunk` 处理异步操作
|
|
|
+- 可选链操作符确保代码健壮性
|
|
|
+- 条件渲染优化 UI 显示
|
|
|
+- 统一的错误处理策略
|
|
|
+
|
|
|
+### 后续工作
|
|
|
+- 实现保存参数 API 并集成
|
|
|
+- 添加更多测试用例
|
|
|
+- 考虑添加快捷键支持
|
|
|
+- 优化用户体验细节
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**文档版本**: v1.0.0
|
|
|
+**最后更新**: 2025-01-14
|
|
|
+**维护者**: 开发团队
|