删除发送任务功能.md 16 KB

删除发送任务功能实现文档

概述

本文档详细记录了删除发送任务功能的实现过程,包括架构设计、数据流、交互流程和具体实现细节。

功能需求

实现传输队列中的删除功能:

  • 用户可以在表格中选择一个或多个发送任务
  • 点击删除按钮后,弹出确认对话框
  • 确认后调用后端接口批量删除选中的任务
  • 删除成功后更新 UI 并清空选中状态

1. 架构分析

1.1 涉及的参与者(从粗到细)

UI 层

  • OutputOperationPanel (容器组件)

    • 位置:src/pages/patient/components/OutputOperationPanel.tsx
    • 职责:组织和布局操作面板
  • OutputActionPanel (操作面板组件)

    • 位置:src/pages/patient/components/OutputActionPanel.tsx
    • 职责:渲染操作按钮(重试、删除)并处理用户交互
    • 修改内容:
    • 新增 handleDelete 方法
    • 导入 Modal 组件和 deleteSendJobThunk
    • 绑定删除按钮的 onClick 事件

状态管理层

  • sendJobSlice (Redux Slice)
    • 位置:src/states/output/sendJob/slices/sendJobSlice.ts
    • 参与者:
    • deleteSendJobThunk - 删除任务的异步 thunk(已存在)
    • sendJobEntitiesSlice - 管理发送任务数据
    • sendJobSelectionSlice - 管理选中的任务 ID

API 层

  • sendJobActions
    • 位置:src/API/output/sendJobActions.ts
    • 方法:deleteSendTask(taskIds: string[]) - 调用删除接口(已存在)

数据模型层

  • SendJob (发送任务实体)
    • 位置:src/domain/output/sendJob.ts
    • 核心属性:
    • task_id: 任务唯一标识
    • patient_name: 患者姓名
    • status: 任务状态

2. 交互流程

泳道图

sequenceDiagram
    participant User as 用户
    participant UI as OutputActionPanel
    participant Modal as 确认对话框
    participant Redux as Redux Store
    participant Thunk as deleteSendJobThunk
    participant API as deleteSendTask
    participant Backend as 后端服务

    User->>UI: 1. 勾选表格中的任务
    UI->>Redux: 2. 更新 selectedIds
    
    User->>UI: 3. 点击删除按钮
    UI->>UI: 4. 校验 selectedIds.length > 0
    
    alt 无选中任务
        UI->>User: 显示警告:请先选择要删除的任务
    else 有选中任务
        UI->>Modal: 5. 显示确认对话框
        Modal->>User: 展示:确定要删除 N 个任务吗?
        
        alt 用户取消
            User->>Modal: 点击取消
            Modal->>UI: 关闭对话框
        else 用户确认
            User->>Modal: 点击确定
            Modal->>Redux: 6. dispatch(deleteSendJobThunk(selectedIds))
            Redux->>Thunk: 7. 执行删除 thunk
            Thunk->>API: 8. 调用 deleteSendTask(taskIds)
            API->>Backend: 9. DELETE /auth/scp/task
            Backend-->>API: 10. 返回结果 {code: '0x000000'}
            API-->>Thunk: 11. 返回响应
            
            alt 删除成功
                Thunk->>Redux: 12. fulfilled - 更新 state
                Redux->>Redux: 13. 过滤掉已删除的任务
                Redux->>Redux: 14. 清空 selectedIds
                Redux-->>UI: 15. 触发重新渲染
                UI->>User: 16. 显示成功消息
            else 删除失败
                Thunk->>Redux: rejected
                Redux-->>UI: 错误信息
                UI->>User: 显示错误消息
            end
        end
    end

3. 数据流

┌─────────────┐
│  用户操作    │
│  勾选任务    │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────┐
│ Redux State 更新              │
│ selectedIds = ['id1', 'id2'] │
└──────┬──────────────────────┘
       │
       │ 用户点击删除按钮
       ▼
┌─────────────────────┐
│ handleDelete 校验    │
│ selectedIds.length   │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ Modal.confirm        │
│ 显示确认对话框        │
└──────┬──────────────┘
       │
       │ 用户确认
       ▼
┌─────────────────────────────────────┐
│ dispatch(deleteSendJobThunk(ids))   │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────────┐
│ API 层                  │
│ deleteSendTask(taskIds) │
└──────┬──────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ HTTP DELETE 请求                 │
│ /auth/scp/task                  │
│ body: ['id1', 'id2']            │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────┐
│ 后端处理并返回       │
│ {code: '0x000000'}  │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────────────────────┐
│ Redux Store 更新                     │
│ - entities.data (过滤已删除项)        │
│ - selection.selectedIds = []         │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────┐
│ UI 重新渲染          │
│ 显示成功消息          │
└─────────────────────┘

4. 核心数据结构

4.1 SendJob (发送任务实体)

