Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/00-系统设计/证据整合V2.0/全景工具箱原型图V5.html
HaHafeng dc6b292308 docs(asl): Complete Tool 3 extraction workbench V2.0 development plan (v1.5)
ASL Tool 3 Development Plan:
- Architecture blueprint v1.5 (6 rounds of architecture review, 13 red lines)
- M1/M2/M3 sprint checklists (Skeleton Pipeline / HITL Workbench / Dynamic Template Engine)
- Code patterns cookbook (9 chapters: Fan-out, Prompt engineering, ACL, SSE dual-track, etc.)
- Key patterns: Fan-out with Last Child Wins, Optimistic Locking, teamConcurrency throttling
- PKB ACL integration (anti-corruption layer), MinerU Cache-Aside, NOTIFY/LISTEN cross-pod SSE
- Data consistency snapshot for long-running extraction tasks

Platform capability:
- Add distributed Fan-out task pattern development guide (7 patterns + 10 anti-patterns)
- Add system-level async architecture risk analysis blueprint
- Add PDF table extraction engine design and usage guide (MinerU integration)
- Add table extraction source code (TableExtractionManager + MinerU engine)

Documentation updates:
- Update ASL module status with Tool 3 V2.0 plan readiness
- Update system status document (v6.2) with latest milestones
- Add V2.0 product requirements, prototypes, and data dictionary specs
- Add architecture review documents (4 rounds of review feedback)
- Add test PDF files for extraction validation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:49:16 +08:00

