回收站功能.md 20 KB

回收站功能实现文档

📋 一、现状分析

已实现部分

通过代码分析,发现回收站功能已有基础框架:

UI组件

  • Bin.tsx - 回收站主页面
  • BinOperationPanel.tsx - 操作面板容器
  • BinActionPanel.tsx - 磁盘信息和操作按钮

⚠️ 待完善部分

  • 缺少专门的状态管理(binSlice)
  • 缺少API接口实现
  • 过滤功能未实现
  • 操作逻辑为空(TODO注释)

🎯 二、参与者清单

1. 前端组件层

页面组件

  • Bin.tsx - 回收站主页面容器
  • BinOperationPanel.tsx - 操作面板容器

UI组件

  • WorklistTable - 表格展示组件(复用)
  • BinActionPanel - 操作按钮组件
  • SearchPanel - 搜索过滤组件(复用)
  • GenericPagination - 分页组件(复用)
  • PatientPortraitFloat - 患者照片浮窗(复用)

2. 状态管理层

Redux Slices(需新建)

  • binEntitiesSlice - 回收站数据实体
  • binFiltersSlice - 过滤条件
  • binPaginationSlice - 分页状态
  • binSelectionSlice - 选择状态
  • binUISlice - UI状态
  • binDiskInfoSlice - 磁盘信息状态

共享Slices(复用)

  • searchSlice - 搜索条件管理
  • thumbnailListSlice - 缩略图列表

3. API服务层(需新建)

文件: src/API/patient/binActions.ts

函数清单:

  • fetchBinDiskInfo() - 获取磁盘信息
  • fetchBinList() - 获取回收站列表
  • fetchBinStudy() - 获取单个检查详情
  • restoreBinStudies() - 恢复条目
  • deleteBinStudies() - 删除条目
  • clearBin() - 清空回收站

4. 数据模型层

类型定义(部分复用,部分新建)

  • Task/Study - 检查数据结构(复用)
  • BinFilter - 回收站过滤条件(新建)
  • DiskInfo - 磁盘信息(新建)

🔄 三、交互流程

用户操作流程

用户进入回收站
    ↓
加载数据(列表+磁盘信息)
    ↓
【可选】应用过滤条件 → 刷新列表
    ↓
【可选】选择条目
    ↓
执行操作:
    ├─ 恢复选中 → API调用 → 刷新列表
    ├─ 删除选中 → 确认对话框 → API调用 → 刷新列表
    └─ 清空回收站 → 确认对话框 → API调用 → 刷新列表

数据流图

sequenceDiagram
    participant U as 用户
    participant C as Bin组件
    participant S as Redux Store
    participant A as API Service
    participant B as 后端服务

    U->>C: 进入回收站
    C->>S: dispatch(fetchBinThunk)
    S->>A: fetchBinList()
    A->>B: GET /recycle_bin/study
    B-->>A: 返回数据
    A-->>S: 数据+total
    S-->>C: 更新state
    C->>S: dispatch(fetchDiskInfo)
    S->>A: fetchBinDiskInfo()
    A->>B: GET /recycle_bin/disk_info
    B-->>A: 磁盘信息
    A-->>S: 磁盘数据
    S-->>C: 更新state
    C-->>U: 显示列表+磁盘信息
    
    U->>C: 选择条目+点击恢复
    C->>S: dispatch(restoreBinThunk)
    S->>A: restoreBinStudies([ids])
    A->>B: POST /recycle_bin/study
    B-->>A: 成功响应
    A-->>S: 操作结果
    S->>S: 刷新列表
    S-->>C: 更新state
    C-->>U: 显示成功提示

📊 四、数据结构设计

回收站状态结构

// src/states/patient/bin/types/binFilter.ts
export interface BinFilter {
  id?: string;
  name?: string;
  acc_no?: string;
  start_time?: string;
  end_time?: string;
  page?: number;
  page_size?: number;
}

// 磁盘信息
export interface DiskInfo {
  total: number;          // 总容量(字节)
  total_str: string;      // 格式化字符串
  free: number;           // 剩余空间(字节)
  free_str: string;       // 格式化字符串
  recycle_bin: number;    // 回收站占用(字节)
  recycle_bin_str: string; // 格式化字符串
}

// 磁盘信息状态
export interface BinDiskInfoState {
  data: DiskInfo | null;
  loading: boolean;
  error: string | null;
}

API数据结构

// src/API/patient/binActions.ts

