通过代码分析,发现回收站功能已有基础框架:
✅ UI组件
Bin.tsx
- 回收站主页面BinOperationPanel.tsx
- 操作面板容器BinActionPanel.tsx
- 磁盘信息和操作按钮⚠️ 待完善部分
页面组件
Bin.tsx
- 回收站主页面容器BinOperationPanel.tsx
- 操作面板容器UI组件
WorklistTable
- 表格展示组件(复用)BinActionPanel
- 操作按钮组件SearchPanel
- 搜索过滤组件(复用)GenericPagination
- 分页组件(复用)PatientPortraitFloat
- 患者照片浮窗(复用)Redux Slices(需新建)
binEntitiesSlice
- 回收站数据实体binFiltersSlice
- 过滤条件binPaginationSlice
- 分页状态binSelectionSlice
- 选择状态binUISlice
- UI状态binDiskInfoSlice
- 磁盘信息状态共享Slices(复用)
searchSlice
- 搜索条件管理thumbnailListSlice
- 缩略图列表文件: src/API/patient/binActions.ts
函数清单:
fetchBinDiskInfo()
- 获取磁盘信息fetchBinList()
- 获取回收站列表fetchBinStudy()
- 获取单个检查详情restoreBinStudies()
- 恢复条目deleteBinStudies()
- 删除条目clearBin()
- 清空回收站类型定义(部分复用,部分新建)
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;
}
// 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[];
};
}
src/API/patient/binActions.ts
fetchBinDiskInfo()
- 获取磁盘信息fetchBinList()
- 获取回收站列表fetchBinStudy()
- 获取检查详情restoreBinStudies()
- 批量恢复deleteBinStudies()
- 批量删除clearBin()
- 清空回收站src/states/patient/bin/
目录types/binFilter.ts
slices/binSlice.ts
- 回收站主sliceslices/binDiskInfoSlice.ts
- 磁盘信息sliceBin.tsx
- 接入状态管理BinOperationPanel.tsx
- 接入磁盘信息BinActionPanel.tsx
- 实现操作逻辑用户点击"回收站"标签
↓
Bin组件挂载
↓
useEffect触发
↓
并行执行:
├─ dispatch(fetchBinThunk()) → 获取列表数据
└─ dispatch(fetchDiskInfoThunk()) → 获取磁盘信息
↓
Redux更新state
↓
组件重新渲染
↓
显示表格+磁盘信息+操作按钮
用户输入过滤条件
↓
SearchPanel组件更新
↓
dispatch(setFilters())
↓
useEffect监听filters变化
↓
dispatch(fetchBinThunk(filters))
↓
重新获取数据
↓
更新表格显示
用户选择条目
↓
dispatch(setSelectedIds())
↓
用户点击"恢复"按钮
↓
显示确认对话框
↓
用户确认
↓
dispatch(restoreBinThunk(selectedIds))
↓
调用API: POST /recycle_bin/study
↓
成功后:
├─ 显示成功提示
├─ 清空选择
└─ 刷新列表和磁盘信息
用户选择条目
↓
用户点击"删除"按钮
↓
显示确认对话框(警告:永久删除)
↓
用户确认
↓
dispatch(deleteBinThunk(selectedIds))
↓
调用API: DELETE /recycle_bin/study
↓
成功后:
├─ 显示成功提示
├─ 清空选择
└─ 刷新列表和磁盘信息
用户点击"清空"按钮
↓
显示确认对话框(警告:清空所有)
↓
用户确认
↓
dispatch(clearBinThunk())
↓
调用API: DELETE /recycle_bin/clear
↓
成功后:
├─ 显示成功提示
└─ 刷新列表和磁盘信息(应为空)
问题:回收站为空时的UI处理 解决方案:
问题:回收站包含大量条目时的性能 解决方案:
问题:用户快速连续点击操作按钮 解决方案:
问题: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);
}
问题:操作后列表与实际状态不一致 解决方案:
问题:删除后选中的ID仍存在 解决方案:
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
// 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);
}
};
// 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());
}
);
// 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('清空失败,请重试');
}
},
});
};
// ...其他代码
};
回收站功能实现完成了以下内容:
src/API/patient/binActions.ts
- 完整的API接口实现src/states/patient/bin/types/binFilter.ts
- 过滤器类型src/states/patient/bin/slices/binSlice.ts
- 主slicesrc/states/patient/bin/slices/binDiskInfoSlice.ts
- 磁盘信息slicesrc/pages/patient/Bin.tsx
- 完整接入状态管理src/pages/patient/components/BinOperationPanel.tsx
- 接入磁盘信息src/pages/patient/components/BinActionPanel.tsx
- 实现操作逻辑src/states/store.ts
- 注册bin相关reducers✅ 数据加载(列表+磁盘信息) ✅ 搜索过滤(ID、姓名、登记号、时间范围) ✅ 分页展示 ✅ 行选择(单选、多选) ✅ 恢复操作(批量) ✅ 删除操作(批量,二次确认) ✅ 清空回收站(二次确认) ✅ 错误处理和用户提示
API层 (100%)
状态管理层 (100%)
UI组件层 (100%)
交互逻辑 (100%)
功能测试
集成测试
测试阶段
优化阶段
文档完善
实现时间: 2025年10月15日
实现状态: ✅ 核心功能已完成,待测试验收
技术栈: React + Redux Toolkit + Ant Design + TypeScript