612 lines
44 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASL全景工具箱与证据合成 V5 - 真独立解耦版</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: { primary: '#1677ff', primaryHover: '#4096ff', bgBase: '#f0f2f5', panelBg: '#ffffff' },
animation: { 'pulse-fast': 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite', }
}
}
}
</script>
<style>
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.drawer-slide-in { transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.drawer-open { transform: translateX(0); }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in { animation: fadeIn 0.3s ease-out forwards; }
.tab-active { color: #1677ff; border-bottom: 2px solid #1677ff; font-weight: 500; }
.tab-inactive { color: #64748b; border-bottom: 2px solid transparent; }
.tab-inactive:hover { color: #1677ff; }
/* PRISMA 连线 */
.prisma-line { width: 2px; height: 24px; background-color: #cbd5e1; margin: 0 auto; position: relative;}
.prisma-line::after { content: ''; position: absolute; bottom: -4px; left: -4px; border-width: 5px; border-style: solid; border-color: #cbd5e1 transparent transparent transparent;}
.prisma-h-line { height: 2px; width: 30px; background-color: #cbd5e1; position: absolute; top: 50%; right: -30px;}
.prisma-h-line::after { content: ''; position: absolute; right: -8px; top: -4px; border-width: 5px; border-style: solid; border-color: transparent transparent transparent #cbd5e1;}
/* Excel 风格输入框 */
.data-grid-input { width: 100%; height: 100%; border: none; outline: none; background: transparent; padding: 6px 8px; font-family: monospace; font-size: 13px; }
.data-grid-input:focus { background: #e6f4ff; box-shadow: inset 0 0 0 1px #1677ff; }
table.excel-table td { padding: 0; border: 1px solid #e2e8f0; }
table.excel-table th { padding: 8px; border: 1px solid #cbd5e1; background-color: #f8fafc; font-weight: 600; font-size: 13px; color: #475569; }
</style>
</head>
<body class="bg-bgBase text-gray-800 font-sans h-screen flex overflow-hidden">
<!-- ================= 左侧导航栏 ================= -->
<aside class="w-64 bg-slate-900 text-white flex flex-col h-full flex-shrink-0 shadow-xl z-20">
<div class="h-16 flex items-center px-6 border-b border-slate-800">
<i class="fa-solid fa-notes-medical text-blue-400 text-xl mr-3"></i>
<span class="text-lg font-bold tracking-wide">AI Clinical ASL</span>
</div>
<div class="p-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">循证医学工具箱 (Toolkit)</div>
<nav class="flex-1 px-3 space-y-1" id="nav-menu">
<button class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-60 cursor-not-allowed text-left rounded-lg">
<i class="fa-solid fa-magnifying-glass-chart w-6 text-center"></i>
<span class="ml-2 font-medium">1: 智能文献检索</span>
<i class="fa-solid fa-check ml-auto text-green-700"></i>
</button>
<button class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-60 cursor-not-allowed text-left rounded-lg">
<i class="fa-solid fa-filter w-6 text-center"></i>
<span class="ml-2 font-medium">2: 标题摘要初筛</span>
<i class="fa-solid fa-check ml-auto text-green-700"></i>
</button>
<div class="my-2 border-t border-slate-800"></div>
<button onclick="switchTool('tool3')" id="nav-tool3" class="w-full flex items-center px-3 py-2.5 bg-blue-600/20 text-blue-400 rounded-lg transition-colors text-left">
<i class="fa-solid fa-file-pdf w-6 text-center"></i>
<span class="ml-2 font-medium">3: 智能提取工作台</span>
</button>
<button onclick="switchTool('tool4')" id="nav-tool4" class="w-full flex items-center px-3 py-2.5 text-slate-300 hover:bg-slate-800 hover:text-white rounded-lg transition-colors text-left">
<i class="fa-solid fa-diagram-project w-6 text-center"></i>
<span class="ml-2 font-medium">4: SR 图表生成器</span>
</button>
<button onclick="switchTool('tool5')" id="nav-tool5" class="w-full flex items-center px-3 py-2.5 text-slate-300 hover:bg-slate-800 hover:text-white rounded-lg transition-colors text-left">
<i class="fa-solid fa-chart-line w-6 text-center"></i>
<span class="ml-2 font-medium">5: Meta 分析引擎</span>
</button>
</nav>
<div class="p-4 border-t border-slate-800">
<div class="text-xs text-slate-500">当前项目: 肺癌综述 (全局模式)</div>
</div>
</aside>
<!-- ================= 右侧工作区 ================= -->
<main class="flex-1 flex flex-col h-full relative">
<!-- 公共 Header -->
<header class="h-16 bg-panelBg shadow-sm flex items-center justify-between px-6 z-10 flex-shrink-0">
<h1 class="text-lg font-semibold text-gray-800" id="header-title">工具 3全文复筛与智能提取工作台</h1>
<div class="flex space-x-3" id="header-actions">
<button class="px-3 py-1.5 bg-white border border-gray-300 rounded text-sm text-gray-600 hover:text-primary transition-colors"><i class="fa-solid fa-file-excel mr-1 text-green-600"></i> 导出数据</button>
</div>
</header>
<!-- 全局 Toast -->
<div id="global-toast" class="fixed top-20 left-1/2 transform -translate-x-1/2 bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded shadow-lg z-50 flex items-center transition-all duration-300 opacity-0 -translate-y-4 pointer-events-none">
<i class="fa-solid fa-circle-check mr-2"></i>
<span id="toast-msg" class="text-sm font-medium">操作成功</span>
</div>
<div class="flex-1 overflow-y-auto p-6 bg-bgBase min-w-[1000px] flex flex-col">
<!-- ======================= 工具 3: 智能提取工作台 ======================= -->
<div id="tool3" class="tool-section flex-1">
<div class="max-w-6xl mx-auto animate-fade-in">
<div class="bg-blue-50 border border-blue-100 p-3 rounded-lg mb-4 text-sm text-gray-700 flex items-start">
<i class="fa-solid fa-circle-info text-blue-500 mt-0.5 mr-2"></i>
<div>请核对 AI 提取结果。只有标记为 <strong>Approved</strong> 的文献才可进入 SR 和 Meta 分析环节。</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<table class="w-full text-left text-sm text-gray-600">
<thead class="bg-gray-50 text-gray-700 text-xs uppercase border-b border-gray-200">
<tr>
<th class="px-4 py-3 font-semibold">第一作者 / 年份</th>
<th class="px-4 py-3 font-semibold">文献标题</th>
<th class="px-4 py-3 font-semibold w-24">PDF解析</th>
<th class="px-4 py-3 font-semibold w-32">提取状态</th>
<th class="px-4 py-3 font-semibold w-24 text-center">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr class="hover:bg-blue-50/30">
<td class="px-4 py-4 font-medium text-gray-800">Gandhi L (2018)</td>
<td class="px-4 py-4 text-primary hover:underline cursor-pointer" onclick="openDrawer()">Pembrolizumab plus Chemotherapy in Metastatic NonSmall-Cell Lung Cancer</td>
<td class="px-4 py-4"><span class="text-xs bg-green-100 text-green-700 px-2 py-1 rounded">成功</span></td>
<td class="px-4 py-4"><span class="text-xs bg-orange-50 text-orange-600 px-2 py-1 rounded border border-orange-200"><span class="w-1.5 h-1.5 inline-block rounded-full bg-orange-500 mr-1 animate-pulse"></span>待核对</span></td>
<td class="px-4 py-4 text-center"><button class="bg-primary text-white text-xs px-3 py-1.5 rounded hover:bg-primaryHover" onclick="openDrawer()">复核提单</button></td>
</tr>
<tr class="hover:bg-blue-50/30">
<td class="px-4 py-4 font-medium text-gray-800">Hellmann MD (2019)</td>
<td class="px-4 py-4 text-gray-600">Nivolumab plus Ipilimumab in Advanced NonSmall-Cell Lung Cancer</td>
<td class="px-4 py-4"><span class="text-xs bg-green-100 text-green-700 px-2 py-1 rounded">成功</span></td>
<td class="px-4 py-4"><span class="text-xs bg-green-50 text-green-600 px-2 py-1 rounded border border-green-200"><i class="fa-solid fa-check-double mr-1"></i>Approved</span></td>
<td class="px-4 py-4 text-center"><button class="border border-gray-300 text-gray-600 text-xs px-3 py-1.5 rounded">查看</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ======================= 工具 4: SR 图表生成器 (V5 解耦升级) ======================= -->
<div id="tool4" class="tool-section hidden flex-1">
<div class="max-w-6xl mx-auto flex gap-6 animate-fade-in">
<!-- 左侧:操作配置区 -->
<div class="w-1/3 shrink-0 space-y-4">
<div class="bg-white p-5 rounded-lg shadow-sm border border-gray-200">
<h3 class="font-bold text-gray-800 mb-4 border-b pb-2">图表类型</h3>
<div class="space-y-2">
<label class="flex items-center p-3 border border-primary bg-blue-50 rounded cursor-pointer">
<input type="radio" checked class="text-primary h-4 w-4">
<span class="ml-3 font-medium text-gray-800 text-sm">PRISMA 2020 流程图</span>
</label>
</div>
</div>
<!-- 💡 V5 核心升级:双通道数据输入 -->
<div class="bg-white p-5 rounded-lg shadow-sm border border-gray-200">
<h3 class="font-bold text-gray-800 mb-3 border-b pb-2">数据源输入 (Data Source)</h3>
<!-- 选项 A: 内部流转 -->
<label class="flex items-center p-2 rounded text-sm text-gray-700 hover:bg-gray-50 cursor-pointer mb-2 border border-transparent has-[:checked]:border-blue-300 has-[:checked]:bg-blue-50 transition-colors">
<input type="radio" name="sr-input" value="auto" class="mr-3 text-primary" onchange="toggleSRUpload(false)">
<div>
<div class="font-medium">关联当前项目流水线</div>
<div class="text-xs text-gray-500 mt-0.5">从初筛与工具3自动汇总数据</div>
</div>
</label>
<!-- 选项 B: 独立上传 -->
<label class="flex items-center p-2 rounded text-sm text-gray-700 hover:bg-gray-50 cursor-pointer border border-transparent has-[:checked]:border-blue-300 has-[:checked]:bg-blue-50 transition-colors">
<input type="radio" name="sr-input" value="manual" checked class="mr-3 text-primary" onchange="toggleSRUpload(true)">
<div>
<div class="font-medium">独立文件上传 (Standalone)</div>
<div class="text-xs text-gray-500 mt-0.5">无需使用上游工具上传Excel直出图</div>
</div>
</label>
<!-- 上传区域 (仅在选项B时显示) -->
<div id="sr-upload-area" class="mt-4 p-4 border-2 border-dashed border-gray-300 rounded-lg text-center bg-gray-50 transition-all">
<i class="fa-solid fa-cloud-arrow-up text-3xl text-gray-400 mb-2"></i>
<p class="text-xs font-medium text-gray-600 mb-1">将整理好的 Excel 拖拽至此处,或</p>
<button class="text-xs bg-white border border-gray-300 px-3 py-1 rounded shadow-sm hover:border-primary hover:text-primary mb-3">选择文件</button>
<div class="border-t border-gray-200 pt-2 mt-2">
<button class="text-xs text-primary hover:underline flex items-center justify-center w-full" onclick="alert('即将下载: SR_Charting_Template.xlsx')">
<i class="fa-solid fa-file-excel mr-1"></i> 下载 PRISMA 标准模板
</button>
</div>
</div>
<button onclick="generatePRISMA()" class="w-full bg-primary hover:bg-primaryHover text-white py-2.5 rounded-lg text-sm font-medium transition-colors mt-4">
<i class="fa-solid fa-wand-magic-sparkles mr-2"></i> 渲染生成图表
</button>
</div>
</div>
<!-- 右侧:渲染结果区 -->
<div class="flex-1 bg-white p-8 rounded-lg shadow-sm border border-gray-200 min-h-[600px] flex flex-col">
<div class="flex justify-between items-center mb-6 border-b pb-3">
<h3 class="text-lg font-bold text-gray-800">渲染结果 (Preview)</h3>
<button class="text-primary text-sm hover:underline"><i class="fa-solid fa-download mr-1"></i>导出 SVG</button>
</div>
<div id="sr-empty" class="flex-1 flex flex-col items-center justify-center text-gray-400">
<i class="fa-solid fa-image text-5xl mb-4 text-gray-200"></i><p>上传数据后点击左侧生成</p>
</div>
<div id="sr-loading" class="hidden flex-1 flex flex-col items-center justify-center text-primary">
<i class="fa-solid fa-circle-notch fa-spin text-4xl mb-4"></i><p class="font-medium">正在解析 Excel 数据并生成拓扑结构...</p>
</div>
<div id="sr-result-prisma" class="hidden flex-1 flex flex-col items-center pb-10 overflow-x-auto">
<h4 class="text-base font-bold text-gray-800 mb-8">PRISMA 2020 Flow Diagram</h4>
<!-- 节点 1 容器 -->
<div class="flex items-start">
<div class="w-64 border-2 border-slate-300 bg-white rounded-md p-3 text-center text-sm shadow-sm z-10">
<strong class="text-gray-700">Records identified</strong><br>
<span class="text-primary font-bold">(n = 1,245)</span>
</div>
<div class="w-12 border-b-2 border-slate-300 mt-6 relative">
<div class="absolute -right-1 -top-1 w-2 h-2 border-t-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
<div class="w-48 border border-red-200 bg-red-50 rounded-md p-3 text-xs shadow-sm text-left">
<strong class="text-red-600">Records removed:</strong><br>Duplicate records (n = 345)
</div>
</div>
<div class="w-64 flex justify-center">
<div class="h-8 border-l-2 border-slate-300 relative">
<div class="absolute -bottom-1 -left-[5px] w-2 h-2 border-b-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
</div>
<!-- 节点 2 容器 -->
<div class="flex items-start">
<div class="w-64 border-2 border-slate-300 bg-white rounded-md p-3 text-center text-sm shadow-sm z-10">
<strong class="text-gray-700">Records screened</strong><br>
<span class="text-primary font-bold">(n = 900)</span>
</div>
<div class="w-12 border-b-2 border-slate-300 mt-6 relative">
<div class="absolute -right-1 -top-1 w-2 h-2 border-t-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
<div class="w-48 border border-red-200 bg-red-50 rounded-md p-3 text-xs shadow-sm text-left">
<strong class="text-red-600">Records excluded:</strong><br>Title/Abstract (n = 700)
</div>
</div>
<div class="w-64 flex justify-center">
<div class="h-8 border-l-2 border-slate-300 relative">
<div class="absolute -bottom-1 -left-[5px] w-2 h-2 border-b-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
</div>
<!-- 节点 3 容器 -->
<div class="flex items-start">
<div class="w-64 border-2 border-slate-300 bg-white rounded-md p-3 text-center text-sm shadow-sm z-10">
<strong class="text-gray-700">Full-text articles assessed</strong><br>
<span class="text-primary font-bold">(n = 200)</span>
</div>
<div class="w-12 border-b-2 border-slate-300 mt-6 relative">
<div class="absolute -right-1 -top-1 w-2 h-2 border-t-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
<div class="w-48 border border-red-200 bg-red-50 rounded-md p-3 text-xs shadow-sm text-left">
<strong class="text-red-600">Reports excluded:</strong><br>Wrong outcomes (n = 50)<br>No PDF (n = 30)
</div>
</div>
<div class="w-64 flex justify-center">
<div class="h-8 border-l-2 border-slate-300 relative">
<div class="absolute -bottom-1 -left-[5px] w-2 h-2 border-b-2 border-r-2 border-slate-300 transform rotate-45"></div>
</div>
</div>
<!-- 节点 4 (最终纳入) -->
<div class="w-64 border-2 border-green-500 bg-green-50 rounded-md p-3 text-center text-sm shadow-md z-10 mr-[240px]">
<strong class="text-green-700">Studies included</strong><br>
<span class="text-green-600 font-bold text-lg">(n = 120)</span>
</div>
</div>
</div>
</div>
</div>
<!-- ======================= 工具 5: Meta 分析量化引擎 (V5 解耦升级) ======================= -->
<div id="tool5" class="tool-section hidden flex-1">
<div class="max-w-7xl mx-auto flex flex-col h-full w-full space-y-4 animate-fade-in">
<!-- 顶部:模型配置 -->
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200 flex items-end gap-6 shrink-0">
<div class="w-64">
<label class="block text-xs font-semibold text-gray-500 mb-1">结局指标数据类型</label>
<select class="w-full text-sm border-gray-300 rounded py-1.5 px-2 border focus:ring-primary"><option>Hazard Ratio (HR) - 预计算效应量</option></select>
</div>
<div class="w-64">
<label class="block text-xs font-semibold text-gray-500 mb-1">统计学模型</label>
<select class="w-full text-sm border-gray-300 rounded py-1.5 px-2 border focus:ring-primary"><option>Random Effects Model (DerSimonian-Laird)</option></select>
</div>
<button onclick="runMetaEngine()" class="bg-purple-600 text-white px-6 py-1.5 rounded hover:bg-purple-700 shadow-sm flex items-center text-sm font-medium ml-auto">
<i class="fa-solid fa-microchip mr-2"></i> 运行 R 引擎计算
</button>
</div>
<!-- 下方:左侧数据表格 + 右侧森林图 -->
<div class="flex flex-1 gap-4 min-h-[500px]">
<!-- 左侧:数据网格 (Excel Style) -->
<div class="w-[500px] bg-white rounded-lg shadow-sm border border-gray-200 flex flex-col overflow-hidden shrink-0">
<!-- 💡 V5 核心升级:数据网格头部工具栏 -->
<div class="p-3 bg-gray-50 border-b border-gray-200 flex justify-between items-center flex-wrap gap-2">
<span class="text-sm font-semibold text-gray-700">数据输入矩阵 (Matrix)</span>
<div class="flex space-x-2">
<button onclick="alert('准备下载: Meta_Analysis_Template_HR.xlsx')" class="text-xs text-gray-500 hover:text-primary transition-colors" title="下载空白模板">
<i class="fa-solid fa-download"></i> 模板
</button>
<div class="w-px h-4 bg-gray-300 my-auto"></div>
<button onclick="simulateFileUpload()" class="text-xs bg-white border border-gray-300 text-gray-700 px-2 py-1 rounded hover:text-primary hover:border-primary shadow-sm transition-colors">
<i class="fa-solid fa-file-import mr-1"></i>上传 Excel
</button>
<button onclick="importDataFromTool3()" class="text-xs bg-blue-50 border border-blue-200 text-primary px-2 py-1 rounded hover:bg-blue-100 shadow-sm transition-colors">
<i class="fa-solid fa-link mr-1"></i>继承工具3
</button>
</div>
</div>
<div class="flex-1 overflow-auto relative">
<!-- 💡 V5 核心升级:未导入时的多通道遮罩 -->
<div id="meta-data-overlay" class="absolute inset-0 bg-white/95 z-20 flex flex-col items-center justify-center p-6 text-center">
<div class="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mb-4 border border-gray-200">
<i class="fa-solid fa-table-cells text-2xl text-gray-400"></i>
</div>
<h3 class="text-sm font-bold text-gray-800 mb-2">数据矩阵为空,请选择输入方式</h3>
<p class="text-xs text-gray-500 mb-6">您可以导入系统内已提取的数据,或者作为独立工具上传本地文件。</p>
<div class="flex gap-3 w-full max-w-[300px]">
<button onclick="importDataFromTool3()" class="flex-1 bg-blue-50 border border-blue-200 text-primary text-xs px-3 py-2.5 rounded shadow-sm hover:bg-blue-100 transition-colors">
<i class="fa-solid fa-link block text-lg mb-1"></i> 继承工具3
</button>
<button onclick="simulateFileUpload()" class="flex-1 bg-white border border-gray-300 text-gray-700 text-xs px-3 py-2.5 rounded shadow-sm hover:border-primary hover:text-primary transition-colors">
<i class="fa-solid fa-cloud-arrow-up block text-lg mb-1"></i> 上传 Excel
</button>
</div>
<button class="text-xs text-gray-400 hover:text-primary mt-6 underline" onclick="alert('即将下载标准数据录入模板')">没有模板?下载各种效应量标准 Excel 模板</button>
</div>
<table class="w-full excel-table text-left" id="meta-data-table">
<thead><tr><th>Study ID</th><th>HR</th><th>Lower CI</th><th>Upper CI</th></tr></thead>
<tbody id="meta-tbody">
<!-- 留空,通过 JS 填充 -->
</tbody>
</table>
</div>
<div class="bg-gray-50 p-2 text-[10px] text-gray-500 border-t border-gray-200 flex justify-between">
<span><i class="fa-solid fa-pen mr-1"></i>支持双击单元格直接修改数据</span>
<span><span id="row-count">0</span></span>
</div>
</div>
<!-- 右侧:森林图展示 -->
<div class="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 relative flex flex-col min-w-[500px]">
<!-- R 引擎加载遮罩 -->
<div id="r-engine-overlay" class="absolute inset-0 bg-slate-900/95 z-20 hidden flex-col items-center justify-center text-white rounded-lg">
<i class="fa-brands fa-r-project text-5xl text-blue-400 mb-3 animate-pulse"></i>
<h3 class="text-lg font-bold">Calling R Statistical Engine</h3>
<p class="text-xs text-slate-400 font-mono mt-2">Packaging JSON Data...</p>
<p class="text-xs text-slate-400 font-mono mt-1">Executing meta::metagen() ...</p>
</div>
<div id="meta-empty" class="absolute inset-0 flex flex-col items-center justify-center text-gray-400 z-10 bg-white rounded-lg">
<i class="fa-solid fa-chart-column text-4xl mb-3 text-gray-200"></i><p class="text-sm">导入数据并运行引擎后,在此生成森林图</p>
</div>
<!-- 渲染结果 -->
<div id="meta-result-view" class="hidden flex-1 flex flex-col p-6">
<div class="flex justify-between items-end mb-4 border-b pb-2">
<div>
<h3 class="text-base font-bold text-gray-800">Forest Plot (Overall Survival)</h3>
<div class="text-xs font-bold text-purple-700 mt-1">Pooled HR: 0.63 [0.52, 0.76]</div>
</div>
<div class="text-right text-xs">
<span class="text-gray-500">Heterogeneity:</span>
<span class="font-mono bg-yellow-100 text-yellow-800 px-1 rounded border border-yellow-200">I²=72%, P=0.01</span>
</div>
</div>
<div class="flex-1 relative pt-6 pb-10 px-4 border border-slate-200 rounded bg-slate-50 overflow-hidden text-sm">
<div class="absolute right-2 top-2 text-[10px] text-gray-400 border px-1">R Plot Area</div>
<!-- 坐标轴 -->
<div class="absolute top-4 bottom-8 w-px bg-gray-400 z-0" style="left: 50%;"></div> <!-- Null line (1.0) -->
<div class="absolute bottom-6 left-8 right-8 h-px bg-gray-600 z-0"></div>
<div class="absolute bottom-1 text-xs text-gray-500" style="left: 25%; transform: translateX(-50%);">0.5</div>
<div class="absolute bottom-1 text-xs text-gray-500" style="left: 50%; transform: translateX(-50%);">1.0</div>
<div class="absolute bottom-1 text-xs text-gray-500" style="left: 75%; transform: translateX(-50%);">1.5</div>
<!-- 数据点行 -->
<div class="relative h-8 w-full flex items-center z-10">
<div class="w-32 text-xs font-medium shrink-0">Gandhi 2018</div>
<div class="absolute h-px bg-slate-800" style="left: 38%; right: 58%;"></div>
<div class="absolute w-3 h-3 bg-blue-600 opacity-80" style="left: 49%; transform: translateX(-50%);"></div>
</div>
<div class="relative h-8 w-full flex items-center z-10">
<div class="w-32 text-xs font-medium shrink-0">Hellmann 2019</div>
<div class="absolute h-px bg-slate-800" style="left: 65%; right: 24%;"></div>
<div class="absolute w-2 h-2 bg-blue-600 opacity-80" style="left: 79%; transform: translateX(-50%);"></div>
</div>
<div class="relative h-8 w-full flex items-center z-10">
<div class="w-32 text-xs font-medium shrink-0">Socinski 2018</div>
<div class="absolute h-px bg-slate-800" style="left: 64%; right: 24%;"></div>
<div class="absolute w-2.5 h-2.5 bg-blue-600 opacity-80" style="left: 78%; transform: translateX(-50%);"></div>
</div>
<div class="relative h-8 w-full flex items-center z-10">
<div class="w-32 text-xs font-medium shrink-0">Reck 2021</div>
<div class="absolute h-px bg-slate-800" style="left: 50%; right: 40%;"></div>
<div class="absolute w-2.5 h-2.5 bg-blue-600 opacity-80" style="left: 59%; transform: translateX(-50%);"></div>
</div>
<!-- 菱形合并结果 -->
<div class="relative h-10 w-full flex items-center z-10 mt-2 border-t border-slate-300 pt-2">
<div class="w-32 text-xs font-bold shrink-0">Random Effects</div>
<div class="absolute h-3 flex items-center justify-center" style="left: 52%; right: 44%;">
<div class="w-3 h-3 bg-purple-600 rotate-45 transform origin-center"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- ================= 侧边抽屉 (工具3使用) ================= -->
<div id="drawer-backdrop" class="fixed inset-0 bg-slate-900/40 z-40 hidden" onclick="closeDrawer()"></div>
<div id="extraction-drawer" class="fixed top-0 right-0 h-full w-[600px] bg-white shadow-2xl z-50 drawer-slide-in flex flex-col">
<!-- 保持不变 -->
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center bg-slate-50">
<div>
<span class="text-xs bg-orange-100 text-orange-600 px-2 py-0.5 rounded border border-orange-200 font-medium">Pending Review</span>
<h2 class="text-base font-bold text-gray-800 mt-1">Pembrolizumab plus Chemotherapy in NSCLC</h2>
</div>
<button class="text-gray-400 hover:text-gray-800 p-2" onclick="closeDrawer()"><i class="fa-solid fa-xmark text-lg"></i></button>
</div>
<div class="flex-1 overflow-y-auto p-6 space-y-6 bg-white">
<div class="border border-gray-200 rounded-lg shadow-sm relative overflow-hidden">
<div class="absolute left-0 top-0 bottom-0 w-1 bg-primary"></div>
<div class="p-4">
<h3 class="text-sm font-bold text-gray-800 mb-3 pl-2">实验组总人数 (Intervention N)</h3>
<input type="text" value="410" class="w-full p-2 border border-blue-300 bg-blue-50 rounded focus:ring-primary outline-none font-bold text-primary font-mono mb-3">
<div class="bg-slate-50 border border-slate-200 p-3 rounded text-sm text-slate-600 italic font-serif">
<span class="text-[10px] bg-slate-200 text-slate-500 px-1 rounded uppercase not-italic mr-2">Quote</span>
"...A total of <span class="bg-yellow-200 font-bold px-1 rounded">410</span> patients were randomly assigned to receive pembrolizumab..."
</div>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 bg-gray-50 flex justify-end gap-3 shrink-0">
<button class="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded bg-white hover:bg-gray-100" onclick="closeDrawer()">取消</button>
<button class="px-4 py-2 text-sm text-white bg-green-600 rounded hover:bg-green-700 shadow flex items-center" onclick="approveAndClose()">
<i class="fa-solid fa-check-double mr-2"></i> 核准保存
</button>
</div>
</div>
<!-- ================= 脚本逻辑 ================= -->
<script>
function showToast(msg) {
const toast = document.getElementById('global-toast');
document.getElementById('toast-msg').innerText = msg;
toast.classList.remove('opacity-0', '-translate-y-4', 'pointer-events-none');
setTimeout(() => toast.classList.add('opacity-0', '-translate-y-4', 'pointer-events-none'), 2500);
}
function switchTool(toolId) {
['tool3', 'tool4', 'tool5'].forEach(id => {
document.getElementById(id).classList.add('hidden');
document.getElementById('nav-' + id).className = 'w-full flex items-center px-3 py-2.5 text-slate-300 hover:bg-slate-800 hover:text-white rounded-lg transition-colors text-left';
});
const activeTool = document.getElementById(toolId);
activeTool.classList.remove('hidden');
const animatedInner = activeTool.querySelector('.animate-fade-in');
if(animatedInner) {
animatedInner.classList.remove('animate-fade-in');
void animatedInner.offsetWidth;
animatedInner.classList.add('animate-fade-in');
}
const headerTitle = document.getElementById('header-title');
if (toolId === 'tool3') {
document.getElementById('nav-tool3').className = 'w-full flex items-center px-3 py-2.5 bg-blue-600/20 text-blue-400 rounded-lg transition-colors text-left';
headerTitle.innerHTML = '工具 3全文复筛与智能提取工作台';
document.getElementById('header-actions').style.display = 'flex';
} else if (toolId === 'tool4') {
document.getElementById('nav-tool4').className = 'w-full flex items-center px-3 py-2.5 bg-blue-600/20 text-blue-400 rounded-lg transition-colors text-left';
headerTitle.innerHTML = '工具 4系统综述 (SR) 图表生成器 <span class="text-xs bg-gray-100 text-gray-600 border px-2 py-1 rounded ml-2 font-normal">支持独立文件模式</span>';
document.getElementById('header-actions').style.display = 'none';
} else if (toolId === 'tool5') {
document.getElementById('nav-tool5').className = 'w-full flex items-center px-3 py-2.5 bg-blue-600/20 text-blue-400 rounded-lg transition-colors text-left';
headerTitle.innerHTML = '工具 5Meta 分析量化引擎 <span class="text-xs bg-gray-100 text-gray-600 border px-2 py-1 rounded ml-2 font-normal">支持独立文件模式</span>';
document.getElementById('header-actions').style.display = 'none';
}
}
// Drawer
const drawer = document.getElementById('extraction-drawer');
const backdrop = document.getElementById('drawer-backdrop');
function openDrawer() { backdrop.classList.remove('hidden'); void drawer.offsetWidth; drawer.classList.add('drawer-open'); }
function closeDrawer() { drawer.classList.remove('drawer-open'); setTimeout(() => backdrop.classList.add('hidden'), 300); }
function approveAndClose() { closeDrawer(); showToast('数据已核准 (Approved)'); }
// Tool 4 Logic
function toggleSRUpload(isManual) {
const uploadArea = document.getElementById('sr-upload-area');
if(isManual) {
uploadArea.classList.remove('hidden');
uploadArea.classList.add('animate-fade-in');
} else {
uploadArea.classList.add('hidden');
}
}
function generatePRISMA() {
// Check which input method is selected
const isManual = document.querySelector('input[name="sr-input"]:checked').value === 'manual';
if(isManual) {
showToast('正在解析上传的 Excel 本地数据...');
} else {
showToast('正在聚合上游工具产生的流水线数据...');
}
document.getElementById('sr-empty').classList.add('hidden');
document.getElementById('sr-result-prisma').classList.add('hidden');
document.getElementById('sr-loading').classList.remove('hidden');
setTimeout(() => {
document.getElementById('sr-loading').classList.add('hidden');
document.getElementById('sr-result-prisma').classList.remove('hidden');
document.getElementById('sr-result-prisma').classList.add('flex');
showToast('PRISMA 流程图渲染成功');
}, 1200);
}
// Tool 5 Logic
const mockTableHTML = `
<tr class="border-b border-gray-200 hover:bg-gray-50"><td class="border-r border-gray-200"><input class="data-grid-input font-medium" value="Gandhi 2018"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.49"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.38"></td><td><input class="data-grid-input" value="0.64"></td></tr>
<tr class="border-b border-gray-200 hover:bg-gray-50"><td class="border-r border-gray-200"><input class="data-grid-input font-medium" value="Hellmann 2019"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.79"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.65"></td><td><input class="data-grid-input" value="0.96"></td></tr>
<tr class="border-b border-gray-200 hover:bg-gray-50"><td class="border-r border-gray-200"><input class="data-grid-input font-medium" value="Socinski 2018"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.78"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.64"></td><td><input class="data-grid-input" value="0.96"></td></tr>
<tr class="border-b border-gray-200 hover:bg-gray-50"><td class="border-r border-gray-200"><input class="data-grid-input font-medium" value="Reck 2021"></td><td class="border-r border-gray-200"><input class="data-grid-input text-red-500 font-bold" value="0.59"></td><td class="border-r border-gray-200"><input class="data-grid-input" value="0.50"></td><td><input class="data-grid-input" value="0.69"></td></tr>
`;
function importDataFromTool3() {
document.getElementById('meta-data-overlay').classList.add('hidden');
document.getElementById('meta-tbody').innerHTML = mockTableHTML;
document.getElementById('row-count').innerText = "4";
showToast('已继承本项目内 4 篇 Approved 的文献数据');
}
function simulateFileUpload() {
// Simulate a file input click
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .csv';
input.onchange = e => {
showToast('读取本地 Excel 成功,正在解析矩阵...');
setTimeout(() => {
document.getElementById('meta-data-overlay').classList.add('hidden');
document.getElementById('meta-tbody').innerHTML = mockTableHTML;
document.getElementById('row-count').innerText = "4";
showToast('本地数据导入完成');
}, 800);
}
input.click();
}
function runMetaEngine() {
if (!document.getElementById('meta-data-overlay').classList.contains('hidden')) {
alert('请先通过 [继承] 或 [上传] 导入数据矩阵'); return;
}
document.getElementById('meta-empty').classList.add('hidden');
document.getElementById('meta-result-view').classList.add('hidden');
const overlay = document.getElementById('r-engine-overlay');
overlay.classList.remove('hidden');
overlay.style.display = 'flex';
setTimeout(() => {
overlay.classList.add('hidden');
overlay.style.display = 'none';
document.getElementById('meta-result-view').classList.remove('hidden');
document.getElementById('meta-result-view').classList.add('flex');
showToast('R 引擎计算完成,森林图生成成功');
}, 2000);
}
</script>
</body>
</html>