Просмотр исходного кода

feat (1.25.0 -> 1.26.0): 实现诊断报告批量下载功能

- 新增诊断报告批量下载模态框组件,支持日期范围选择和批量下载报告
- 在操作面板中添加批量下载报告按钮,集成到历史列表界面
- 修改报告下载API,将 downloadReportXlsx 从POST改为GET方法,调整参数传递方式
- 新增诊断报告批量下载服务,处理下载逻辑和数据验证
- 新增日期范围验证工具类,确保下载日期范围的有效性
- 新增诊断报告批量下载相关的TypeScript类型定义
- 完善Redux状态管理,添加诊断报告批量下载slice和状态处理
- 更新Redux store配置,集成新的状态管理模块

改动文件:
- docs/实现/诊断报告批量下载功能实现文档.md
- src/components/DiagnosticReportBatchDownloadModal.tsx
- src/services/diagnosticReportBatchDownloadService.ts
- src/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice.ts
- src/types/diagnosticReportBatchDownload.ts
- src/utils/dateRangeValidator.ts
- src/API/report/ReportActions.ts
- src/pages/patient/components/ActionPanel.tsx
- src/states/store.ts
- CHANGELOG.md
- package.json
dengdx 2 недель назад
Родитель
Сommit
bb4ca93c31

+ 43 - 0
CHANGELOG.md

@@ -2,6 +2,49 @@
 
 
 本项目的所有重要变更都将记录在此文件中。
 本项目的所有重要变更都将记录在此文件中。
 
 
+## [1.26.0] - 2025-12-24 17:24
+
+### 新增 (Added)
+- **诊断报告批量下载功能** - 实现完整的诊断报告批量下载系统,支持日期范围选择和批量下载
+  - 新增诊断报告批量下载模态框组件,支持日期范围选择和批量下载报告
+  - 在操作面板中添加批量下载报告按钮,集成到历史列表界面
+  - 修改报告下载API,将 downloadReportXlsx 从POST改为GET方法,调整参数传递方式
+  - 新增诊断报告批量下载服务,处理下载逻辑和数据验证
+  - 新增日期范围验证工具类,确保下载日期范围的有效性
+  - 新增诊断报告批量下载相关的TypeScript类型定义
+  - 完善Redux状态管理,添加诊断报告批量下载slice和状态处理
+  - 更新Redux store配置,集成新的状态管理模块
+
+**核心功能实现:**
+- 批量下载界面:用户可以选择日期范围,批量下载指定时间段内的诊断报告
+- API优化:调整下载接口为GET方法,提升兼容性和易用性
+- 用户界面集成:在患者历史列表页面添加批量下载按钮
+- 数据验证:内置日期范围验证,确保输入数据的有效性
+- 状态管理:完整的Redux状态管理,支持下载状态跟踪和错误处理
+
+**技术实现:**
+- 新增组件层:DiagnosticReportBatchDownloadModal 模态框组件
+- 服务层扩展:diagnosticReportBatchDownloadService 处理下载业务逻辑
+- 状态管理扩展:diagnosticReportBatchDownloadSlice 处理UI状态
+- 类型安全:完善的TypeScript类型定义和接口
+- 工具类:dateRangeValidator 提供日期验证功能
+- API层更新:ReportActions.ts 调整下载接口实现
+
+**改动文件:**
+- docs/实现/诊断报告批量下载功能实现文档.md (新增)
+- src/components/DiagnosticReportBatchDownloadModal.tsx (新增)
+- src/services/diagnosticReportBatchDownloadService.ts (新增)
+- src/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice.ts (新增)
+- src/types/diagnosticReportBatchDownload.ts (新增)
+- src/utils/dateRangeValidator.ts (新增)
+- src/API/report/ReportActions.ts (修改)
+- src/pages/patient/components/ActionPanel.tsx (修改)
+- src/states/store.ts (修改)
+- CHANGELOG.md
+- package.json (版本更新: 1.25.0 -> 1.26.0)
+
+---
+
 ## [1.25.0] - 2025-12-24 12:42
 ## [1.25.0] - 2025-12-24 12:42
 
 
 ### 新增 (Added)
 ### 新增 (Added)

+ 483 - 0
docs/实现/诊断报告批量下载功能实现文档.md