// 磁盘信息响应
export interface DiskInfoResponse {
  code: string;
  description: string;
  solution: string;
  data: DiskInfo;
}

// 回收站列表响应
export interface BinListResponse {
  code: string;
  description: string;
  solution: string;
  data: {
    '@type': string;
    count: number;
    studies: Study[];
  };
}

📝 五、实现待办清单

阶段一:API层实现 ✅

  • 创建 src/API/patient/binActions.ts
  • 实现 fetchBinDiskInfo() - 获取磁盘信息
  • 实现 fetchBinList() - 获取回收站列表
  • 实现 fetchBinStudy() - 获取检查详情
  • 实现 restoreBinStudies() - 批量恢复
  • 实现 deleteBinStudies() - 批量删除
  • 实现 clearBin() - 清空回收站

阶段二:状态管理实现 ✅

  • 创建 src/states/patient/bin/ 目录
  • 创建类型定义 types/binFilter.ts
  • 创建 slices/binSlice.ts - 回收站主slice
  • 创建 slices/binDiskInfoSlice.ts - 磁盘信息slice
  • 实现fetchBinThunk - 数据获取
  • 实现restoreBinThunk - 恢复操作
  • 实现deleteBinThunk - 删除操作
  • 实现clearBinThunk - 清空操作
  • 在store中注册bin相关reducers

阶段三:UI组件完善 ✅

  • 修改 Bin.tsx - 接入状态管理
  • 修改 BinOperationPanel.tsx - 接入磁盘信息
  • 修改 BinActionPanel.tsx - 实现操作逻辑
  • 添加确认对话框
  • 添加操作成功/失败提示
  • 实现过滤功能
  • 实现分页功能
  • 实现表格行选择
  • 添加患者照片显示功能

阶段四:测试与优化

  • 编写组件单元测试
  • 编写集成测试
  • 测试边界情况
  • 性能优化
  • 代码审查

🔍 六、执行流程详解

1. 初始化流程

用户点击"回收站"标签
    ↓
Bin组件挂载
    ↓
useEffect触发
    ↓
并行执行:
├─ dispatch(fetchBinThunk()) → 获取列表数据
└─ dispatch(fetchDiskInfoThunk()) → 获取磁盘信息
    ↓
Redux更新state
    ↓
组件重新渲染
    ↓
显示表格+磁盘信息+操作按钮

2. 过滤流程

用户输入过滤条件
    ↓
SearchPanel组件更新
    ↓
dispatch(setFilters())
    ↓
useEffect监听filters变化
    ↓
dispatch(fetchBinThunk(filters))
    ↓
重新获取数据
    ↓
更新表格显示

3. 恢复流程

用户选择条目
    ↓
dispatch(setSelectedIds())
    ↓
用户点击"恢复"按钮
    ↓
显示确认对话框
    ↓
用户确认
    ↓
dispatch(restoreBinThunk(selectedIds))
    ↓
调用API: POST /recycle_bin/study
    ↓
成功后:
├─ 显示成功提示
├─ 清空选择
└─ 刷新列表和磁盘信息

4. 删除流程

用户选择条目
    ↓
用户点击"删除"按钮
    ↓
显示确认对话框(警告:永久删除)
    ↓
用户确认
    ↓
dispatch(deleteBinThunk(selectedIds))
    ↓
调用API: DELETE /recycle_bin/study
    ↓
成功后:
├─ 显示成功提示
├─ 清空选择
└─ 刷新列表和磁盘信息

5. 清空流程

用户点击"清空"按钮
    ↓
显示确认对话框(警告:清空所有)
    ↓
用户确认
    ↓
dispatch(clearBinThunk())
    ↓
调用API: DELETE /recycle_bin/clear
    ↓
成功后:
├─ 显示成功提示
└─ 刷新列表和磁盘信息(应为空)

🧪 七、测试方案

功能测试场景

1. 数据加载测试

  • ✅ 进入回收站页面,验证列表数据正确加载
  • ✅ 验证磁盘信息正确显示
  • ✅ 验证空状态时的UI显示
  • ✅ 验证加载失败时的错误提示

2. 过滤功能测试

  • ✅ 按患者ID过滤
  • ✅ 按患者姓名过滤
  • ✅ 按登记号过滤
  • ✅ 按时间范围过滤
  • ✅ 组合条件过滤
  • ✅ 清空过滤条件

