功能名称:图像另存为(Save Image As)
功能描述:允许用户复制当前选中的已曝光图像,创建一个新的体位副本添加到当前 Study 的体位列表中。
业务场景:
src/pages/view/components/ImageStateControl.tsx
的"另存为"按钮sop_instance_uid
)POST /auth/image/save_as
{ instance_uid: string }
{ code: string, description: string, data: {} }
┌─────────────────────────────────────────┐
│ UI Layer (React Component) │
│ ImageStateControl.tsx │
│ - 用户交互 │
│ - 确认对话框 │
│ - 消息提示 │
└──────────────┬──────────────────────────┘
│ dispatch(saveImageAsThunk)
↓
┌─────────────────────────────────────────┐
│ State Management (Redux) │
│ bodyPositionListSlice.ts │
│ - saveImageAsThunk │
│ - Loading状态 │
│ - 更新体位列表 │
└──────────────┬──────────────────────────┘
│ executeSaveImageAs()
↓
┌─────────────────────────────────────────┐
│ Domain Layer (Business Logic) │
│ domain/exam/saveImageAs.ts │
│ - 协调完整流程 │
│ - 数据转换 │
│ - 错误处理 │
└──────────────┬──────────────────────────┘
│ API Calls
↓
┌─────────────────────────────────────────┐
│ API Layer (HTTP) │
│ API/imageActions.ts │
│ API/patient/workActions.ts │
│ - saveImageAs() │
│ - fetchTaskDetails() │
└─────────────────────────────────────────┘
sequenceDiagram
participant User as 👤 用户
participant UI as ImageStateControl
participant Redux as Redux Store
participant Domain as Domain Layer
participant API1 as saveImageAs API
participant API2 as fetchTaskDetails API
participant Backend as 🖥️ 后端服务
User->>UI: 1. 点击"另存为"
UI->>UI: 2. 显示确认对话框
User->>UI: 3. 确认操作
UI->>Redux: 4. dispatch(saveImageAsThunk)
Redux->>Redux: 5. 设置 loading=true
Redux->>Domain: 6. executeSaveImageAs()
Note over Domain: === 核心业务逻辑 ===
Domain->>API1: 7. saveImageAs(sopInstanceUid)
API1->>Backend: 8. POST /auth/image/save_as
Backend-->>API1: 9. { code: "0x000000", data: {} }
API1-->>Domain: 10. 返回成功
Domain->>API2: 11. fetchTaskDetails(studyId)
API2->>Backend: 12. GET /auth/study/{studyId}
Backend-->>API2: 13. 返回完整 Study 数据
API2-->>Domain: 14. RegisterWorkResponseData
Domain->>Domain: 15. 数据转换 (Work → ExtendedBodyPosition[])
Domain-->>Redux: 16. { success: true, updatedBodyPositions }
Redux->>Redux: 17. 更新 bodyPositions
Redux->>Redux: 18. 设置 loading=false
Redux-->>UI: 19. 通知完成
UI->>User: 20. 显示"图像另存为成功"
UI->>UI: 21. 体位列表自动刷新
// 用户点击"另存为"按钮
// 条件:单分格 + 已选中图像 + 图像已曝光
showSaveAsButton = isSingleGrid && selectedImage !== null && expose_status === 'Exposed'
Modal.confirm({
title: '确认另存为',
content: '确定要复制选中的图像吗?',
onOk: async () => {
// 执行另存为逻辑
}
});
await dispatch(
saveImageAsThunk({
sopInstanceUid: selectedImage.sop_instance_uid,
studyId: selectedImage.study_id
})
).unwrap();
// Step 1: 调用后端API执行另存为
await apiSaveImageAs(sopInstanceUid);
// Step 2: 重新获取Study详情
const studyDetails = await fetchTaskDetails(studyId);
// Step 3: 数据转换
const work = transformToWork(studyDetails);
const updatedBodyPositions = await transformWorksToBodyPositions([work]);
// Step 4: 返回结果
return { success: true, updatedBodyPositions };
// Redux自动更新
state.bodyPositions = action.payload.updatedBodyPositions;
state.loading = false;
// React自动重新渲染
// BodyPositionList组件显示更新后的列表
message.success('图像另存为成功,体位列表已更新');
文件:src/domain/exam/saveImageAs.ts
✨ 新增
职责:
导出:
executeSaveImageAs(sopInstanceUid, studyId)
- 核心函数SaveImageAsResult
- 结果类型文件:src/API/imageActions.ts
✏️ 修改
新增内容:
saveImageAs(sopInstanceUid)
- 调用另存为接口SaveImageAsResponse
- 响应类型文件:src/API/patient/workActions.ts
✏️ 修改
修改内容:
RegisterWorkResponseData
- 添加宠物相关可选字段
chip_number?: string
variety?: string
sex_neutered?: string
is_anaesthesia?: boolean
is_sedation?: boolean
pregnancy_status?: string
create_time?: string
文件:src/states/exam/bodyPositionListSlice.ts
✏️ 修改
新增内容:
saveImageAsThunk
- Redux异步thunksaveImageAsThunk.pending
saveImageAsThunk.fulfilled
saveImageAsThunk.rejected
文件:src/pages/view/components/ImageStateControl.tsx
✏️ 修改
新增内容:
handleSaveAs()
- 另存为按钮处理函数showSaveAsButton
- 按钮显示条件interface SaveImageAsResult {
/** 操作是否成功 */
success: boolean;
/** 更新后的体位列表 */
updatedBodyPositions: ExtendedBodyPosition[];
/** 错误信息(如果失败) */
error?: string;
}
interface SaveImageAsResponse {
/** 响应码 */
code: string;
/** 描述信息 */
description: string;
/** 解决方案 */
solution: string;
/** 数据内容(空对象) */
data: Record<string, never>;
}
interface ExtendedBodyPosition extends BodyPosition {
patient_name: string;
patient_id: string;
registration_number: string;
study_description: string;
body_position_image: string;
collimator_length: number | string;
collimator_width: number | string;
sid: string;
work: Work;
sop_instance_uid: string;
series_instance_uid?: string;
study_instance_uid?: string;
secondary_sop_uid?: string;
study_id?: string;
dview: dview;
}
describe('executeSaveImageAs', () => {
it('应该成功执行另存为并返回更新的体位列表', async () => {
// Arrange
const sopInstanceUid = 'test-sop-uid';
const studyId = 'test-study-id';
// Act
const result = await executeSaveImageAs(sopInstanceUid, studyId);
// Assert
expect(result.success).toBe(true);
expect(result.updatedBodyPositions).toBeInstanceOf(Array);
});
it('应该处理API调用失败的情况', async () => {
// Arrange
mockApiToFail();
// Act
const result = await executeSaveImageAs('invalid', 'invalid');
// Assert
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
describe('saveImageAsThunk', () => {
it('应该设置loading状态并更新体位列表', async () => {
// Test implementation
});
it('应该处理错误状态', async () => {
// Test implementation
});
});
describe('ImageStateControl - Save As', () => {
it('应该在满足条件时显示另存为按钮', () => {
// Arrange: 单分格 + 已选中 + 已曝光
// Act: 渲染组件
// Assert: 按钮可见
});
it('应该在点击后显示确认对话框', async () => {
// Test implementation
});
it('应该在确认后调用Redux thunk', async () => {
// Test implementation
});
});
问题:用户快速多次点击"另存为"
解决方案:
loading
状态禁用按钮问题:另存为完成后,新增的体位可能不在视图中
解决方案:
问题:刷新体位列表后,原来选中的体位可能丢失
当前方案:
未来优化:
sop_instance_uid
问题:一个 study 包含很多体位时,刷新可能较慢
当前方案:
未来优化:
问题:各种错误场景的处理
解决方案:
// Domain层
try {
// 业务逻辑
} catch (error) {
return {
success: false,
updatedBodyPositions: [],
error: error.message
};
}
// UI层
try {
await dispatch(saveImageAsThunk(...)).unwrap();
message.success('成功');
} catch (err) {
message.error('失败');
}
缓存优化
增量更新
虚拟滚动
message.success('图像另存为成功,体位列表已更新')
message.error('图像另存为失败')
any
类型executeSaveImageAs
, handleSaveAs
SaveImageAsResult
showSaveAsButton
实现日期:2025-10-20
实现者:Claude (Cline)
版本:v1.0.0
提交信息:
feat: 实现图像另存为功能
- 新增 Domain 层业务逻辑 (saveImageAs.ts)
- 新增 API 层接口调用 (saveImageAs in imageActions.ts)
- 新增 Redux thunk (saveImageAsThunk)
- 实现 UI 组件按钮功能 (ImageStateControl.tsx)
- 完善类型定义,确保类型安全
- 添加完整的错误处理和用户反馈
Closes #XXX