@@ -0,0 +1,483 @@
+# 📋 诊断报告批量下载功能实现思路文档
+
+## 🎯 功能概述
+在historylist页面的ActionPanel中添加批量下载按钮,实现诊断报告的批量下载功能。支持按日期查询、多选报告、分页显示、进度提示,适配Electron、Cordova、浏览器三端。
+
+## 📝 需求整理
+
+### 功能详细描述
+- **功能入口**: historylist的右侧ActionPanel,即 `src/pages/patient/components/ActionPanel.tsx` 中提供一个批量下载按钮
+- **用户操作**: 点击按钮后会打开批量下载Modal,在Modal中可以让用户选择开始时间和结束时间
+- **时间选择**: Modal中提供日期时间选择器,让用户手动选择下载的时间范围
+- **下载功能**: 调用downloadReportXlsx API下载用户选择时间范围内的所有报告为XLSX格式
+- **进度提示**: 下载过程中显示进度提示
+- **技术要求**: 可以参考诊断报告的pdf下载,适配三端,包括electron、cordova(android)、浏览器
+
+### 核心功能点
+1. 在ActionPanel添加批量下载按钮(仅historylist页面显示)
+2. 创建DiagnosticReportBatchDownloadModal组件
+3. 实现日期时间范围选择功能
+4. 实现批量下载逻辑,调用downloadReportXlsx API
+5. 实现下载进度显示和错误处理
+6. 跨平台适配(Electron/Cordova/浏览器)
+
+## 🔌 涉及的API
+
+### 报告相关API
+- **`getReportList(params: ReportQueryParams)`**
+  - 接口: `POST /dr/api/v1/auth/report`
+  - 参数: `{start_time?, end_time?, page?, page_size?}`
+  - 返回: `ReportListResponse`
+  - 用途: 分页查询报告列表(可选,用于调试或扩展)
+
+- **`downloadReportXlsx(params: {start_time, end_time})`**
+  - 接口: `GET /dr/api/v1/auth/report/xls`
+  - 参数: `{start_time: string, end_time: string}` (查询参数)
+  - 返回: `Blob` (XLSX文件)
+  - 用途: 批量下载指定时间范围内的报告为XLSX格式
+
+### 工具API(可选)
+- **`fetchDiagnosisReportContent(studyId: string)`**
+  - 接口: `GET /dr/api/v1/auth/study/{id}/report_content`
+  - 返回: `ReportContentResponse`
+  - 用途: 获取单个报告的完整内容(可选,用于调试)
+
+- **`downloadDiagnosisReportPdf(studyId: string)`**
+  - 接口: `GET /dr/api/v1/auth/study/{id}/report`
+  - 返回: `Blob` (PDF文件)
+  - 用途: 下载单个报告的PDF(参考实现)
+
+## 🏗️ 架构设计
+
+### 核心组件关系
+```
+ActionPanel (入口)
+    ↓
+DiagnosticReportBatchDownloadModal (UI容器)
+    ↓
+├── DateRangePicker (日期选择)
+├── DownloadProgress (下载进度)
+└── DiagnosticReportBatchDownloadService (业务逻辑)
+    ↓
+    ├── DateRangeCalculator (日期范围计算)
+    └── DownloadManager (跨平台下载)
+```
+
+### 状态管理设计
+```typescript
+interface DiagnosticReportBatchDownloadState {
+  modalVisible: boolean;
+  downloading: boolean;
+  progress: number;
+  error?: string;
+  startTime?: string;
+  endTime?: string;
+}
+```
+
+## 👥 功能参与者分析(从粗到细)
+
+### UI层组件
+- **ActionPanel.tsx**: 添加批量下载按钮,处理Modal显示
+- **DiagnosticReportBatchDownloadModal**: 新建的批量下载Modal组件
+  - 日期时间选择器组件
+  - 下载进度显示组件
+  - 确认/取消按钮
+
+### 状态管理
+- **Redux Slice**: 诊断报告批量下载相关的状态管理
+  - Modal显示状态(modalVisible: boolean)
+  - 下载状态(downloading: boolean)
+  - 下载进度(progress: number)
+  - 错误信息(error?: string)
+  - 日期范围(startTime, endTime)
+
+### API层
+- **downloadReportXlsx**: 诊断报告批量下载XLSX API
+- **getReportList**: 报告列表API(可选,用于验证)
+
+### 服务层
+- **downloadManager**: 跨平台下载管理器
+- **DiagnosticReportBatchDownloadService**: 诊断报告批量下载业务逻辑服务
+  - 调用下载API
+  - 处理进度和错误
+
+### 工具类
+- **DateRangeValidator**: 日期范围验证工具
+- **ProgressTracker**: 下载进度跟踪工具
+- **FileNamer**: 文件命名生成工具
+
+## 📋 TODO List
+
+### 新建文件
+- [x] `src/components/DiagnosticReportBatchDownloadModal.tsx` - 诊断报告批量下载Modal组件
+- [x] `src/states/patient/diagnosticReportBatchDownloadSlice.ts` - Redux状态管理
+- [x] `src/services/diagnosticReportBatchDownloadService.ts` - 诊断报告批量下载业务逻辑
+- [x] `src/utils/dateRangeValidator.ts` - 日期范围验证工具
+- [x] `src/types/diagnosticReportBatchDownload.ts` - 诊断报告批量下载类型定义
+
+### 修改文件
+- [x] `src/pages/patient/components/ActionPanel.tsx` - 添加诊断报告批量下载按钮和Modal触发
+- [x] `src/API/report/ReportActions.ts` - 确保downloadReportXlsx API导出正确
+- [x] `src/states/store.ts` - 添加diagnosticReportBatchDownload reducer
+
+### 国际化
+- [ ] 添加诊断报告批量下载Modal相关的i18n键值对
+
+## 📊 Mermaid 图表
+
+### 序列图 - 完整下载流程
+```mermaid
+sequenceDiagram
+    participant U as 用户
+    participant AP as ActionPanel
+    participant MODAL as DiagnosticReportBatchDownloadModal
+    participant DRBS as DiagnosticReportBatchDownloadService
+    participant RS as Redux Store
+    participant AS as API Service
+    participant DM as DownloadManager
+
+    U->>AP: 点击诊断报告批量下载按钮
+    AP->>MODAL: 打开Modal
+    MODAL->>RS: 设置modalVisible=true
+
+    U->>MODAL: 选择开始时间和结束时间
+    MODAL->>RS: 更新日期范围
+
+    U->>MODAL: 点击下载按钮
+    MODAL->>DRBS: 调用批量下载服务
+    DRBS->>AS: 调用downloadReportXlsx API
+    AS-->>DRBS: 返回XLSX数据
+
+    DRBS->>DM: 调用downloadManager下载
+    DM-->>DRBS: 下载进度更新
+    DRBS->>RS: 更新下载进度
+    RS-->>MODAL: 显示进度状态
+
+    DM-->>DRBS: 下载完成
+    DRBS->>RS: 下载完成
+    RS-->>MODAL: 显示完成提示
+    MODAL->>RS: 设置modalVisible=false
+```
+
+### 流程图 - 用户操作流程
+```mermaid
+flowchart TD
+    A[用户进入historylist页面] --> B{点击诊断报告批量下载按钮?}
+    B -->|是| C[打开批量下载Modal]
+    B -->|否| A
+
+    C --> D[显示默认日期范围]
+    D --> E{用户修改日期范围?}
+    E -->|是| F[更新日期范围]
+    E -->|否| G{点击下载按钮?}
+
+    F --> G
+    G -->|是| H[验证日期范围]
+    G -->|否| D
+
+    H --> I{日期范围有效?}
+    I -->|否| J[显示日期错误提示]
+    I -->|是| K[调用downloadReportXlsx API]
+
+    J --> D
+    K --> L{API成功?}
+    L -->|是| M[显示下载进度]
+    L -->|否| N[显示下载失败]
+
+    M --> O[调用downloadManager下载]
+    O --> P{下载成功?}
+    P -->|是| Q[显示成功提示]
+    P -->|否| R[显示失败提示]
+
+    Q --> S[关闭Modal]
+    R --> D
+    N --> D
+    S --> A
+```
+
+### 类图 - 组件关系
+```mermaid
+classDiagram
+    class ActionPanel {
+        +handleDiagnosticReportBatchDownload()
+        +render()
+    }
+
+    class DiagnosticReportBatchDownloadModal {
+        +handleDateRangeChange()
+        +handleDownload()
+        +render()
+    }
+
+    class DiagnosticReportBatchDownloadService {
+        +downloadReports(dateRange)
+    }
+
+    class DateRangeValidator {
+        +validateRange(range)
+    }
+
+    class DownloadManager {
+        +download(options)
+    }
+
+    class DiagnosticReportBatchDownloadSlice {
+        +setModalVisible()
+        +setDownloading()
+        +setProgress()
+        +setError()
+        +setDateRange()
+    }
+
+    ActionPanel --> DiagnosticReportBatchDownloadModal
+    DiagnosticReportBatchDownloadModal --> DiagnosticReportBatchDownloadService
+    DiagnosticReportBatchDownloadModal --> DateRangeValidator
+    DiagnosticReportBatchDownloadModal --> DiagnosticReportBatchDownloadSlice
+    DiagnosticReportBatchDownloadService --> DownloadManager
+    DiagnosticReportBatchDownloadService --> DiagnosticReportBatchDownloadSlice
+    DiagnosticReportBatchDownloadService --> API
+```
+
+## 🌊 交互泳道图
+
+```
+用户                    ActionPanel           DiagnosticReportBatchDownloadModal     DiagnosticReportBatchDownloadService    Redux Store          API Service         DownloadManager
+  |                          |                        |                        |                     |                     |                     |
+  |---点击诊断报告批量下载按钮------->|                        |                        |                     |                     |                     |
+  |                          |---打开Modal------------>|                        |                     |                     |                     |
+  |                          |                        |---设置modalVisible=true|                     |                     |                     |
+  |                          |                        |                        |---更新modal状态----->|                     |                     |
+  |                          |                        |<-----------------------|<--------------------|                     |
+  |                          |                        |---显示Modal------------>|                     |                     |                     |
+  |                          |                        |                        |                     |                     |                     |
+  |---选择日期范围---------->|                        |                        |                     |                     |                     |
+  |                          |                        |---更新日期范围-------->|                     |                     |                     |
+  |                          |                        |                        |---保存日期范围------>|                     |                     |
+  |                          |                        |<-----------------------|<--------------------|                     |
+  |                          |                        |                        |                     |                     |                     |
+  |---点击下载按钮---------->|                        |                        |                     |                     |                     |
+  |                          |                        |---调用批量下载服务---->|                     |                     |                     |
+  |                          |                        |                        |---调用downloadReportXlsx|                  |                     |
+  |                          |                        |                        |                     |---API调用---------->|                     |
+  |                          |                        |                        |                     |<--------------------|                     |
+  |                          |                        |                        |<--------------------|---返回XLSX数据----->|                     |
+  |                          |                        |                        |---调用downloadManager|                     |                     |
+  |                          |                        |                        |                     |                     |---开始下载--------->|
+  |                          |                        |                        |                     |                     |                     |---文件保存|
+  |                          |                        |                        |                     |                     |<--------------------|<--------|
+  |                          |                        |                        |<--------------------|---更新下载进度----->|                     |
+  |                          |                        |---显示下载进度-------->|                     |                     |                     |
+  |                          |                        |                        |                     |                     |                     |
+  |                          |                        |<-----------------------|---下载完成---------->|                     |
+  |                          |                        |---显示完成提示-------->|                     |                     |                     |
+  |                          |                        |---关闭Modal------------>|                     |                     |                     |
+  |                          |                        |                        |---设置modalVisible=false|                  |                     |
+```
+
+## 💾 数据流
+
+### 下载阶段数据流
+```
+选中study列表 → DateRangeCalculator → 日期范围 → downloadReportXlsx API → XLSX数据 → DownloadManager → 文件保存
+                                                                 ↓
+                                                         进度更新 → Redux Store → ActionPanel显示
+```
+
+### 状态管理数据流
+```
+用户操作 → ActionPanel → BatchDownloadService → Redux Store → UI状态更新
+```
+
+## 📊 相关数据结构
+
+### 诊断报告批量下载相关类型
+```typescript
+// 日期范围
+interface DateRange {
+  start_time: string;
+  end_time: string;
+}
+
+// 诊断报告批量下载状态
+interface DiagnosticReportBatchDownloadState {
+  downloading: boolean;
+  progress: number;
+  error?: string;
+  startTime?: string;
+  endTime?: string;
+}
+
+// Study数据结构(简化)
+interface StudyItem {
+  StudyID: string;
+  StudyDate?: string;
+  StudyTime?: string;
+}
+```
+
+### 现有数据结构复用
+- `DownloadOptions`: 下载选项
+- `DownloadResult`: 下载结果
+- `ReportQueryParams`: 报告查询参数(用于API调用)
+
+## 🚀 执行流程
+
+### 起点
+用户在historylist页面的ActionPanel中点击批量下载按钮
+
+### 完整执行流程
+1. **入口触发**: 用户点击ActionPanel中的批量下载按钮
+2. **Modal打开**: ActionPanel触发打开DiagnosticReportBatchDownloadModal
+3. **初始状态**: Modal显示默认的日期时间范围(例如最近7天)
+4. **用户选择**: 用户可以修改开始时间和结束时间
+5. **验证范围**: 点击下载前验证日期范围的有效性
+6. **调用API**: 使用用户选择的日期范围调用downloadReportXlsx API
+7. **开始下载**: 使用downloadManager进行跨平台文件下载
+8. **进度显示**: Modal中实时显示下载进度状态
+9. **完成处理**: 下载完成后显示成功提示,自动关闭Modal
+
+### 异常处理流程
+- 日期范围无效: 显示错误提示,要求用户重新选择日期范围
+- API调用失败: 显示错误提示,用户可以重试
+- 下载失败: 显示错误信息,用户可以重新选择日期范围重试
+- 网络超时: 显示超时提示,支持重试
+
+## 🧪 测试方案
+
+### 功能测试场景
+1. **正常下载流程**
+   - ✅ 进入historylist页面,先选择一个或多个study
+   - ✅ 点击"批量下载"按钮,按钮显示loading状态
+   - ✅ 自动计算选中study的日期范围
+   - ✅ 调用downloadReportXlsx API下载报告
+   - ✅ 下载完成后显示成功提示
+   - ✅ 文件成功保存到本地
+
+2. **边界情况测试**
+   - ✅ 未选中任何study时点击下载,显示错误提示
+   - ✅ 单个study下载功能正常
+   - ✅ 多个study下载功能正常
+   - ✅ study时间范围异常(结束时间早于开始时间)
+
+3. **异常情况测试**
+   - ✅ 网络错误时的错误提示
+   - ✅ API调用失败的错误处理
+   - ✅ 下载过程中断的错误处理
+   - ✅ 大文件下载超时处理
+
+4. **跨平台测试**
+   - ✅ Electron: 下载到指定目录
+   - ✅ 浏览器: 下载到默认下载目录
+   - ✅ Cordova: 移动端下载功能
+
+### 性能测试
+- ✅ API调用响应时间 < 5秒
+- ✅ 批量下载进度更新流畅
+- ✅ 内存使用在合理范围内
+
+### 兼容性测试
+- ✅ Chrome/Edge/Firefox浏览器
+- ✅ Windows/Mac/Linux桌面端
+- ✅ Android/iOS移动端
+
+## 🎨 UI设计
+
+### 现有UI基础
+- 复用现有的ActionPanel按钮样式
+- 复用现有的loading状态显示
+- 复用现有的Toast/Notification组件
+- 复用现有的错误提示组件
+
+### UI改动设计
+
+#### ActionPanel按钮改动
+- 在historylist页面添加"批量下载"按钮
+- 按钮图标使用现有的下载图标
+- 下载时按钮显示loading状态和禁用
+- 下载完成后恢复正常状态
+
+#### 进度提示设计
+- 使用Toast组件显示下载进度
+- 显示格式: "正在下载诊断报告..."
+- 成功后显示: "诊断报告下载完成"
+- 失败时显示: "下载失败: [错误信息]"
+
+#### 错误提示设计
+- 未选中study: "请先选择要下载报告的检查项目"
+- API错误: "下载失败,请稍后重试"
+- 网络错误: "网络连接失败,请检查网络后重试"
+
+## 🐛 潜在问题分析
+
+### 边界情况
+1. **大文件下载**: XLSX文件可能很大,需要考虑内存使用
+   - 解决: 使用流式下载,显示准确进度
+
+2. **网络超时**: 长列表查询或大文件下载可能超时
+   - 解决: 设置合理的超时时间,提供重试机制
+
+3. **并发下载**: 用户可能同时下载多个文件
+   - 解决: 限制同时下载数量,队列处理
+
+4. **权限问题**: 不同平台的文件写入权限
+   - 解决: 平台特定的错误处理和用户提示
+
+### 异常处理
+1. **API错误**: 后端服务异常
+   - 处理: 统一的错误提示,支持重试
+
+2. **网络中断**: 下载过程中网络断开
+   - 处理: 显示中断提示,支持断点续传(如可能)
+
+3. **磁盘空间不足**: 下载目录空间不足
+   - 处理: 检查可用空间,提前提示
+
+4. **文件名冲突**: 同名文件已存在
+   - 处理: 自动重命名或提示覆盖
+
+### 用户体验优化
+1. **加载状态**: 查询和下载过程中的加载指示
+2. **操作反馈**: 所有操作都有明确的用户反馈
+3. **取消操作**: 支持随时取消正在进行的操作
+4. **历史记录**: 可选功能,记录下载历史
+
+---
+
+## 📝 实现计划总结
+
+### 核心文件清单
+1. **状态管理**: `src/states/patient/diagnosticReportBatchDownloadSlice.ts`
+2. **业务逻辑**: `src/services/diagnosticReportBatchDownloadService.ts`
+3. **工具函数**: `src/utils/dateRangeCalculator.ts`
+4. **类型定义**: `src/types/diagnosticReportBatchDownload.ts`
+5. **UI改动**: `src/pages/patient/components/ActionPanel.tsx`
+
+### 关键技术点
+- Redux状态管理集成
+- 日期范围自动计算
+- 跨平台下载适配
+- 进度显示和错误处理
+- Toast/Notification提示
+
+### 开发优先级
+1. 创建Redux状态管理和类型定义
+2. 实现日期范围计算工具
+3. 实现批量下载业务逻辑服务
+4. 修改ActionPanel添加下载按钮
+5. 完善错误处理和用户体验
+6. 跨平台测试和优化
+
+### 人工测试步骤
+1. 进入historylist页面
+2. 点击ActionPanel中的"批量下载"按钮
+3. 验证Modal正确打开并显示默认日期范围
+4. 修改开始时间和结束时间
+5. 点击下载按钮
+6. 观察Modal中的下载进度显示
+7. 等待下载完成,查看Modal中的完成提示
+8. 验证Modal自动关闭
+9. 验证文件是否成功下载到本地
+10. 测试异常情况(无效日期范围、网络错误等)
+
+这个文档提供了完整的实现思路和详细的技术方案,为后续开发提供了清晰的指导。

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "zsis",
   "name": "zsis",
