# 全文复筛前端开发计划 > **文档版本:** v1.0 > **创建日期:** 2025-11-23 > **最后更新:** 2025-11-23 > **预计工期:** 2.5天 > **开发阶段:** 全文复筛模块 - 前端UI实现 --- ## 📋 目录 - [1. 开发目标](#1-开发目标) - [2. 页面架构](#2-页面架构) - [3. 详细设计](#3-详细设计) - [4. 组件清单](#4-组件清单) - [5. 开发排期](#5-开发排期) - [6. 技术实现](#6-技术实现) - [7. 测试计划](#7-测试计划) --- ## 1. 开发目标 ### 1.1 核心目标 实现全文复筛模块的完整前端UI,包括: - 4个核心页面 - 支持独立运行和衔接标题摘要初筛两种模式 - 实时任务进度监控 - 双模型判断对比 - 简单PDF全文预览 - Excel结果导出 ### 1.2 设计原则 1. **灵活性** - 既能独立运行,也能衔接标题摘要初筛 2. **实时性** - 长时间LLM任务需要实时进度反馈 3. **可视化** - 冲突检测、PICS符合性清晰展示 4. **易用性** - 简化操作流程,降低学习成本 5. **一致性** - 与标题摘要初筛保持UI风格一致 --- ## 2. 页面架构 ### 2.1 路由设计 ``` /asl/fulltext-screening ├── /settings - 设置与启动页面 ├── /progress/:taskId - 任务进度监控页面 ├── /workbench/:taskId - 审核工作台页面 └── /results/:taskId - 结果展示页面 ``` ### 2.2 页面流转 ``` 设置页面 (Settings) ↓ 点击"开始全文复筛" ↓ POST /api/v1/asl/fulltext-screening/tasks ↓ 进度监控页面 (Progress) ↓ 轮询 GET /tasks/:taskId/progress (每3秒) ↓ status === 'completed' ↓ 审核工作台 (Workbench) ↓ 人工审核冲突和待定文献 ↓ PUT /results/:resultId/decision ↓ 点击"完成审核" ↓ 结果展示页面 (Results) ↓ GET /tasks/:taskId/results ↓ GET /tasks/:taskId/export (下载Excel) ``` ### 2.3 灵活导航 用户可以通过左侧导航随时在4个页面之间跳转: - 进度页面未完成时,工作台显示"部分结果" - 任何阶段都可以导出当前状态的Excel - 支持返回设置页面重新开始 --- ## 3. 详细设计 ### 3.1 设置与启动页面 (Settings) #### 3.1.1 页面结构 ``` ┌─────────────────────────────────────────────┐ │ 全文复筛 / 设置与启动 │ ├─────────────────────────────────────────────┤ │ │ │ 📋 PICOS标准 [调整标准] │ │ ┌─────────────────────────────────────────┐ │ │ │ P: 2型糖尿病成人患者 │ │ │ │ I: SGLT2抑制剂 │ │ │ │ C: 安慰剂或常规降糖疗法 │ │ │ │ O: 心血管事件、死亡率 │ │ │ │ S: 随机对照试验 (RCT) │ │ │ └─────────────────────────────────────────┘ │ │ │ │ ⚙️ 模型配置 │ │ ┌─────────────────────────────────────────┐ │ │ │ Model A: [DeepSeek-V3 ▼] │ │ │ │ Model B: [Qwen-Max ▼] │ │ │ │ 并发数: [3 ] │ │ │ └─────────────────────────────────────────┘ │ │ │ │ 📚 全文文献管理 (5篇) [📤 上传PDF] │ │ ┌─────────────────────────────────────────┐ │ │ │ ☑ 文献A.pdf ✅ 已上传 [查看][删除] │ │ │ │ ☑ 文献B.pdf ✅ 已上传 [查看][删除] │ │ │ │ ☑ 文献C.pdf ⏳ 上传中... 45% │ │ │ │ □ 文献D.pdf ❌ 上传失败 [重试] │ │ │ └─────────────────────────────────────────┘ │ │ │ │ [开始全文复筛] ← 大绿色按钮 │ └─────────────────────────────────────────────┘ ``` #### 3.1.2 功能模块 **(1)PICOS标准卡片** - 显示当前PICOS标准(来自项目或手动编辑) - "[调整标准]" 按钮 → 打开编辑弹窗 - 支持临时调整本次筛选标准 **(2)模型配置** - Model A 下拉选择:DeepSeek-V3 / Qwen-Max / GPT-4o / Claude-Sonnet-4 - Model B 下拉选择:同上 - 并发数输入:1-10,默认3 **(3)文献管理表格** - 显示已上传的PDF列表 - 显示上传状态:已上传/上传中/上传失败 - 操作列:查看、删除、重试 - "[📤 上传PDF]" 按钮 → 打开上传弹窗 **(4)开始复筛按钮** - 当所有PDF上传成功后,按钮变为可用(绿色) - 点击后跳转到进度监控页面 #### 3.1.3 PICOS编辑弹窗 **触发**: 点击 "[调整标准]" 按钮 **设计**: ``` ┌─────────────────────────────────────────────┐ │ 📋 编辑PICOS标准 [×] │ ├─────────────────────────────────────────────┤ │ │ │ Population (人群) * │ │ ┌─────────────────────────────────────────┐ │ │ │ 2型糖尿病成人患者 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ Intervention (干预) * │ │ ┌─────────────────────────────────────────┐ │ │ │ SGLT2抑制剂 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ Comparison (对照) │ │ ┌─────────────────────────────────────────┐ │ │ │ 安慰剂或其他常规降糖疗法 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ Outcome (结局) * │ │ ┌─────────────────────────────────────────┐ │ │ │ 心血管事件、全因死亡、卒中复发 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ Study Design (研究设计) * │ │ ┌─────────────────────────────────────────┐ │ │ │ 随机对照试验 (RCT) │ │ │ └─────────────────────────────────────────┘ │ │ │ │ ⚠️ 注意:修改PICOS标准会影响AI筛选结果 │ │ │ │ [取消] [保存修改] │ └─────────────────────────────────────────────┘ ``` **字段说明**: - 所有字段支持多行文本输入 - P/I/O/S 为必填项(*标记) - 保存后自动关闭弹窗,刷新PICOS卡片 #### 3.1.4 上传PDF弹窗 **触发**: 点击 "[📤 上传PDF]" 按钮 **设计**: ``` ┌─────────────────────────────────────────────┐ │ 📤 上传PDF文件 [×] │ ├─────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ │ │ 📁 拖拽文件到此处 │ │ │ │ 或点击选择文件 │ │ │ │ │ │ │ │ • 支持格式:PDF │ │ │ │ • 单个文件最大:50MB │ │ │ │ • 支持批量上传(最多20篇) │ │ │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ 上传列表: │ │ ┌─────────────────────────────────────────┐ │ │ │ ✅ 文献A.pdf (2.3MB) │ │ │ │ ⏳ 文献B.pdf (5.1MB) - 上传中... 67% │ │ │ │ ❌ 文献C.pdf (120MB) - 文件过大 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ [取消] [开始上传] │ └─────────────────────────────────────────────┘ ``` **功能说明**: - 使用 Ant Design `Upload.Dragger` 组件 - 支持拖拽和点击选择 - 自动验证文件格式和大小 - 显示实时上传进度 - 上传完成后自动关闭弹窗,刷新文献列表 --- ### 3.2 任务进度监控页面 (Progress) #### 3.2.1 页面结构 ``` ┌─────────────────────────────────────────────┐ │ 全文复筛 / 任务进度 │ ├─────────────────────────────────────────────┤ │ │ │ 🤖 AI全文复筛进行中... │ │ │ │ ████████████░░░░░░░░ 60% │ │ │ │ 📊 实时统计 │ │ ┌─────────────────────────────────────────┐ │ │ │ 当前进度:3 / 5 篇 │ │ │ │ ✅ 成功:2篇 │ │ │ │ ❌ 失败:1篇 │ │ │ │ ⚠️ 降级模式:0篇 │ │ │ │ │ │ │ │ 💰 Token消耗:45,234 │ │ │ │ 💵 成本:¥0.1523 │ │ │ │ │ │ │ │ ⏱️ 已用时:3分25秒 │ │ │ │ 📈 预计剩余:2分10秒 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ 📝 处理日志 (自动滚动) │ │ ┌─────────────────────────────────────────┐ │ │ │ 10:24:30 ✅ PMID123 处理完成 │ │ │ │ DeepSeek: ✓ Qwen: ✓ │ │ │ │ Token: 12,345 成本: ¥0.0412 │ │ │ │ │ │ │ │ 10:25:15 ⏳ PMID456 正在处理... │ │ │ │ DeepSeek: 处理中... │ │ │ │ │ │ │ │ 10:26:02 ❌ PMID789 处理失败 │ │ │ │ 错误: PDF提取失败 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ [取消任务] [查看已完成结果] │ └─────────────────────────────────────────────┘ ``` #### 3.2.2 功能说明 **(1)进度条** - 显示整体进度百分比 - 使用 Ant Design `Progress` 组件 - 根据 `processedCount / totalCount` 计算 **(2)实时统计卡片** - 当前进度、成功数、失败数、降级数 - Token消耗和成本统计 - 已用时间和预计剩余时间(基于平均处理速度) **(3)处理日志** - 实时显示每篇文献的处理状态 - 自动滚动到最新日志 - 显示详细的错误信息 **(4)操作按钮** - "取消任务" - 中断当前任务 - "查看已完成结果" - 跳转到审核工作台(显示部分结果) #### 3.2.3 技术要点 **轮询机制**: ```typescript const { data: task, refetch } = useQuery({ queryKey: ['fulltextTask', taskId], queryFn: () => api.getTaskProgress(taskId), refetchInterval: (data) => { // 任务进行中:每3秒轮询一次 // 任务完成或失败:停止轮询 return data?.status === 'processing' ? 3000 : false; }, }); // 任务完成后自动跳转 useEffect(() => { if (task?.status === 'completed') { setTimeout(() => { navigate(`/asl/fulltext-screening/workbench/${taskId}`); }, 2000); // 延迟2秒,让用户看到完成状态 } }, [task?.status]); ``` --- ### 3.3 审核工作台页面 (Workbench) #### 3.3.1 页面结构 ``` ┌──────────────────────────────────────────────────────────────┐ │ 全文复筛 / 审核工作台 │ ├──────────────────────────────────────────────────────────────┤ │ │ │ 📋 PICOS标准 (点击展开) ▼ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ P: 2型糖尿病成人患者 | I: SGLT2抑制剂 | ... │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ 📊 统计概览 │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 总数: 5 | 一致: 3 | 冲突: 2 | 待定: 0 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ 🔍 筛选: [全部▼] [冲突▼] [一致▼] 🔎 搜索: [____]│ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 文献 | DS判断(PICS) | Q3判断(PICS) | 冲突 | 决策 | 操作│ │ │ ├────────────────────────────────────────────────────────┤ │ │ │ [+] │ │ │ │ │ │ │ │ │ PMID │ ✓ ✓ ✓ ✓ 纳入 │ ✓ ✓ ✓ ✓ 纳入│ 一致 │纳入 │详情 │ │ │ │ 123 │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────┤ │ │ │ [+] │ │ │ │ │ │ │ │ │ PMID │ ✓ ✓ ✗ ✓ 排除 │ ✓ ✓ ✓ ✓ 纳入│❗冲突│[▼] │详情 │ │ │ │ 456 │ │ │ │ │ │ │ │ │ │ ├──────────────────────────────────────────────────│ │ │ │ └─> │ 💡 冲突原因:对照组(C)判断不一致 │ │ │ │ │ • DS: C不符合(未提及对照组) │ │ │ │ │ • Q3: C符合(安慰剂对照) │ │ │ │ └──────────────────────────────────────────────────│ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ [完成审核,进入结果页面] │ └──────────────────────────────────────────────────────────────┘ ``` #### 3.3.2 功能模块 **(1)PICOS标准折叠面板** - 默认折叠,节省空间 - 点击展开显示完整PICOS标准 - 供审核时参考 **(2)统计概览卡片** - 显示总数、一致数、冲突数、待定数 - 快速了解整体情况 **(3)筛选和搜索** - 按冲突状态筛选:全部/仅冲突/仅一致 - 按决策筛选:全部/纳入/排除/待定 - 搜索:支持PMID、标题关键词 **(4)双模型判断表格** - 显示PICS四维度符合性(✓/✗/?) - 显示双模型结论(纳入/排除) - 冲突行自动高亮(红色背景) - 展开行显示冲突详情 **(5)最终决策列** - 一致文献:自动采用AI决策 - 冲突文献:显示下拉框,需人工决策 - 支持三个选项:纳入/排除/待定 **(6)操作列** - "详情" 按钮 → 打开详情抽屉 #### 3.3.3 详情抽屉设计 **布局**: 右侧滑出(宽度800px) ``` ┌────────────────────────────────────────┐ │ 文献详情 - PMID456 [×] │ ├────────────────────────────────────────┤ │ [AI判断对比] [PDF全文] [12字段详情] │ ← Tab切换 ├────────────────────────────────────────┤ │ │ │ 📄 基本信息 │ │ • 标题:Effect of SGLT2 inhibitors... │ │ • 作者:Zhang W, Li H, Wang Y │ │ • 期刊:JAMA 2023;15(3):e124 │ │ • 年份:2023 │ │ │ │ ───────────────────────────────────── │ │ │ │ 🤖 AI判断对比 │ │ │ │ ┌──────────────┬──────────────┐ │ │ │ DeepSeek-V3 │ Qwen-Max │ │ │ ├──────────────┼──────────────┤ │ │ │ P: ✓ 符合 │ P: ✓ 符合 │ │ │ │ 成人T2DM患 │ 成人T2DM │ │ │ │ 者,年龄... │ 患者 │ │ │ │ │ │ │ │ │ I: ✓ 符合 │ I: ✓ 符合 │ │ │ │ SGLT2抑制剂│ 达格列净 │ │ │ │ 达格列净... │ │ │ │ │ │ │ │ │ │ C: ✗ 不符合 │ C: ✓ 符合 │ ← 冲突│ │ │ 未明确对照 │ 安慰剂对 │ │ │ │ 组,仅提及 │ 照,双盲 │ │ │ │ "常规治疗" │ │ │ │ │ │ │ │ │ │ S: ✓ 符合 │ S: ✓ 符合 │ │ │ │ 随机对照试 │ RCT,多 │ │ │ │ 验,多中心 │ 中心研究 │ │ │ │ │ │ │ │ │ 结论:排除 │ 结论:纳入 │ │ │ └──────────────┴──────────────┘ │ │ │ │ ⚠️ 冲突提示:两个模型在对照组(C)判断 │ │ 上存在分歧,建议查看原文验证 │ │ │ │ ───────────────────────────────────── │ │ │ │ ✏️ 人工决策 │ │ 最终决策:[纳入 ▼] │ │ 决策理由: │ │ ┌──────────────────────────────────┐ │ │ │ 虽然DS判断对照组不符合,但查看 │ │ │ │ 原文后确认为安慰剂对照,符合纳入 │ │ │ │ 标准。 │ │ │ └──────────────────────────────────┘ │ │ │ │ [取消] [保存决策] │ └────────────────────────────────────────┘ ``` **Tab 2: PDF全文预览** ``` ┌────────────────────────────────────────┐ │ 文献详情 - PMID456 [×] │ ├────────────────────────────────────────┤ │ [AI判断对比] [PDF全文] [12字段详情] │ ├────────────────────────────────────────┤ │ │ │ 📄 PDF预览 │ │ ┌────────────────────────────────────┐ │ │ │ │ │ │ │ [PDF内容渲染区域] │ │ │ │ │ │ │ │ 使用 react-pdf 渲染 │ │ │ │ │ │ │ │ 支持: │ │ │ │ • 翻页(上一页/下一页) │ │ │ │ • 缩放(放大/缩小/适应) │ │ │ │ • 跳页(输入页码直接跳转) │ │ │ │ │ │ │ └────────────────────────────────────┘ │ │ │ │ 页码:3 / 15 [<] [>] [跳转__] │ │ 缩放:[100% ▼] [适应宽度] [适应高度] │ │ │ └────────────────────────────────────────┘ ``` **Tab 3: 12字段详情** ``` ┌────────────────────────────────────────┐ │ 文献详情 - PMID456 [×] │ ├────────────────────────────────────────┤ │ [AI判断对比] [PDF全文] [12字段详情] │ ├────────────────────────────────────────┤ │ │ │ 📋 12字段完整性评估 │ │ │ │ ▼ 1. 文献来源 │ │ 存在性: ✅ 完整 │ │ 第一作者: Zhang W │ │ 年份: 2023 │ │ 期刊: JAMA │ │ │ │ ▼ 2. 研究类型 │ │ 存在性: ✅ 完整 │ │ 类型: RCT │ │ │ │ ▶ 3. 研究设计细节 (点击展开) │ │ │ │ ▶ 4. 疾病诊断标准 │ │ │ │ ▶ 5. 人群特征 ⭐ (关键字段) │ │ │ │ ... (共12个字段) │ │ │ └────────────────────────────────────────┘ ``` --- ### 3.4 结果展示页面 (Results) #### 3.4.1 页面结构 ``` ┌─────────────────────────────────────────────┐ │ 全文复筛 / 结果展示 │ ├─────────────────────────────────────────────┤ │ │ │ 📊 总体统计 │ │ ┌───────────┬───────────┬───────────┐ │ │ │ 总计复筛 │ 最终纳入 │ 排除 │ │ │ │ 100 │ 55 │ 45 │ │ │ └───────────┴───────────┴───────────┘ │ │ │ │ 📈 PRISMA流程图统计 │ │ ┌─────────────────────────────────────────┐ │ │ │ 排除原因统计: │ │ │ │ • 排除文献总数: 45篇 │ │ │ │ • 非随机对照研究(S): 5篇 │ │ │ │ • 非目标人群(P): 7篇 │ │ │ │ • 干预/对照不符(I/C): 18篇 │ │ │ │ • 结局指标不符(O): 9篇 │ │ │ │ • 其他原因: 6篇 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ 💰 成本统计 │ │ ┌─────────────────────────────────────────┐ │ │ │ • DeepSeek-V3: Token 245K, 成本 ¥0.82 │ │ │ │ • Qwen-Max: Token 198K, 成本 ¥1.35 │ │ │ │ • 总计: Token 443K, 成本 ¥2.17 │ │ │ │ • 平均单篇成本: ¥0.0217 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ 📋 文献列表 [导出Excel]│ │ ┌─────────────────────────────────────────┐ │ │ │ [最终纳入 (55)] [排除 (45)] ← Tab │ │ │ │ │ │ │ │ 🔎 搜索: [________] [筛选▼] │ │ │ │ │ │ │ │ PMID | 研究ID | 来源 | 决策 | 方式 │ │ │ │ 123 | A1 2021│ ... | 纳入 | AI纳入 │ │ │ │ 456 | B2 2022│ ... | 纳入 | 人工审核 │ │ │ │ ... │ │ │ └─────────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ``` #### 3.4.2 功能说明 **(1)总体统计卡片** - 显示总数、纳入数、排除数 - 大数字突出显示 - 绿色(纳入)、红色(排除)配色 **(2)PRISMA统计卡片** - 按排除原因分类统计 - 符合PRISMA流程图要求 - 便于撰写系统评价报告 **(3)成本统计卡片** - 双模型Token和成本对比 - 显示平均单篇成本 - 便于成本控制和优化 **(4)文献列表** - Tab切换:最终纳入 / 排除 - 显示文献基本信息 - 显示决策方式:AI纳入/AI排除/人工审核 - 支持搜索和筛选 **(5)导出Excel按钮** - 下载4-Sheet Excel报告 - 包含:纳入文献、排除文献、PRISMA统计、成本统计 --- ## 4. 组件清单 ### 4.1 页面组件(Page Components) | 组件名 | 文件路径 | 功能描述 | 预计工作量 | |--------|----------|----------|------------| | `FulltextScreeningSettings` | `pages/FulltextScreeningSettings.tsx` | 设置与启动页面 | 4小时 | | `FulltextScreeningProgress` | `pages/FulltextScreeningProgress.tsx` | 任务进度监控页面 | 3小时 | | `FulltextScreeningWorkbench` | `pages/FulltextScreeningWorkbench.tsx` | 审核工作台页面 | 6小时 | | `FulltextScreeningResults` | `pages/FulltextScreeningResults.tsx` | 结果展示页面 | 3小时 | ### 4.2 功能组件(Feature Components) | 组件名 | 文件路径 | 功能描述 | 预计工作量 | |--------|----------|----------|------------| | `PICOSCard` | `components/PICOSCard.tsx` | PICOS标准展示卡片(可折叠) | 1小时 | | `PICOSEditModal` | `components/PICOSEditModal.tsx` | PICOS编辑弹窗 | 2小时 | | `PDFUploadModal` | `components/PDFUploadModal.tsx` | PDF上传弹窗 | 2小时 | | `LiteratureTable` | `components/LiteratureTable.tsx` | 文献管理表格 | 2小时 | | `ProgressMonitor` | `components/ProgressMonitor.tsx` | 进度监控组件 | 2小时 | | `DualModelJudgmentTable` | `components/DualModelJudgmentTable.tsx` | 双模型判断表格 | 4小时 | | `LiteratureDetailDrawer` | `components/LiteratureDetailDrawer.tsx` | 文献详情抽屉 | 4小时 | | `PDFViewer` | `components/PDFViewer.tsx` | PDF预览组件 | 2小时 | | `FieldsCollapse` | `components/FieldsCollapse.tsx` | 12字段折叠面板 | 2小时 | | `PRISMAStatistics` | `components/PRISMAStatistics.tsx` | PRISMA统计卡片 | 1小时 | ### 4.3 复用组件(Reused Components) | 组件名 | 来源 | 功能描述 | |--------|------|----------| | `ASLLayout` | 标题摘要初筛 | 左侧导航布局 | | `JudgmentBadge` | 标题摘要初筛 | PICOS判断标签(✓/✗/?) | | `ConclusionTag` | 标题摘要初筛 | 决策标签(纳入/排除) | ### 4.4 Hooks(自定义钩子) | Hook名 | 文件路径 | 功能描述 | 预计工作量 | |--------|----------|----------|------------| | `useFulltextTask` | `hooks/useFulltextTask.ts` | 管理全文复筛任务状态 | 1小时 | | `useTaskProgress` | `hooks/useTaskProgress.ts` | 轮询任务进度 | 1小时 | | `useTaskResults` | `hooks/useTaskResults.ts` | 获取任务结果 | 1小时 | --- ## 5. 开发排期 ### 5.1 Day 6 - 基础页面(8小时) #### 上午(4小时) **5.1.1 设置与启动页面基础**(2小时) - [ ] 创建页面文件和路由 - [ ] 实现PICOS展示卡片 - [ ] 实现模型配置表单 - [ ] 实现文献列表表格(基础版) **5.1.2 PICOS编辑和PDF上传弹窗**(2小时) - [ ] 实现PICOS编辑弹窗 - [ ] 实现PDF上传弹窗(Ant Design Upload) - [ ] 集成后端PDF上传API - [ ] 实时显示上传进度 #### 下午(4小时) **5.1.3 任务进度监控页面**(4小时) - [ ] 创建页面文件和路由 - [ ] 实现进度条和统计卡片 - [ ] 实现轮询机制(React Query) - [ ] 实现处理日志滚动显示 - [ ] 任务完成后自动跳转 --- ### 5.2 Day 7 - 核心功能(10小时) #### 上午(5小时) **5.2.1 审核工作台页面基础**(3小时) - [ ] 创建页面文件和路由 - [ ] 实现PICOS折叠面板 - [ ] 实现统计概览卡片 - [ ] 实现筛选和搜索功能 - [ ] 实现双模型判断表格(基础版) **5.2.2 冲突可视化**(2小时) - [ ] 冲突行高亮显示 - [ ] 展开行显示冲突详情 - [ ] 冲突原因文字说明 - [ ] 最终决策下拉框(含验证) #### 下午(5小时) **5.2.3 文献详情抽屉 - AI判断对比Tab**(2小时) - [ ] 创建抽屉组件(Ant Design Drawer) - [ ] 实现基本信息展示 - [ ] 实现双模型判断对比(左右分栏) - [ ] 高亮冲突字段 - [ ] 人工决策表单(含提交) **5.2.4 PDF预览Tab**(2小时) - [ ] 集成react-pdf库 - [ ] 实现基础PDF渲染 - [ ] 实现翻页功能(上一页/下一页/跳页) - [ ] 实现缩放功能(放大/缩小/适应) - [ ] 优化加载性能 **5.2.5 12字段详情Tab**(1小时) - [ ] 实现12字段折叠面板(Ant Design Collapse) - [ ] 显示每个字段的存在性、完整性 - [ ] 支持展开/折叠单个字段 --- ### 5.3 Day 8 - 结果页面与联调(6小时) #### 上午(3小时) **5.3.1 结果展示页面**(3小时) - [ ] 创建页面文件和路由 - [ ] 实现总体统计卡片 - [ ] 实现PRISMA统计卡片 - [ ] 实现成本统计卡片 - [ ] 实现文献列表(Tab切换) - [ ] 实现Excel导出功能 #### 下午(3小时) **5.3.2 前后端联调**(2小时) - [ ] 测试完整流程(设置→进度→工作台→结果) - [ ] 测试PDF上传 - [ ] 测试实时进度轮询 - [ ] 测试人工决策提交 - [ ] 测试Excel导出下载 **5.3.3 Bug修复和优化**(1小时) - [ ] 修复发现的Bug - [ ] 优化UI细节 - [ ] 优化性能(懒加载、虚拟滚动等) - [ ] 补充错误处理和提示 --- ## 6. 技术实现 ### 6.1 技术栈 - **框架**: React 18 + TypeScript 5 - **路由**: React Router DOM v6 - **状态管理**: @tanstack/react-query (React Query v5) - **UI组件**: Ant Design v5 - **PDF预览**: react-pdf v7 - **文件上传**: Ant Design Upload + axios - **样式**: TailwindCSS v3 ### 6.2 关键技术实现 #### 6.2.1 PDF上传 ```typescript import { Upload, message } from 'antd'; import type { UploadFile } from 'antd/es/upload/interface'; const PDFUploadModal = () => { const [fileList, setFileList] = useState([]); const customRequest = async ({ file, onProgress, onSuccess, onError }: any) => { try { const formData = new FormData(); formData.append('file', file); const response = await axios.post( '/api/v1/asl/literatures/upload-pdf', formData, { onUploadProgress: (e) => { const percent = Math.round((e.loaded / e.total!) * 100); onProgress({ percent }); }, } ); onSuccess(response.data); message.success(`${file.name} 上传成功`); } catch (error) { onError(error); message.error(`${file.name} 上传失败`); } }; return ( setFileList(fileList)} beforeUpload={(file) => { // 验证文件大小(最大50MB) const isLt50M = file.size / 1024 / 1024 < 50; if (!isLt50M) { message.error('文件大小不能超过50MB!'); } return isLt50M; }} >

