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>
612 lines
44 KiB
HTML
612 lines
44 KiB
HTML
<!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 Non–Small-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 Non–Small-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 = '工具 5:Meta 分析量化引擎 <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> |