3. 选择功能测试

  • ✅ 单行选择
  • ✅ 多行选择
  • ✅ 全选
  • ✅ 取消选择
  • ✅ 未选择时操作按钮禁用

4. 恢复功能测试

  • ✅ 恢复单个条目
  • ✅ 批量恢复多个条目
  • ✅ 恢复成功后列表更新
  • ✅ 恢复失败时错误提示
  • ✅ 取消恢复操作

5. 删除功能测试

  • ✅ 删除单个条目
  • ✅ 批量删除多个条目
  • ✅ 删除成功后列表更新
  • ✅ 删除失败时错误提示
  • ✅ 取消删除操作
  • ✅ 确认对话框正确显示警告信息

6. 清空功能测试

  • ✅ 清空回收站
  • ✅ 清空后列表为空
  • ✅ 清空后磁盘信息更新
  • ✅ 清空失败时错误提示
  • ✅ 取消清空操作
  • ✅ 确认对话框正确显示警告信息

7. 分页测试

  • ✅ 切换页码
  • ✅ 修改每页条数
  • ✅ 分页信息正确显示
  • ✅ 跳转到指定页

8. 响应式测试

  • ✅ 桌面端布局正确
  • ✅ 移动端布局正确
  • ✅ 抽屉菜单功能正常

🐛 八、潜在问题分析

边界情况

1. 空数据状态

问题:回收站为空时的UI处理 解决方案

  • 显示空状态提示组件
  • 禁用清空按钮
  • 保持磁盘信息显示

2. 大量数据

问题:回收站包含大量条目时的性能 解决方案

  • 实现虚拟滚动
  • 优化分页加载
  • 添加加载骨架屏

3. 并发操作

问题:用户快速连续点击操作按钮 解决方案

  • 操作进行时禁用按钮
  • 使用loading状态管理
  • 防抖处理

4. 网络异常

问题:API调用失败的处理 解决方案

  • 显示错误提示
  • 提供重试机制
  • 保持页面状态不崩溃

异常处理策略

1. API错误处理

try {
  const result = await binAPI.restore(ids);
  if (result.code !== '0x000000') {
    message.error(result.description);
    return;
  }
  message.success('恢复成功');
  dispatch(fetchBinThunk());
} catch (error) {
  message.error('操作失败,请重试');
  console.error('Restore error:', error);
}

2. 状态同步问题

问题:操作后列表与实际状态不一致 解决方案

  • 操作成功后立即刷新列表
  • 使用乐观更新+回滚机制
  • 添加手动刷新按钮

3. 选择状态管理

问题:删除后选中的ID仍存在 解决方案

  • 操作成功后清空选择
  • 过滤无效的选中ID
  • 同步更新相关UI状态

📐 九、架构图

组件关系图

graph TD
    A[PatientManagement] --> B[Bin.tsx]
    B --> C[WorklistTable]
    B --> D[BinOperationPanel]
    B --> E[GenericPagination]
    B --> F[PatientPortraitFloat]
    D --> G[SearchPanel]
    D --> H[BinActionPanel]
    
    B -.Redux.-> I[binEntitiesSlice]
    B -.Redux.-> J[binFiltersSlice]
    B -.Redux.-> K[binPaginationSlice]
    B -.Redux.-> L[binSelectionSlice]
    D -.Redux.-> M[binDiskInfoSlice]
    
    I -.API.-> N[binActions.ts]
    M -.API.-> N
    N -.HTTP.-> O[Backend API]

类图

classDiagram
    class BinPage {
        +diskInfo: DiskInfo
        +binData: Study[]
        +filters: BinFilter
        +selectedIds: string[]
        +handleRowClick()
        +handleRestore()
        +handleDelete()
        +handleClear()
    }
    
    class BinActionPanel {
        +totalCapacity: number
        +freeSpace: number
        +binCapacity: number
        +onDelete()
        +onRestore()
        +onEmpty()
    }
    
    class BinSlice {
        +entities: Study[]
        +filters: BinFilter
        +pagination: Pagination
        +selection: Selection
        +fetchBinThunk()
        +restoreBinThunk()
        +deleteBinThunk()
        +clearBinThunk()
    }
    
    class BinActions {
        +fetchBinDiskInfo()
        +fetchBinList()
        +restoreBinStudies()
        +deleteBinStudies()
        +clearBin()
    }
    
    BinPage --> BinActionPanel
    BinPage --> BinSlice
    BinSlice --> BinActions

📌 十、关键代码示例

1. API实现