📁

拖拽文件到此处,或点击选择文件

支持格式:PDF | 单个文件最大:50MB | 支持批量上传(最多20篇)

); }; ``` #### 6.2.2 任务进度轮询 ```typescript import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; const useTaskProgress = (taskId: string) => { const navigate = useNavigate(); const { data: task, isLoading } = useQuery({ queryKey: ['fulltextTask', taskId], queryFn: async () => { const res = await axios.get(`/api/v1/asl/fulltext-screening/tasks/${taskId}/progress`); return res.data.data; }, refetchInterval: (data) => { // 任务进行中:每3秒轮询 // 任务完成或失败:停止轮询 return data?.status === 'processing' ? 3000 : false; }, refetchOnWindowFocus: false, }); // 任务完成后自动跳转 useEffect(() => { if (task?.status === 'completed') { setTimeout(() => { navigate(`/asl/fulltext-screening/workbench/${taskId}`); }, 2000); } }, [task?.status, taskId, navigate]); return { task, isLoading }; }; ``` #### 6.2.3 PDF预览组件 ```typescript import { useState } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import 'react-pdf/dist/esm/Page/TextLayer.css'; // 配置PDF.js worker pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; interface PDFViewerProps { url: string; } const PDFViewer: React.FC = ({ url }) => { const [numPages, setNumPages] = useState(0); const [pageNumber, setPageNumber] = useState(1); const [scale, setScale] = useState(1.0); const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { setNumPages(numPages); setPageNumber(1); }; const changePage = (offset: number) => { setPageNumber((prev) => Math.min(Math.max(1, prev + offset), numPages)); }; const changeScale = (newScale: number) => { setScale(Math.min(Math.max(0.5, newScale), 2.0)); }; return (
加载中...
} error={
PDF加载失败
} >
页码:{pageNumber} / {numPages}
{Math.round(scale * 100)}%
); }; export default PDFViewer; ``` #### 6.2.4 双模型判断表格 ```typescript interface DualModelJudgmentTableProps { results: FulltextScreeningResult[]; onUpdateDecision: (resultId: string, decision: string) => void; onOpenDetail: (result: FulltextScreeningResult) => void; } const DualModelJudgmentTable: React.FC = ({ results, onUpdateDecision, onOpenDetail, }) => { const [expandedKeys, setExpandedKeys] = useState([]); const columns: ColumnsType = [ { title: '文献', dataIndex: 'literatureId', key: 'literatureId', render: (_, record) => record.literature.pmid, }, { title: 'DS判断(PICS)', key: 'modelA', render: (_, record) => { const { modelAFields, modelAOverall } = record; return (
); }, }, { title: 'Q3判断(PICS)', key: 'modelB', render: (_, record) => { const { modelBFields, modelBOverall } = record; return (
); }, }, { title: '冲突', key: 'conflict', render: (_, record) => { const hasConflict = record.conflictSeverity !== null; return hasConflict ? ( 冲突 ) : ( 一致 ); }, }, { title: '决策', key: 'decision', render: (_, record) => { const hasConflict = record.conflictSeverity !== null; if (!hasConflict && record.finalDecision) { return ; } return ( ); }, }, { title: '操作', key: 'action', render: (_, record) => ( ), }, ]; return ( { if (expanded) { setExpandedKeys([...expandedKeys, record.id]); } else { setExpandedKeys(expandedKeys.filter((k) => k !== record.id)); } }, expandedRowRender: (record) => { if (!record.conflictFields || record.conflictFields.length === 0) { return null; } return (

💡 冲突详情:

    {record.conflictFields.map((field) => (
  • {field}: Model A vs Model B 判断不一致
  • ))}
); }, rowExpandable: (record) => record.conflictFields && record.conflictFields.length > 0, }} rowClassName={(record) => record.conflictSeverity ? 'bg-red-50' : '' } /> ); }; ``` #### 6.2.5 Excel导出 ```typescript const handleExportExcel = async (taskId: string) => { try { message.loading({ content: '正在生成Excel...', key: 'export' }); const response = await axios.get( `/api/v1/asl/fulltext-screening/tasks/${taskId}/export`, { responseType: 'blob', // 重要:指定响应类型为blob } ); // 创建下载链接 const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; // 从响应头获取文件名 const contentDisposition = response.headers['content-disposition']; const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : `全文复筛结果_${taskId}.xlsx`; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); // 清理 link.remove(); window.URL.revokeObjectURL(url); message.success({ content: '导出成功!', key: 'export' }); } catch (error) { message.error({ content: '导出失败', key: 'export' }); console.error('Export error:', error); } }; ``` --- ## 7. 测试计划 ### 7.1 功能测试 **测试用例**: | 编号 | 测试项 | 测试步骤 | 预期结果 | |------|--------|----------|----------| | T1 | PDF上传 | 上传单个PDF | 上传成功,显示在文献列表 | | T2 | PDF批量上传 | 上传5个PDF | 全部上传成功 | | T3 | PDF大小验证 | 上传>50MB的PDF | 提示文件过大,上传失败 | | T4 | PICOS编辑 | 修改P字段并保存 | PICOS卡片更新 | | T5 | 创建任务 | 点击"开始全文复筛" | 跳转到进度页面 | | T6 | 进度轮询 | 停留在进度页面 | 每3秒更新一次进度 | | T7 | 自动跳转 | 等待任务完成 | 自动跳转到工作台 | | T8 | 冲突可视化 | 查看工作台冲突行 | 红色高亮,显示冲突详情 | | T9 | 人工决策 | 修改冲突文献决策 | 保存成功,刷新列表 | | T10 | PDF预览 | 打开详情抽屉查看PDF | PDF正常渲染,可翻页缩放 | | T11 | Excel导出 | 点击"导出Excel" | 下载4-Sheet Excel文件 | ### 7.2 性能测试 | 测试项 | 测试条件 | 性能指标 | |--------|----------|----------| | 页面加载 | 首次访问 | < 2秒 | | PDF上传 | 10MB文件 | < 5秒 | | 进度轮询 | 轮询100次 | 无内存泄漏 | | PDF渲染 | 15页PDF | < 3秒 | | 表格渲染 | 100条数据 | < 1秒 | ### 7.3 兼容性测试 | 浏览器 | 版本 | 测试结果 | |--------|------|----------| | Chrome | 最新版 | ✅ 通过 | | Firefox | 最新版 | ✅ 通过 | | Edge | 最新版 | ✅ 通过 | | Safari | 最新版 | ⚠️ PDF预览需额外测试 | --- ## 📝 附录 ### A. API接口清单 | 接口 | 方法 | 路径 | 功能 | |------|------|------|------| | 上传PDF | POST | `/api/v1/asl/literatures/upload-pdf` | 上传单个PDF文件 | | 创建任务 | POST | `/api/v1/asl/fulltext-screening/tasks` | 创建全文复筛任务 | | 获取进度 | GET | `/api/v1/asl/fulltext-screening/tasks/:taskId/progress` | 获取任务进度 | | 获取结果 | GET | `/api/v1/asl/fulltext-screening/tasks/:taskId/results` | 获取任务结果列表 | | 更新决策 | PUT | `/api/v1/asl/fulltext-screening/results/:resultId/decision` | 人工更新决策 | | 导出Excel | GET | `/api/v1/asl/fulltext-screening/tasks/:taskId/export` | 导出Excel报告 | ### B. 数据结构 **FulltextScreeningTask**: ```typescript interface FulltextScreeningTask { id: string; projectId: string; status: 'pending' | 'processing' | 'completed' | 'failed'; totalCount: number; processedCount: number; successCount: number; failedCount: number; degradedCount: number; totalTokens: number; totalCost: number; createdAt: Date; startedAt: Date | null; completedAt: Date | null; } ``` **FulltextScreeningResult**: ```typescript interface FulltextScreeningResult { id: string; taskId: string; literatureId: string; literature: { pmid: string; title: string; authors: string; journal: string; publicationYear: number; }; modelAName: string; modelAStatus: 'success' | 'failed'; modelAFields: any; // 12字段 modelAOverall: { overall_decision: 'include' | 'exclude'; overall_reasoning: string; }; modelBName: string; modelBStatus: 'success' | 'failed'; modelBFields: any; modelBOverall: any; conflictFields: string[]; conflictSeverity: 'high' | 'medium' | 'low' | null; finalDecision: 'include' | 'exclude' | 'pending' | null; reviewedBy: string | null; reviewedAt: Date | null; } ``` --- **文档维护**: - 开发过程中如有调整,及时更新本文档 - 每个页面开发完成后,更新完成状态 - 遇到技术难点,记录在文档中 **相关文档**: - [全文复筛开发计划](./04-全文复筛开发计划.md) - [API设计规范](../02-技术设计/02-API设计规范.md) - [数据库设计](../02-技术设计/01-数据库设计.md) - [技术债务清单](../06-技术债务/技术债务清单.md) --- **文档版本历史**: - v1.0 (2025-11-23) - 初始版本,Day 5完成后创建