-  "version": "1.25.0",
+  "version": "1.26.0",
   "private": true,
   "private": true,
   "description": "医学成像系统",
   "description": "医学成像系统",
   "main": "main.js",
   "main": "main.js",

+ 3 - 2
src/API/report/ReportActions.ts

@@ -113,12 +113,13 @@ export const getReportList = async (
 
 
 /**
 /**
  * 批量下载报告(xlsx)
  * 批量下载报告(xlsx)
- * POST /dr/api/v1/auth/report/xls
+ * GET /dr/api/v1/auth/report/xls
  */
  */
 export const downloadReportXlsx = async (
 export const downloadReportXlsx = async (
   params: Pick<ReportQueryParams, 'start_time' | 'end_time'>
   params: Pick<ReportQueryParams, 'start_time' | 'end_time'>
 ): Promise<Blob> => {
 ): Promise<Blob> => {
-  const response = await axiosInstance.post('/auth/report/xls', params, {
+  const response = await axiosInstance.get('/auth/report/xls', {
+    params,
     responseType: 'blob'
     responseType: 'blob'
   });
   });
   return response.data;
   return response.data;

+ 219 - 0
src/components/DiagnosticReportBatchDownloadModal.tsx

@@ -0,0 +1,219 @@
+/**
+ * 诊断报告批量下载Modal组件
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Modal, DatePicker, Button, Progress, Alert, Space, message } from 'antd';
+import { DownloadOutlined, CloseOutlined } from '@ant-design/icons';
+import { useSelector, useDispatch } from 'react-redux';
+import { RootState } from '@/states/store';
+import {
+  setModalVisible,
+  setDownloading,
+  setProgress,
+  setError,
+  setDateRange,
+  initializeDefaultDateRange,
+} from '@/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice';
+import { diagnosticReportBatchDownloadService } from '@/services/diagnosticReportBatchDownloadService';
+import { validateDateRange } from '@/utils/dateRangeValidator';
+import { DateRange } from '@/types/diagnosticReportBatchDownload';
+import dayjs from 'dayjs';
+
+const { RangePicker } = DatePicker;
+
+const DiagnosticReportBatchDownloadModal: React.FC = () => {
+  const dispatch = useDispatch();
+  const {
+    modalVisible,
+    downloading,
+    progress,
+    error,
+    startTime,
+    endTime,
+  } = useSelector((state: RootState) => state.diagnosticReportBatchDownload);
+
+  const [dateRange, setDateRangeState] = useState<[dayjs.Dayjs | null, dayjs.Dayjs | null]>([null, null]);
+
+  // 初始化默认日期范围
+  useEffect(() => {
+    if (modalVisible) {
+      dispatch(initializeDefaultDateRange());
+    }
+  }, [modalVisible, dispatch]);
+
+  // 同步Redux状态到本地状态
+  useEffect(() => {
+    if (startTime && endTime) {
+      setDateRangeState([dayjs(startTime), dayjs(endTime)]);
+    }
+  }, [startTime, endTime]);
+
+  /**
+   * 处理日期范围变化
+   */
+  const handleDateRangeChange = (dates: [dayjs.Dayjs | null, dayjs.Dayjs | null] | null) => {
+    setDateRangeState(dates || [null, null]);
+
+    if (dates && dates[0] && dates[1]) {
+      const dateRangeObj: DateRange = {
+        start_time: dates[0].format('YYYY-MM-DDTHH:mm'),
+        end_time: dates[1].format('YYYY-MM-DDTHH:mm'),
+      };
+      dispatch(setDateRange(dateRangeObj));
+    }
+  };
+
+  /**
+   * 处理下载
+   */
+  const handleDownload = async () => {
+    if (!startTime || !endTime) {
+      message.error('请选择日期范围');
+      return;
+    }
+
+    const validation = validateDateRange({ start_time: startTime, end_time: endTime });
+    if (!validation.isValid) {
+      message.error(validation.error);
+      return;
+    }
+
+    try {
+      dispatch(setDownloading(true));
+      dispatch(setError(undefined));
+      dispatch(setProgress(10)); // 开始进度
+
+      const result = await diagnosticReportBatchDownloadService.downloadReports(
+        { start_time: startTime, end_time: endTime },
+        (progressValue) => {
+          dispatch(setProgress(progressValue));
+        }
+      );
+
+      if (result.success) {
+        message.success('诊断报告下载完成');
+        dispatch(setModalVisible(false));
+      } else {
+        dispatch(setError(result.error));
+        message.error(result.error || '下载失败');
+      }
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : '下载失败';
+      dispatch(setError(errorMessage));
+      message.error(errorMessage);
+    } finally {
+      dispatch(setDownloading(false));
+    }
+  };
+
+  /**
+   * 处理取消
+   */
+  const handleCancel = () => {
+    if (downloading) {
+      // 如果正在下载,询问用户是否确认取消
+      Modal.confirm({
+        title: '确认取消',
+        content: '正在下载中,确定要取消吗?',
+        onOk: () => {
+          dispatch(setModalVisible(false));
+        },
+      });
+    } else {
+      dispatch(setModalVisible(false));
+    }
+  };
+
+  return (
+    <Modal
+      title="批量下载诊断报告"
+      open={modalVisible}
+      onCancel={handleCancel}
+      footer={
+        <Space>
+          <Button
+            onClick={handleCancel}
+            disabled={downloading}
+            icon={<CloseOutlined />}
+          >
+            取消
+          </Button>
+          <Button
+            type="primary"
+            onClick={handleDownload}
+            loading={downloading}
+            disabled={!startTime || !endTime || downloading}
+            icon={<DownloadOutlined />}
+          >
+            {downloading ? '下载中...' : '下载'}
+          </Button>
+        </Space>
+      }
+      width={600}
+      destroyOnClose
+    >
+      <Space direction="vertical" size="large" style={{ width: '100%' }}>
+        {/* 日期范围选择 */}
+        <div>
+          <label style={{ display: 'block', marginBottom: 8, fontWeight: 'bold' }}>
+            选择时间范围:
+          </label>
+          <RangePicker
+            showTime
+            format="YYYY-MM-DD HH:mm"
+            placeholder={['开始时间', '结束时间']}
+            value={dateRange}
+            onChange={handleDateRangeChange}
+            disabled={downloading}
+            style={{ width: '100%' }}
+          />
+          <div style={{ marginTop: 4, fontSize: '12px', color: '#666' }}>
+            请选择要下载诊断报告的时间范围
+          </div>
+        </div>
+
+        {/* 下载进度 */}
+        {downloading && (
+          <div>
+            <label style={{ display: 'block', marginBottom: 8, fontWeight: 'bold' }}>
+              下载进度:
+            </label>
+            <Progress
+              percent={progress}
+              status={progress === 100 ? 'success' : 'active'}
+              strokeColor={{
+                '0%': '#108ee9',
+                '100%': '#87d068',
+              }}
+            />
+          </div>
+        )}
+
+        {/* 错误提示 */}
+        {error && (
+          <Alert
+            message="下载失败"
+            description={error}
+            type="error"
+            showIcon
+            closable
+            onClose={() => dispatch(setError(undefined))}
+          />
+        )}
+
+        {/* 说明信息 */}
+        {!downloading && !error && (
+          <Alert
+            message="下载说明"
+            description="系统将根据您选择的时间范围,从数据库中导出该时间段内的所有诊断报告,并生成Excel文件进行下载。"
+            type="info"
+            showIcon
+          />
+        )}
+      </Space>
+    </Modal>
+  );
+};
+
+export default DiagnosticReportBatchDownloadModal;

+ 29 - 0
src/pages/patient/components/ActionPanel.tsx

@@ -16,6 +16,8 @@ import { Popup } from 'antd-mobile';
 import { setVisible } from '@/states/patient/DiagnosticReport/slice';
 import { setVisible } from '@/states/patient/DiagnosticReport/slice';
 import EditTaskModal from './EditTaskModal';
 import EditTaskModal from './EditTaskModal';
 import { openEditModal } from '@/states/patient/edit/editFormSlice';
 import { openEditModal } from '@/states/patient/edit/editFormSlice';
+import DiagnosticReportBatchDownloadModal from '@/components/DiagnosticReportBatchDownloadModal';
+import { setModalVisible } from '@/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice';
 import { setBusinessFlow } from '@/states/BusinessFlowSlice';
 import { setBusinessFlow } from '@/states/BusinessFlowSlice';
 import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
 import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
 import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister';
 import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister';
@@ -622,6 +624,32 @@ const ActionPanel: React.FC = () => {
         }
         }
         onClick={handleReRegister}
         onClick={handleReRegister}
       />)}
       />)}