interface SendJob {
  task_id: string;           // 任务唯一标识
  patient_name: string;      // 患者姓名
  patient_id: string;        // 患者 ID
  priority: SendJobPriority; // 优先级: 'High' | 'Medium' | 'Low'
  status: SendJobStatus;     // 状态: 'ARRIVED' | 'SENDING' | 'FAILED' | 'SUCCESS'
  retry_count?: number;      // 重试次数
  destination: string;       // 目标 PACS 节点
}

4.2 Redux State 结构

// 选中状态
interface SelectionState {
  selectedIds: string[];  // 选中的任务 ID 列表
}

// 实体状态
interface EntitiesState<SendJob> {
  data: SendJob[];    // 任务列表
  total: number;      // 总数
}

// UI 状态
interface UIState {
  loading: boolean;
  error: string | null;
}

4.3 API 接口

请求

// DELETE /auth/scp/task
// Content-Type: application/json
// Body: string[] (任务 ID 数组)

例如:
["0199cd46-82f0-76c5-b1d3-9399668a1a05", "0199cd46-460d-770e-ac8e-549939a4a7d4"]

响应

interface BaseResponse {
  code: string;           // '0x000000' 表示成功
  description: string;    // 描述信息
  solution: string;       // 解决方案
  data: Record<string, any>;
}

5. 具体实现

5.1 组件修改:OutputActionPanel.tsx

导入依赖

// 新增 Modal 组件和 deleteSendJobThunk
import { Tooltip, Button, Space, message, Modal } from 'antd';
import { retrySendJobThunk, deleteSendJobThunk } from '@/states/output/sendJob/slices/sendJobSlice';

实现 handleDelete 方法

const handleDelete = async () => {
  // 第 1 步:校验是否有选中的任务
  if (selectedIds.length === 0) {
    message.warning('请先选择要删除的任务');
    return;
  }

  // 第 2 步:显示确认对话框
  Modal.confirm({
    title: '确认删除',
    content: `确定要删除选中的 ${selectedIds.length} 个任务吗?此操作不可恢复。`,
    okText: '确定',
    cancelText: '取消',
    onOk: async () => {
      try {
        // 第 3 步:调用删除 thunk
        await dispatch(deleteSendJobThunk(selectedIds)).unwrap();
        
        // 第 4 步:显示成功消息
        message.success(`成功删除 ${selectedIds.length} 个任务`);
      } catch (error) {
        // 第 5 步:错误处理
        console.error('删除任务失败:', error);
        message.error('删除任务失败,请稍后再试');
      }
    },
  });
};

绑定事件处理器

<Button
  type="default"
  icon={<Icon name="Delete" />}
  onClick={handleDelete}  // 绑定删除处理函数
/>

5.2 Redux 层(已存在,无需修改)

deleteSendJobThunk 实现

// src/states/output/sendJob/slices/sendJobSlice.ts
export const deleteSendJobThunk = createDeleteThunk(
  'sendJob',
  async (ids: string[]) => {
    await deleteSendTask(ids);
    store.dispatch(sendJobSelectionSlice.actions.clearSelection());
  }
);

Redux Slice 配置

// 在 createEntityListSlices 中自动处理
builder.addCase(
  deleteThunk.fulfilled,
  (state, action: PayloadAction<string[]>) => {
    // 从数据列表中过滤掉已删除的任务
    state.data = state.data.filter(
      (item) => !action.payload.includes(item.task_id)
    );
  }
);

5.3 API 层(已存在,无需修改)

// src/API/output/sendJobActions.ts
export const deleteSendTask = async (
  taskIds: string[]
): Promise<BaseResponse> => {
  try {
    const response = await axiosInstance.delete<BaseResponse>(
      '/auth/scp/task',
      {
        data: taskIds,  // 传递任务 ID 数组
      }
    );

    if (response.data.code !== '0x000000') {
      throw new Error(`删除发送任务失败: ${response.data.description}`);
    }

    return response.data;
  } catch (error) {
    console.error('Error deleting send task:', error);
    throw error;
  }
};

6. 功能执行流程

起点:用户操作

1. 用户在传输队列表格中勾选任务
   ├─ 触发 checkbox onChange
   └─ 更新 Redux: selectedIds = ['id1', 'id2', ...]

2. 用户点击删除按钮
   └─ 调用 handleDelete()

3. 前置校验
   ├─ 检查 selectedIds.length === 0
   │  └─ 是:显示警告消息 "请先选择要删除的任务"
   │  └─ 否:继续下一步
   
4. 显示确认对话框
   ├─ 标题:确认删除
   ├─ 内容:确定要删除选中的 N 个任务吗?此操作不可恢复。
   └─ 按钮:[取消] [确定]
   
5. 用户选择
   ├─ 点击取消:关闭对话框,不执行删除
   └─ 点击确定:执行删除操作

6. 执行删除
   ├─ dispatch(deleteSendJobThunk(selectedIds))
   ├─ 调用 API: deleteSendTask(taskIds)
   ├─ 发送 HTTP DELETE 请求
   └─ 等待响应

