# 从任务清单进入检查的流程
## 📋 文档概述
**文档名称**: 从任务清单进入检查的流程总结
**创建日期**: 2025-12-22
**文档目的**: 全面总结从 Worklist/History 任务清单双击进入检查或处理界面的完整业务流程
**涵盖范围**:
- 用户交互流程
- 核心业务逻辑
- 数据流转关系
- Redux 状态管理
- 路由导航机制
---
## 📊 主流程泳道图
```mermaid
sequenceDiagram
participant User as 👤 用户
participant UI as 🖥️ UI层
(Worklist/History)
participant Domain as ⚙️ Domain层
(worklistToExam)
participant API as 🌐 后端API
participant Redux as 📦 Redux Store
participant Router as 🔀 路由系统
User->>UI: 双击任务行
UI->>Domain: worklistToExam(task)
Note over Domain: 1. 准备数据阶段
Domain->>API: fetchTaskDetails(StudyID)
API-->>Domain: 返回完整Study数据
(series + images)
Note over Domain: 2. 数据转换阶段
Domain->>Domain: 转换为Task格式
(包含所有Views)
Note over Domain: 3. 曝光状态判断
Domain->>Domain: 检查所有Views的
expose_status
alt 所有体位已曝光
Note over Domain: 分支A: 进入处理界面
Domain->>Redux: clearWorks()
Domain->>Redux: addWork(updatedTask)
Domain->>Domain: transformWorksToBodyPositions()
Domain->>Redux: setBodyPositions(bodyPositions)
Domain->>Redux: setBusinessFlow('process')
Redux-->>Router: 触发路由变更
Router-->>User: 导航到处理界面
else 存在未曝光体位
Note over Domain: 分支B: 进入检查界面
Domain->>Redux: clearWorks()
Domain->>Redux: addWork(updatedTask)
Domain->>Redux: setBusinessFlow('exam')
Redux-->>Router: 触发路由变更
Router-->>User: 导航到检查界面
end
```
---
## 🔀 核心决策流程图
```mermaid
flowchart TD
Start([用户双击任务]) --> Fetch[调用 fetchTaskDetails API]
Fetch --> Transform[转换数据格式
Series + Images → Views]
Transform --> Check{检查曝光状态}
Check -->|Views.length > 0 &&
所有 expose_status === 'Exposed'| AllExposed[所有体位已曝光]
Check -->|存在任何
expose_status !== 'Exposed'| HasUnexposed[存在未曝光体位]
AllExposed --> ClearA[清空缓存 clearWorks]
ClearA --> AddA[保存任务 addWork]
AddA --> TransformBP[转换为体位列表
transformWorksToBodyPositions]
TransformBP --> SetBP[设置体位数据
setBodyPositions]
SetBP --> ToProcess[设置流程为 'process']
ToProcess --> ProcessPage([进入处理界面])
HasUnexposed --> ClearB[清空缓存 clearWorks]
ClearB --> AddB[保存任务 addWork]
AddB --> ToExam[设置流程为 'exam']
ToExam --> ExamPage([进入检查界面])
style AllExposed fill:#90EE90
style HasUnexposed fill:#FFB6C1
style ProcessPage fill:#4169E1,color:#fff
style ExamPage fill:#FF8C00,color:#fff
```
---
## 🏊 完整业务泳道图(包含多个入口)
```mermaid
flowchart TB
subgraph UserActions[用户操作层]
W[Worklist双击]
H[History双击]
end
subgraph DomainLogic[业务逻辑层]
WTE[worklistToExam函数]
PWE[prepareWorksForExam
准备详细数据]
subgraph DataProcess[数据处理]
FetchAPI[调用 fetchTaskDetails API]
MapData[映射数据:
Series → Views
Images → dview]
end
subgraph Decision[决策逻辑]
CheckStatus{检查曝光状态
allExposed?}
end
end
subgraph StateManagement[状态管理层 Redux]
ClearCache[clearWorks
清空缓存]
AddCache[addWork
保存任务]
SetBP[setBodyPositions
设置体位列表]
SetFlow[setBusinessFlow
设置业务流程]
end
subgraph Routing[路由层]
ToExamRoute[导航到 /exam]
ToProcessRoute[导航到 /process]
end
subgraph Pages[页面层]
ExamPage[📋 检查界面
Exam Page]
ProcessPage[🖼️ 处理界面
Process Page]
end
W --> WTE
H --> WTE
WTE --> PWE
PWE --> FetchAPI
FetchAPI --> MapData
MapData --> CheckStatus
CheckStatus -->|是
所有体位已曝光| PathA[处理路径]
CheckStatus -->|否
存在未曝光体位| PathB[检查路径]
PathA --> ClearCache
ClearCache --> AddCache
AddCache --> SetBP
SetBP --> SetFlowProcess[setBusinessFlow
'process']
SetFlowProcess --> ToProcessRoute
ToProcessRoute --> ProcessPage
PathB --> ClearCache2[clearWorks]
ClearCache2 --> AddCache2[addWork]
AddCache2 --> SetFlowExam[setBusinessFlow
'exam']
SetFlowExam --> ToExamRoute
ToExamRoute --> ExamPage
style W fill:#E6F3FF
style H fill:#E6F3FF
style CheckStatus fill:#FFF4E6
style ExamPage fill:#FF8C00,color:#fff
style ProcessPage fill:#4169E1,color:#fff
```
---
## 📋 详细流程说明
### 阶段1: 用户触发操作
**入口点**:
- Worklist 页面双击任务行
- History 页面双击任务行
**触发函数**:
- `handleRowDoubleClick(record: Task)` → `worklistToExam(task)`
---
### 阶段2: 数据准备 (prepareWorksForExam)
**输入**: `Task` 对象(可能包含不完整的 Views 数据)
**处理流程**:
1. 调用 `fetchTaskDetails(StudyID)` 获取完整的 Study 数据
2. 接收后端返回的 `series` 和 `images` 数据
3. 使用 `flatMap` 将 Series 和 Images 转换为 `dview[]` 格式
4. 构建包含完整 Views 的 Task 对象
**输出**: `Task` 对象(包含完整的 Views 数据)
**数据映射关系**:
```typescript
TaskDetails.series[] → flatMap → Task.Views[]
├─ Series.images[] → map → dview[]
└─ 每个 Image 映射为一个 dview 对象
```
**dview 字段映射**:
| 源字段 (Image/Series) | 目标字段 (dview) | 说明 |
|----------------------|------------------|------|
| `image.view_id` | `view_id` | 体位ID |
| `series.series_instance_uid` | `series_instance_uid` | Series UID |
| `taskDetails.study_instance_uid` | `study_instance_uid` | Study UID |
| `image.sop_instance_uid` | `PrimarySopUID` | 图像 SOP UID |
| `image.expose_status` | `expose_status` | **曝光状态(关键)** |
| `image.image_file_path` | `image_file` | 图像文件路径 |
| `image.thumbnail_file` | `thumbnail_file` | 缩略图路径 |
---
### 阶段3: 曝光状态判断 ⚠️ **核心决策点**
**判断逻辑**:
```typescript
const allExposed =
updatedTask.Views.length > 0 &&
updatedTask.Views.every((view) => view.expose_status === 'Exposed');
```
**决策规则**:
| 条件 | 判断结果 | 目标界面 |
|------|---------|---------|
| `Views.length === 0` | `allExposed = false` | Exam(检查界面) |
| `Views.length > 0` 且所有 `expose_status === 'Exposed'` | `allExposed = true` | **Process(处理界面)** |
| `Views.length > 0` 但存在 `expose_status !== 'Exposed'` | `allExposed = false` | Exam(检查界面) |
**状态值说明**:
- `'Exposed'` - 已曝光(已拍摄图像)
- `'Unexposed'` - 未曝光(待拍摄)
---
### 阶段4: Redux 状态更新
#### 4A. 进入处理界面 (allExposed === true)
**步骤顺序**:
```typescript
1. dispatch(clearWorks()) // 清空现有工作缓存
2. dispatch(addWork(updatedTask)) // 保存新任务到缓存
3. bodyPositions = await transformWorksToBodyPositions([updatedTask]) // 转换为体位列表
4. dispatch(setBodyPositions(bodyPositions)) // 设置体位列表数据
5. dispatch(setBusinessFlow('process')) // 切换业务流程到 process
```
**Redux 状态变化**:
| Slice | Action | 状态变化 |
|-------|--------|---------|
| `examWorksCache` | `clearWorks` | `works: []` |
| `examWorksCache` | `addWork` | `works: [updatedTask]` |
| `bodyPositionList` | `setBodyPositions` | `positions: ExtendedBodyPosition[]` |
| `businessFlow` | `setBusinessFlow` | `currentKey: 'process'` |
**关键依赖**: 必须先设置 `bodyPositions`,否则处理界面无法正常显示
---
#### 4B. 进入检查界面 (allExposed === false)
**步骤顺序**:
```typescript
1. dispatch(clearWorks()) // 清空现有工作缓存
2. dispatch(addWork(updatedTask)) // 保存新任务到缓存
3. dispatch(setBusinessFlow('exam')) // 切换业务流程到 exam
```
**Redux 状态变化**:
| Slice | Action | 状态变化 |
|-------|--------|---------|
| `examWorksCache` | `clearWorks` | `works: []` |
| `examWorksCache` | `addWork` | `works: [updatedTask]` |
| `businessFlow` | `setBusinessFlow` | `currentKey: 'exam'` |
**说明**: 检查界面不需要 `bodyPositions`,因为会在界面内动态生成和管理
---
### 阶段5: 路由导航
**触发机制**: `setBusinessFlow` 的 Redux 状态变化会触发路由监听
**导航目标**:
- `businessFlow.currentKey === 'exam'` → 导航到 `/exam` 路由
- `businessFlow.currentKey === 'process'` → 导航到 `/process` 路由
---
## 📊 数据流转关系表
| 阶段 | 输入 | 处理函数/逻辑 | 输出 |
|------|------|--------------|------|
| **1. API 调用** | `StudyID: string` | `fetchTaskDetails(StudyID)` | `TaskDetails` (包含 series, images) |
| **2. 数据转换** | `TaskDetails` | `Series.flatMap(s => s.images.map(...))` | `Task` (包含 Views) |
| **3. 状态判断** | `Task.Views[]` | `Views.every(v => v.expose_status === 'Exposed')` | `boolean` (allExposed) |
| **4A. 体位转换** | `Task[]` | `transformWorksToBodyPositions(tasks)` | `ExtendedBodyPosition[]` |
| **4B. Redux 更新** | `Task`, `allExposed` | 条件分支 dispatch | Redux State 更新 |
| **5. 路由导航** | `businessFlow.currentKey` | 路由监听 | 页面切换 |
---
## 🎯 四种业务场景
### 场景矩阵
| 入口 | 曝光状态 | 判断条件 | 目标界面 | 测试文件 |
|------|---------|---------|---------|---------|
| **Worklist** | 未曝光 | `allExposed = false` | **Exam** | `worklist-enter-exam-unexposed.cy.ts` |
| **Worklist** | 已曝光 | `allExposed = true` | **Process** | `worklist-enter-process-exposed.cy.ts` |
| **History** | 未曝光 | `allExposed = false` | **Exam** | `history-enter-exam-unexposed.cy.ts` |
| **History** | 已曝光 | `allExposed = true` | **Process** | `history-enter-process-exposed.cy.ts` |
---
### 场景1: Worklist → Exam(未曝光)
```mermaid
sequenceDiagram
participant User
participant Worklist
participant worklistToExam
participant API
participant Redux
participant ExamPage
User->>Worklist: 双击未曝光任务
Worklist->>worklistToExam: worklistToExam(task)
worklistToExam->>API: fetchTaskDetails(StudyID)
API-->>worklistToExam: TaskDetails (有未曝光体位)
worklistToExam->>worklistToExam: allExposed = false
worklistToExam->>Redux: clearWorks() + addWork()
worklistToExam->>Redux: setBusinessFlow('exam')
Redux-->>ExamPage: 导航到检查界面
```
**关键验证点**:
- ✅ API 返回包含 `expose_status: 'Unexposed'` 的 images
- ✅ `businessFlow.currentKey === 'exam'`
- ✅ `examWorksCache.works.length === 1`
- ✅ 检查界面主要元素可见
---
### 场景2: Worklist → Process(已曝光)
```mermaid
sequenceDiagram
participant User
participant Worklist
participant worklistToExam
participant API
participant Redux
participant ProcessPage
User->>Worklist: 双击已曝光任务
Worklist->>worklistToExam: worklistToExam(task)
worklistToExam->>API: fetchTaskDetails(StudyID)
API-->>worklistToExam: TaskDetails (所有体位已曝光)
worklistToExam->>worklistToExam: allExposed = true
worklistToExam->>Redux: clearWorks() + addWork()
worklistToExam->>worklistToExam: transformWorksToBodyPositions()
worklistToExam->>Redux: setBodyPositions() + setBusinessFlow('process')
Redux-->>ProcessPage: 导航到处理界面
```
**关键验证点**:
- ✅ API 返回所有 images 的 `expose_status === 'Exposed'`
- ✅ `businessFlow.currentKey === 'process'`
- ✅ `bodyPositionList.positions.length > 0`
- ✅ 处理界面主要元素可见
---
### 场景3: History → Exam(未曝光)
**流程**: 与场景1相同,仅入口点不同(从 History 页面触发)
---
### 场景4: History → Process(已曝光)
**流程**: 与场景2相同,仅入口点不同(从 History 页面触发)
---
## 🗂️ 核心文件清单
### Domain 业务逻辑层
| 文件路径 | 职责 | 关键函数 |
|---------|------|---------|
| `src/domain/patient/worklistToExam.ts` | **核心业务逻辑** | `worklistToExam()`, `prepareWorksForExam()`, `worklistToProcess()` |
### UI 组件层
| 文件路径 | 职责 | 关键函数/组件 |
|---------|------|--------------|
| `src/pages/patient/worklist.tsx` | Worklist 页面 | `handleRowDoubleClick()` |
| `src/pages/patient/HistoryList.tsx` | History 页面 | `handleRowDoubleClick()` |
| `src/pages/patient/components/WorklistTable.tsx` | 表格组件 | `onDoubleClick` 事件处理 |
### Redux 状态管理层
| 文件路径 | Slice 名称 | 关键 Actions |
|---------|-----------|-------------|
| `src/states/exam/examWorksCacheSlice.ts` | `examWorksCache` | `clearWorks()`, `addWork()` |
| `src/states/exam/bodyPositionListSlice.ts` | `bodyPositionList` | `setBodyPositions()`, `transformWorksToBodyPositions()` |
| `src/states/BusinessFlowSlice.ts` | `businessFlow` | `setBusinessFlow()` |
### API 层
| 文件路径 | 职责 | 关键函数 |
|---------|------|---------|
| `src/API/patient/workActions.ts` | 任务相关 API | `fetchTaskDetails()` |
### 类型定义
| 文件路径 | 定义类型 |
|---------|---------|
| `src/domain/work.ts` | `Task` 接口 |
| `src/domain/dview.ts` | `dview` 接口(体位视图) |
| `src/domain/series.ts` | `Series` 接口 |
| `src/domain/xImage.ts` | `XImage` 接口 |
---
## 🔍 核心代码片段
### 1. worklistToExam 函数(完整逻辑)
```typescript
// 文件: src/domain/patient/worklistToExam.ts
const worklistToExam = async (task: Task) => {
const dispatch = store.dispatch;
try {
// 步骤1: 准备详细数据
const [updatedTask] = await prepareWorksForExam(task);
// 步骤2: 判断曝光状态
const allExposed =
updatedTask.Views.length > 0 &&
updatedTask.Views.every((view) => view.expose_status === 'Exposed');
// 步骤3: 清空并保存任务
dispatch(clearWorks());
dispatch(addWork(updatedTask));
// 步骤4: 根据状态决定流程
if (allExposed) {
// 所有体位已曝光 → 进入处理界面
const bodyPositions = await transformWorksToBodyPositions([updatedTask]);
dispatch(setBodyPositions(bodyPositions));
dispatch(setBusinessFlow('process'));
} else {
// 存在未曝光体位 → 进入检查界面
dispatch(setBusinessFlow('exam'));
}
} catch (error) {
console.error('Error in worklistToExam:', error);
throw error;
}
};
```
---
### 2. prepareWorksForExam 函数(数据准备)
```typescript
// 文件: src/domain/patient/worklistToExam.ts
export const prepareWorksForExam = async (
works: Task | Task[]
): Promise => {
const workArray = Array.isArray(works) ? works : [works];
const preparedWorks = await Promise.all(
workArray.map(async (work) => {
// 调用 API 获取详细数据
const taskDetails = await fetchTaskDetails(work.StudyID);
// 转换数据格式: Series → Views
return {
...work,
Views: taskDetails.series.flatMap((series: Series) =>
series.images.map(
(image: XImage) =>
({
view_id: image.view_id,
series_instance_uid: series.series_instance_uid,
study_instance_uid: taskDetails.study_instance_uid,
study_id: taskDetails.study_id,
procedure_id: series.procedure_id,
view_description: image.view_description,
view_type: '',
PrimarySopUID: image.sop_instance_uid,
expose_status: image.expose_status, // 关键字段
judged_status: image.judged_status,
image_file_path: image.image_file_path,
image_file: image.image_file_path,
thumbnail_file: image.thumbnail_file || '',
}) satisfies dview
)
),
} as Task;
})
);
return preparedWorks;
};
```
---
### 3. 曝光状态判断(关键逻辑)
```typescript
// 判断条件
const allExposed =
updatedTask.Views.length > 0 && // 1. 确保有体位数据
updatedTask.Views.every((view) => view.expose_status === 'Exposed'); // 2. 所有体位已曝光
// 决策分支
if (allExposed) {
// 路径A: 进入处理界面
const bodyPositions = await transformWorksToBodyPositions([updatedTask]);
dispatch(setBodyPositions(bodyPositions));
dispatch(setBusinessFlow('process'));
} else {
// 路径B: 进入检查界面
dispatch(setBusinessFlow('exam'));
}
```
---
## 🧪 测试验证要点
### Redux 状态验证
#### 验证 examWorksCache
```typescript
cy.window()
.its('store')
.invoke('getState')
.its('examWorksCache')
.its('works')
.should('have.length', 1)
.its(0)
.should((task) => {
expect(task).to.have.property('StudyID');
expect(task.Views).to.be.an('array');
expect(task.Views.length).to.be.greaterThan(0);
});
```
#### 验证 businessFlow(检查界面)
```typescript
cy.window()
.its('store')
.invoke('getState')
.its('businessFlow')
.its('currentKey')
.should('eq', 'exam');
```
#### 验证 businessFlow(处理界面)
```typescript
cy.window()
.its('store')
.invoke('getState')
.its('businessFlow')
.its('currentKey')
.should('eq', 'process');
```
#### 验证 bodyPositionList(处理界面)
```typescript
cy.window()
.its('store')
.invoke('getState')
.its('bodyPositionList')
.its('positions')
.should('have.length.greaterThan', 0)
.each((bp) => {
expect(bp).to.have.property('dview');
expect(bp.dview).to.have.property('expose_status', 'Exposed');
});
```
---
### API Mock 验证
#### Mock 未曝光任务详情
```typescript
cy.intercept('GET', '/dr/api/v1/auth/study/*', {
statusCode: 200,
body: {
study_id: 'TEST001',
study_instance_uid: '1.2.3.4.5',
series: [
{
series_instance_uid: '1.2.3.4.5.1',
images: [
{
sop_instance_uid: '1.2.3.4.5.1.1',
view_id: 'VIEW001',
expose_status: 'Unexposed', // 关键: 未曝光
// ... 其他字段
}
]
}
]
}
}).as('getStudyDetailsUnexposed');
```
#### Mock 已曝光任务详情
```typescript
cy.intercept('GET', '/dr/api/v1/auth/study/*', {
statusCode: 200,
body: {
study_id: 'TEST002',
study_instance_uid: '1.2.3.4.6',
series: [
{
series_instance_uid: '1.2.3.4.6.1',
images: [
{
sop_instance_uid: '1.2.3.4.6.1.1',
view_id: 'VIEW002',
expose_status: 'Exposed', // 关键: 已曝光
image_file_path: '/path/to/image.dcm',
thumbnail_file: '/path/to/thumb.jpg',
// ... 其他字段
}
]
}
]
}
}).as('getStudyDetailsExposed');
```
---
## 🐛 边界情况处理
### 1. Views 为空数组
**情况**: `updatedTask.Views.length === 0`
**判断结果**: `allExposed = false`(因为前置条件失败)
**处理**: 进入检查界面(Exam)
**原因**: 没有体位数据,需要在检查界面配置
---
### 2. 混合曝光状态
**情况**: 部分体位 `expose_status === 'Exposed'`,部分 `'Unexposed'`
**判断结果**: `allExposed = false`(`every()` 条件不满足)
**处理**: 进入检查界面(Exam)
**原因**: 存在未完成的曝光任务,需要继续检查
---
### 3. expose_status 其他值
**可能值**:
- `'Exposed'` - 已曝光(已拍摄)
- `'Unexposed'` - 未曝光(待拍摄)
- 可能存在其他状态(如 `'Deleted'`, `'Archived'` 等)
**处理**:
- 只有完全等于 `'Exposed'` 才算已曝光
- 其他任何值都视为未曝光,进入检查界面
---
### 4. API 调用失败
**情况**: `fetchTaskDetails()` 抛出错误
**处理**:
```typescript
try {
// ... 业务逻辑
} catch (error) {
console.error('Error in worklistToExam:', error);
throw error; // 向上层抛出,由调用方处理
}
```
**说明**: 错误会冒泡到 UI 层,通常会显示错误提示
---
## 📝 设计模式和架构特点
### 1. 关注点分离
| 层次 | 职责 | 示例文件 |
|------|------|---------|
| **UI 层** | 用户交互、事件触发 | `worklist.tsx`, `HistoryList.tsx` |
| **Domain 层** | 业务逻辑、数据转换 | `worklistToExam.ts` |
| **API 层** | 后端通信 | `workActions.ts` |
| **State 层** | 状态管理 | `examWorksCacheSlice.ts`, `businessFlowSlice.ts` |
---
### 2. 单一职责原则
- `prepareWorksForExam()` - 仅负责数据准备和转换
- `worklistToExam()` - 负责业务逻辑和流程控制
- `transformWorksToBodyPositions()` - 仅负责体位列表转换
---
### 3. 异步流程处理
所有 API 调用和数据转换都使用 `async/await` 模式:
```typescript
const [updatedTask] = await prepareWorksForExam(task);
const bodyPositions = await transformWorksToBodyPositions([updatedTask]);
```
---
### 4. 数据不可变性
使用 Redux Toolkit 的 Immer,确保状态更新的不可变性:
```typescript
dispatch(clearWorks()); // 不直接修改 state,而是 dispatch action
dispatch(addWork(updatedTask));
```
---
## 🚀 性能优化考虑
### 1. 并行数据获取
`prepareWorksForExam` 使用 `Promise.all` 并行处理多个任务:
```typescript
const preparedWorks = await Promise.all(
workArray.map(async (work) => {
const taskDetails = await fetchTaskDetails(work.StudyID);
return { ...work, Views: ... };
})
);
```
**优势**: 多个任务同时请求,减少总等待时间
---
### 2. 条件性体位转换
只有在进入处理界面时才执行体位转换:
```typescript
if (allExposed) {
const bodyPositions = await transformWorksToBodyPositions([updatedTask]);
dispatch(setBodyPositions(bodyPositions));
dispatch(setBusinessFlow('process'));
}
```
**优势**: 避免不必要的数据转换计算
---
### 3. 缓存清理
每次进入前清空旧缓存,避免数据冲突:
```typescript
dispatch(clearWorks()); // 清理旧数据
dispatch(addWork(updatedTask)); // 添加新数据
```
---
## 📚 相关文档索引
### 实现文档
- [体位全曝光-worklist-双击-进入检查.md](./实现/体位全曝光-worklist-双击-进入检查.md)
- [exam体位列表-双击已曝光体位进入处理.md](./实现/exam体位列表-双击已曝光体位进入处理.md)
### 测试文档
- [进入检查功能测试方案.md](./测试/进入检查功能测试方案.md)
### 代码文件
- [worklistToExam.ts](../src/domain/patient/worklistToExam.ts) - 核心业务逻辑
- [worklist.tsx](../src/pages/patient/worklist.tsx) - Worklist 页面
- [HistoryList.tsx](../src/pages/patient/HistoryList.tsx) - History 页面
---
## ✅ 总结
### 核心要点
1. **双击触发**: 从 Worklist 或 History 双击任务行触发流程
2. **数据准备**: 通过 API 获取完整的 Study 数据,转换为 Views
3. **状态判断**: 根据所有体位的 `expose_status` 判断是否全部曝光
4. **流程分支**:
- 全部已曝光 → 处理界面 (Process)
- 存在未曝光 → 检查界面 (Exam)
5. **状态管理**: 通过 Redux 管理任务缓存、体位列表和业务流程
### 关键判断逻辑
```typescript
const allExposed =
updatedTask.Views.length > 0 &&
updatedTask.Views.every((view) => view.expose_status === 'Exposed');
```
### 测试覆盖
- ✅ Worklist → Exam (未曝光)
- ✅ Worklist → Process (已曝光)
- ✅ History → Exam (未曝光)
- ✅ History → Process (已曝光)
---
**文档版本**: v1.0
**最后更新**: 2025-12-22
**维护者**: Development Team