// src/API/patient/binActions.ts
export const fetchBinDiskInfo = async (): Promise<DiskInfoResponse> => {
  const response = await axiosInstance.get('/auth/recycle_bin/disk_info');
  return response.data;
};

export const fetchBinList = async (
  page: number,
  pageSize: number,
  filter: BinFilter
): Promise<{ items: Study[]; total: number }> => {
  const response = await axiosInstance.get('/auth/recycle_bin/study', {
    params: {
      page,
      page_size: pageSize,
      id: filter.id,
      name: filter.name,
      acc_no: filter.acc_no,
      start_time: filter.start_time,
      end_time: filter.end_time,
    },
  });
  
  const { studies, count } = response.data.data;
  return { items: studies, total: count };
};

export const restoreBinStudies = async (studyIds: string[]): Promise<void> => {
  const response = await axiosInstance.post('/auth/recycle_bin/study', studyIds);
  if (response.data.code !== '0x000000') {
    throw new Error(response.data.description);
  }
};

export const deleteBinStudies = async (studyIds: string[]): Promise<void> => {
  const response = await axiosInstance.delete('/auth/recycle_bin/study', {
    data: studyIds,
  });
  if (response.data.code !== '0x000000') {
    throw new Error(response.data.description);
  }
};

export const clearBin = async (): Promise<void> => {
  const response = await axiosInstance.delete('/auth/recycle_bin/clear');
  if (response.data.code !== '0x000000') {
    throw new Error(response.data.description);
  }
};

2. Slice实现

// src/states/patient/bin/slices/binSlice.ts
export const fetchBinThunk = createFetchThunk<BinFilter, Study>(
  'bin',
  async ({ page, pageSize, filters }) => {
    const { items, total } = await fetchBinList(page, pageSize, filters);
    return { data: items, total };
  }
);

export const restoreBinThunk = createAsyncThunk(
  'bin/restore',
  async (ids: string[], { dispatch }) => {
    await restoreBinStudies(ids);
    dispatch(selectionSlice.actions.clearSelection());
    dispatch(fetchBinThunk());
    dispatch(fetchDiskInfoThunk());
  }
);

export const deleteBinThunk = createAsyncThunk(
  'bin/delete',
  async (ids: string[], { dispatch }) => {
    await deleteBinStudies(ids);
    dispatch(selectionSlice.actions.clearSelection());
    dispatch(fetchBinThunk());
    dispatch(fetchDiskInfoThunk());
  }
);

export const clearBinThunk = createAsyncThunk(
  'bin/clear',
  async (_, { dispatch }) => {
    await clearBin();
    dispatch(selectionSlice.actions.clearSelection());
    dispatch(fetchBinThunk());
    dispatch(fetchDiskInfoThunk());
  }
);

3. 组件实现

// src/pages/patient/Bin.tsx 关键部分
const BinPage: React.FC = () => {
  const dispatch = useAppDispatch();
  const binData = useSelector((state: RootState) => state.binEntities.data);
  const selectedIds = useSelector((state: RootState) => state.binSelection.selectedIds);
  const diskInfo = useSelector((state: RootState) => state.binDiskInfo.data);
  
  useEffect(() => {
    dispatch(fetchBinThunk());
    dispatch(fetchDiskInfoThunk());
  }, []);
  
  const handleRestore = () => {
    if (selectedIds.length === 0) {
      message.warning('请先选择要恢复的条目');
      return;
    }
    
    Modal.confirm({
      title: '确认恢复',
      content: `确定要恢复选中的 ${selectedIds.length} 个条目吗?`,
      onOk: async () => {
        try {
          await dispatch(restoreBinThunk(selectedIds)).unwrap();
          message.success('恢复成功');
        } catch (error) {
          message.error('恢复失败,请重试');
        }
      },
    });
  };
  
  const handleDelete = () => {
    if (selectedIds.length === 0) {
      message.warning('请先选择要删除的条目');
      return;
    }
    
    Modal.confirm({
      title: '确认删除',
      content: `确定要永久删除选中的 ${selectedIds.length} 个条目吗?此操作不可恢复!`,
      okText: '确认删除',
      okType: 'danger',
      onOk: async () => {
        try {
          await dispatch(deleteBinThunk(selectedIds)).unwrap();
          message.success('删除成功');
        } catch (error) {
          message.error('删除失败,请重试');
        }
      },
    });
  };
  
  const handleClear = () => {
    Modal.confirm({
      title: '确认清空回收站',
      content: '确定要清空回收站吗?此操作将永久删除所有内容,不可恢复!',
      okText: '确认清空',
      okType: 'danger',
      onOk: async () => {
        try {
          await dispatch(clearBinThunk()).unwrap();
          message.success('清空成功');
        } catch (error) {
          message.error('清空失败,请重试');
        }
      },
    });
  };
  
  // ...其他代码
};