+      {currentKey === 'historylist' && (
+        <ActionButton
+          icon={
+            <Icon
+              module="module-patient"
+              name="Export"
+              userId="base"
+              theme={themeType}
+              size="2x"
+              state="normal"
+              /*控制svg图标的大小,暂时使用这种fontSize方式 */
+              style={{fontSize: '48px'}}
+              /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
+              className={`text-[${useToken().token.colorPrimary}]`}
+            />
+          }
+          tooltip={
+            <FormattedMessage
+              id="actionPanel.batchDownloadReports"
+              defaultMessage="批量下载报告"
+            />
+          }
+          onClick={() => dispatch(setModalVisible(true))}
+          data-testid="batch-download-reports-button"
+        />
+      )}
 
 
 
 
       <ActionButton
       <ActionButton
@@ -768,6 +796,7 @@ const ActionPanel: React.FC = () => {
         <DiagnosticReport />
         <DiagnosticReport />
       </Popup>
       </Popup>
       <EditTaskModal />
       <EditTaskModal />
+      <DiagnosticReportBatchDownloadModal />
     </div>
     </div>
   );
   );
 };
 };

+ 109 - 0
src/services/diagnosticReportBatchDownloadService.ts

@@ -0,0 +1,109 @@
+/**
+ * 诊断报告批量下载业务逻辑服务
+ */
+
+import { downloadReportXlsx } from '@/API/report/ReportActions';
+import { downloadManager } from '@/services/download';
+import { DateRange, DownloadResult } from '@/types/diagnosticReportBatchDownload';
+import { formatDateTimeForAPI } from '@/utils/dateRangeValidator';
+
+export class DiagnosticReportBatchDownloadService {
+  /**
+   * 下载诊断报告
+   * @param dateRange 日期范围
+   * @param onProgress 进度回调函数
+   * @returns 下载结果
+   */
+  async downloadReports(
+    dateRange: DateRange,
+    onProgress?: (progress: number) => void
+  ): Promise<DownloadResult> {
+    try {
+      console.log('[DiagnosticReportBatchDownloadService] 开始下载诊断报告:', dateRange);
+
+      // 格式化日期时间为API需要的格式
+      const apiParams = {
+        start_time: formatDateTimeForAPI(dateRange.start_time),
+        end_time: formatDateTimeForAPI(dateRange.end_time),
+      };
+
+      // 调用API获取XLSX文件数据
+      const xlsxBlob = await downloadReportXlsx(apiParams);
+
+      // 生成文件名
+      const fileName = this.generateFileName(dateRange);
+
+      // 使用downloadManager下载文件
+      const downloadResult = await downloadManager.download({
+        data: xlsxBlob,
+        filename: fileName,
+        mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        filters: [
+          { name: 'Excel 文件', extensions: ['xlsx'] },
+        ],
+        title: '保存诊断报告',
+      });
+
+      if (downloadResult.success) {
+        console.log('[DiagnosticReportBatchDownloadService] 诊断报告下载成功');
+        return { success: true, filePath: downloadResult.filePath };
+      } else {
+        console.error('[DiagnosticReportBatchDownloadService] 诊断报告下载失败:', downloadResult.error);
+        return {
+          success: false,
+          error: downloadResult.error || '下载失败'
+        };
+      }
+    } catch (error) {
+      console.error('[DiagnosticReportBatchDownloadService] 下载过程中发生错误:', error);
+      const errorMessage = error instanceof Error ? error.message : '未知错误';
+      return {
+        success: false,
+        error: `下载失败: ${errorMessage}`
+      };
+    }
+  }
+
+  /**
+   * 生成下载文件名
+   * @param dateRange 日期范围
+   * @returns 文件名
+   */
+  private generateFileName(dateRange: DateRange): string {
+    const startDate = new Date(dateRange.start_time);
+    const endDate = new Date(dateRange.end_time);
+
+    const formatDate = (date: Date) => {
+      return date.toISOString().slice(0, 10).replace(/-/g, '');
+    };
+
+    const startStr = formatDate(startDate);
+    const endStr = formatDate(endDate);
+    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
+
+    return `诊断报告_${startStr}_${endStr}_${timestamp}.xlsx`;
+  }
+
+  /**
+   * 验证下载参数
+   * @param dateRange 日期范围
+   * @returns 是否有效
+   */
+  validateDownloadParams(dateRange: DateRange): boolean {
+    const { start_time, end_time } = dateRange;
+
+    if (!start_time || !end_time) {
+      return false;
+    }
+
+    const startDate = new Date(start_time);
+    const endDate = new Date(end_time);
+
+    return !isNaN(startDate.getTime()) &&
+           !isNaN(endDate.getTime()) &&
+           endDate > startDate;
+  }
+}
+
+// 导出单例实例
+export const diagnosticReportBatchDownloadService = new DiagnosticReportBatchDownloadService();