7. 后端处理
   ├─ 验证请求合法性
   ├─ 从数据库删除任务记录
   └─ 返回响应 {code: '0x000000'}

8. Redux Store 更新
   ├─ deleteSendJobThunk.fulfilled 触发
   ├─ 从 entities.data 过滤掉已删除的任务
   │  state.data = state.data.filter(item => !deletedIds.includes(item.task_id))
   └─ 清空 selectedIds
      store.dispatch(sendJobSelectionSlice.actions.clearSelection())

9. UI 反馈
   ├─ 表格重新渲染(已删除的行消失)
   ├─ 选中状态清除
   └─ 显示成功消息:"成功删除 N 个任务"

7. 设计决策

7.1 为什么使用确认对话框?

  • 防止误操作:删除是不可逆操作,需要二次确认
  • 用户体验:明确告知将删除多少个任务
  • 行业最佳实践:关键操作都应有确认机制

7.2 为什么批量删除而不是逐个删除?

  • 性能考虑:减少 HTTP 请求次数
  • 原子性:批量操作更容易保证数据一致性
  • 后端设计:API 已支持批量删除(接收数组参数)

7.3 为什么在删除后清空 selectedIds?

  • 逻辑合理性:已删除的任务不应继续保持选中状态
  • 防止二次操作:避免用户再次点击删除时操作到不存在的任务
  • 参考重试功能:deleteSendJobThunk 内部已实现此逻辑

7.4 错误处理策略

  • 前置校验:在操作前检查是否有选中任务
  • API 错误:catch 捕获并显示友好的错误消息
  • 日志记录:console.error 记录详细错误信息便于调试

8. 与重试功能的对比

特性 重试功能 (handleRetry) 删除功能 (handleDelete)
操作方式 逐个任务重试 批量删除
API 调用 retrySendTask(taskId) 循环调用 deleteSendTask(taskIds) 一次性调用
状态更新 更新任务状态为 SENDING 从列表中移除任务
确认对话框 不需要 需要(不可逆操作)
选中状态 不清空 清空 selectedIds
错误提示 "重试任务失败,请稍后再试" "删除任务失败,请稍后再试"
成功提示 "成功提交 N 个重试任务" "成功删除 N 个任务"

9. 测试要点

9.1 功能测试

  • 未选中任务时点击删除,显示警告消息
  • 选中单个任务后点击删除,显示确认对话框
  • 选中多个任务后点击删除,显示正确的任务数量
  • 确认对话框中点击"取消",不执行删除
  • 确认对话框中点击"确定",成功删除任务
  • 删除成功后,表格中的任务消失
  • 删除成功后,selectedIds 被清空
  • API 失败时,显示错误消息

9.2 边界测试

  • 删除全部任务后,列表为空
  • 网络异常时的错误处理
  • 并发删除(快速多次点击删除按钮)

9.3 用户体验测试

  • 确认对话框的文案清晰易懂
  • 成功/失败消息显示时机正确
  • 删除按钮在没有选中任务时的反馈

10. 后续优化建议

10.1 功能增强

  1. 软删除:实现撤销功能,允许用户恢复误删的任务
  2. 批量操作进度:显示删除进度条(任务数量多时)
  3. 权限控制:根据用户角色控制删除按钮的可见性
  4. 审计日志:记录删除操作的用户和时间戳

10.2 性能优化

  1. 防抖处理:防止用户快速多次点击删除按钮
  2. 乐观更新:先更新 UI,再调用 API(提升响应速度)
  3. 分页处理:删除后自动刷新当前页数据

10.3 用户体验优化

  1. 快捷键支持:支持 Delete 键快速删除
  2. 批量操作提示:删除数量超过阈值时额外提醒
  3. 加载状态:删除过程中显示 loading 状态

11. 总结

实现要点

  1. 复用现有架构:充分利用已有的 Redux 模板和 thunk 工厂
  2. 参考重试功能:删除功能与重试功能结构相似,但有关键差异
  3. 用户体验优先:添加确认对话框防止误操作
  4. 完善错误处理:各个环节都有相应的错误捕获和用户提示

修改文件

  • src/pages/patient/components/OutputActionPanel.tsx
    • 导入 Modal 组件和 deleteSendJobThunk
    • 新增 handleDelete 方法
    • 绑定删除按钮的 onClick 事件

无需修改(已存在)

  • src/states/output/sendJob/slices/sendJobSlice.ts - deleteSendJobThunk
  • src/API/output/sendJobActions.ts - deleteSendTask API
  • src/domain/output/sendJob.ts - SendJob 数据模型

关键代码行数

  • 新增代码:约 30 行
  • 修改代码:1 行(添加 onClick)
  • 总变更:轻量级,高度可维护

文档版本:v1.0
创建时间:2025-10-11
最后更新:2025-10-11
作者:Cline