✅ 十一、总结

回收站功能实现完成了以下内容:

新建文件

  1. API层src/API/patient/binActions.ts - 完整的API接口实现
  2. 类型定义src/states/patient/bin/types/binFilter.ts - 过滤器类型
  3. 状态管理
    • src/states/patient/bin/slices/binSlice.ts - 主slice
    • src/states/patient/bin/slices/binDiskInfoSlice.ts - 磁盘信息slice

修改文件

  1. 页面组件src/pages/patient/Bin.tsx - 完整接入状态管理
  2. 操作面板
    • src/pages/patient/components/BinOperationPanel.tsx - 接入磁盘信息
    • src/pages/patient/components/BinActionPanel.tsx - 实现操作逻辑
  3. Store配置src/states/store.ts - 注册bin相关reducers

复用组件

  • WorklistTable - 表格显示
  • SearchPanel - 搜索过滤
  • GenericPagination - 分页
  • PatientPortraitFloat - 患者照片

核心功能

✅ 数据加载(列表+磁盘信息) ✅ 搜索过滤(ID、姓名、登记号、时间范围) ✅ 分页展示 ✅ 行选择(单选、多选) ✅ 恢复操作(批量) ✅ 删除操作(批量,二次确认) ✅ 清空回收站(二次确认) ✅ 错误处理和用户提示

技术特点

  • 参考worklist和historylist的实现模式
  • 使用统一的列表模板创建slices
  • 完善的错误处理和loading状态
  • 所有危险操作都有二次确认
  • 响应式设计支持移动端

🎉 十二、实现完成状态

✅ 已完成的功能模块

API层 (100%)

  • ✅ fetchBinDiskInfo - 获取磁盘信息
  • ✅ fetchBinList - 获取回收站列表(支持过滤和分页)
  • ✅ fetchBinStudy - 获取单个检查详情
  • ✅ restoreBinStudies - 批量恢复条目
  • ✅ deleteBinStudies - 批量删除条目
  • ✅ clearBin - 清空回收站

状态管理层 (100%)

  • ✅ binEntitiesSlice - 实体数据管理
  • ✅ binFiltersSlice - 过滤条件管理
  • ✅ binPaginationSlice - 分页状态管理
  • ✅ binSelectionSlice - 选择状态管理
  • ✅ binUISlice - UI状态管理
  • ✅ binDiskInfoSlice - 磁盘信息管理
  • ✅ 所有thunk函数(fetch、restore、delete、clear)
  • ✅ 在store中注册所有reducers

UI组件层 (100%)

  • ✅ Bin.tsx - 主页面完整实现
  • ✅ BinOperationPanel.tsx - 操作面板实现
  • ✅ BinActionPanel.tsx - 操作按钮实现
  • ✅ 复用WorklistTable、SearchPanel等组件
  • ✅ 响应式布局(桌面/移动端)
  • ✅ 患者照片浮窗功能

交互逻辑 (100%)

  • ✅ 数据加载与刷新
  • ✅ 搜索过滤功能
  • ✅ 分页导航
  • ✅ 行选择交互
  • ✅ 恢复操作(含确认对话框)
  • ✅ 删除操作(含警告对话框)
  • ✅ 清空操作(含警告对话框)
  • ✅ 操作成功/失败提示
  • ✅ 错误处理机制

📋 待测试项目

功能测试

  • 端到端功能测试
  • 边界情况测试
  • 错误处理测试
  • 性能测试

集成测试

  • 与worklist的交互测试
  • 与historylist的交互测试
  • API集成测试

🚀 下一步工作

  1. 测试阶段

    • 编写单元测试
    • 执行集成测试
    • 用户验收测试
  2. 优化阶段

    • 性能优化
    • 用户体验优化
    • 代码审查
  3. 文档完善

    • 更新用户手册
    • 编写开发者文档
    • 记录已知问题

实现时间: 2025年10月15日
实现状态: ✅ 核心功能已完成,待测试验收
技术栈: React + Redux Toolkit + Ant Design + TypeScript