+ 97 - 0
src/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice.ts

@@ -0,0 +1,97 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { DiagnosticReportBatchDownloadState, DateRange } from '@/types/diagnosticReportBatchDownload';
+
+const initialState: DiagnosticReportBatchDownloadState = {
+  modalVisible: false,
+  downloading: false,
+  progress: 0,
+  error: undefined,
+  startTime: undefined,
+  endTime: undefined,
+};
+
+const diagnosticReportBatchDownloadSlice = createSlice({
+  name: 'diagnosticReportBatchDownload',
+  initialState,
+  reducers: {
+    /**
+     * 设置Modal显示状态
+     */
+    setModalVisible(state, action: PayloadAction<boolean>) {
+      state.modalVisible = action.payload;
+      // 关闭Modal时重置状态
+      if (!action.payload) {
+        state.downloading = false;
+        state.progress = 0;
+        state.error = undefined;
+      }
+    },
+
+    /**
+     * 设置下载状态
+     */
+    setDownloading(state, action: PayloadAction<boolean>) {
+      state.downloading = action.payload;
+      if (!action.payload) {
+        state.progress = 0;
+      }
+    },
+
+    /**
+     * 设置下载进度
+     */
+    setProgress(state, action: PayloadAction<number>) {
+      state.progress = Math.max(0, Math.min(100, action.payload));
+    },
+
+    /**
+     * 设置错误信息
+     */
+    setError(state, action: PayloadAction<string | undefined>) {
+      state.error = action.payload;
+      state.downloading = false;
+      state.progress = 0;
+    },
+
+    /**
+     * 设置日期范围
+     */
+    setDateRange(state, action: PayloadAction<DateRange>) {
+      state.startTime = action.payload.start_time;
+      state.endTime = action.payload.end_time;
+    },
+
+    /**
+     * 重置下载状态
+     */
+    resetDownloadState(state) {
+      state.downloading = false;
+      state.progress = 0;
+      state.error = undefined;
+    },
+
+    /**
+     * 初始化默认日期范围(最近7天)
+     */
+    initializeDefaultDateRange(state) {
+      const endDate = new Date();
+      const startDate = new Date();
+      startDate.setDate(endDate.getDate() - 7);
+
+      state.startTime = startDate.toISOString().slice(0, 16); // YYYY-MM-DDTHH:mm格式
+      state.endTime = endDate.toISOString().slice(0, 16);
+    },
+  },
+});
+
+export const {
+  setModalVisible,
+  setDownloading,
+  setProgress,
+  setError,
+  setDateRange,
+  resetDownloadState,
+  initializeDefaultDateRange,
+} = diagnosticReportBatchDownloadSlice.actions;
+
+export default diagnosticReportBatchDownloadSlice.reducer;

+ 2 - 0
src/states/store.ts

@@ -69,6 +69,7 @@ import studyFilterReducer from './patient/DiagnosticReport/studyFilterSlice';
 import templateReducer from './patient/DiagnosticReport/templateSlice';
 import templateReducer from './patient/DiagnosticReport/templateSlice';
 import imageSelectionReducer from './patient/DiagnosticReport/imageSelectionSlice';
 import imageSelectionReducer from './patient/DiagnosticReport/imageSelectionSlice';
 import diagnosticReportReducer from './patient/DiagnosticReport/slice';
 import diagnosticReportReducer from './patient/DiagnosticReport/slice';
+import diagnosticReportBatchDownloadReducer from './patient/DiagnosticReport/diagnosticReportBatchDownloadSlice';
 import permissionReducer from './permissionSlice';
 import permissionReducer from './permissionSlice';
 import i18nReducer from './i18nSlice';
 import i18nReducer from './i18nSlice';
 import featureNotAvailableReducer from './featureNotAvailableSlice';
 import featureNotAvailableReducer from './featureNotAvailableSlice';
@@ -155,6 +156,7 @@ const store = configureStore({
     template: templateReducer,
     template: templateReducer,
     imageSelection: imageSelectionReducer,
     imageSelection: imageSelectionReducer,
     diagnosticReport: diagnosticReportReducer,
     diagnosticReport: diagnosticReportReducer,
+    diagnosticReportBatchDownload: diagnosticReportBatchDownloadReducer,
     permission: permissionReducer,
     permission: permissionReducer,
     i18n: i18nReducer,
     i18n: i18nReducer,
     featureNotAvailable: featureNotAvailableReducer,
     featureNotAvailable: featureNotAvailableReducer,

+ 40 - 0
src/types/diagnosticReportBatchDownload.ts

@@ -0,0 +1,40 @@
+/**
+ * 诊断报告批量下载相关类型定义
+ */
+
+/**
+ * 日期范围
+ */
+export interface DateRange {
+  start_time: string;
+  end_time: string;
+}
+
+/**
+ * 诊断报告批量下载状态
+ */
+export interface DiagnosticReportBatchDownloadState {
+  modalVisible: boolean;
+  downloading: boolean;
+  progress: number;
+  error?: string;
+  startTime?: string;
+  endTime?: string;
+}
+
+/**
+ * 下载请求参数
+ */
+export interface DownloadRequestParams {
+  start_time: string;
+  end_time: string;
+}
+
+/**
+ * 下载结果
+ */
+export interface DownloadResult {
+  success: boolean;
+  error?: string;
+  filePath?: string;
+}

+ 83 - 0
src/utils/dateRangeValidator.ts

@@ -0,0 +1,83 @@
+/**
+ * 日期范围验证工具
+ */
+
+import { DateRange } from '@/types/diagnosticReportBatchDownload';
+
+/**
+ * 验证日期范围
+ * @param dateRange 日期范围对象
+ * @returns 验证结果,包含是否有效和错误信息
+ */
+export interface ValidationResult {
+  isValid: boolean;
+  error?: string;
+}
+
+export const validateDateRange = (dateRange: DateRange): ValidationResult => {
+  const { start_time, end_time } = dateRange;
+
+  // 检查日期是否为空
+  if (!start_time || !end_time) {
+    return {
+      isValid: false,
+      error: '开始时间和结束时间不能为空',
+    };
+  }
+
+  // 检查日期格式
+  const startDate = new Date(start_time);
+  const endDate = new Date(end_time);
+
+  if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
+    return {
+      isValid: false,
+      error: '日期格式无效',
+    };
+  }
+
+  // 检查结束时间是否晚于开始时间
+  if (endDate <= startDate) {
+    return {
+      isValid: false,
+      error: '结束时间必须晚于开始时间',
+    };
+  }
+
+  // 检查日期范围是否合理(不能超过1年)
+  const oneYearInMs = 365 * 24 * 60 * 60 * 1000;
+  if (endDate.getTime() - startDate.getTime() > oneYearInMs) {
+    return {
+      isValid: false,
+      error: '日期范围不能超过1年',
+    };
+  }
+
+  return { isValid: true };
+};
+
+/**
+ * 格式化日期时间为API需要的格式
+ * @param dateTime 日期时间字符串
+ * @returns 格式化后的字符串
+ */
+export const formatDateTimeForAPI = (dateTime: string): string => {
+  const date = new Date(dateTime);
+  return date.toISOString();
+};
+
+/**
+ * 格式化日期时间为显示格式
+ * @param dateTime 日期时间字符串
+ * @returns 显示格式的字符串
+ */
+export const formatDateTimeForDisplay = (dateTime: string): string => {
+  const date = new Date(dateTime);
+  return date.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit',
+  });
+};