docs: complete documentation system (250+ files)

- System architecture and design documentation
- Business module docs (ASL/AIA/PKB/RVW/DC/SSA/ST)
- ASL module complete design (quality assurance, tech selection)
- Platform layer and common capabilities docs
- Development standards and API specifications
- Deployment and operations guides
- Project management and milestone tracking
- Architecture implementation reports
- Documentation templates and guides
This commit is contained in:
2025-11-16 15:43:55 +08:00
parent 0fe6821a89
commit e52020409c
173 changed files with 46227 additions and 11964 deletions

View File

@@ -0,0 +1,87 @@
# 标题摘要初筛UI设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## 📋 文档说明
本文档描述标题摘要初筛模块的UI设计包括页面布局、交互设计、视觉规范等。
---
## 🎨 页面视图
### 1. 设置与启动视图
**布局结构**:
- 顶部: 标准参考面板(可折叠)
- 中间: 临时调整入口(可选)
- 底部: 文献导入区域 + 启动按钮
**设计要点**:
- 清晰展示从研究方案继承的PICO和入排标准
- 提供简洁的文献导入入口Excel上传
- 导入后激活启动按钮
### 2. 表格化审核工作台视图 ⭐核心
**表格结构**:
- **表头**: 两行结构
- 第一行: 合并单元格标示模型区域DS模型 | Q3模型 | 决策)
- 第二行: 各模型下细分P/I/C/S/结论列
- **主行**:
- 展开/收起按钮
- 文献ID、研究ID、文献来源
- DS-P/I/C/S/结论判断(✓/✗/?
- Q3-P/I/C/S/结论判断
- 冲突状态指示
- 最终决策下拉框
- **展开行**:
- DS证据列: P/I/C/S对应的关键短语
- Q3证据列: P/I/C/S对应的关键短语
**交互设计**:
- 点击判断图标(✓/✗/?) → 弹出双视图原文审查模态框
- 冲突项行背景高亮显示
- 支持批量选择和非冲突项批量决策
### 3. 结果展示视图
**布局结构**:
- 顶部: 统计卡片(总计、纳入、排除)
- 中间: PRISMA式排除总结
- 底部: 结果列表Tab切换纳入/排除)+ 导出按钮
---
## 🖼️ 原型参考
详细原型请参考: `../../01-设计文档/AI智能文献-标题摘要初筛原型.html`
---
## ⏳ 待完善内容
后续将补充:
- 详细的页面布局图
- 交互流程图
- 视觉设计规范
- 组件样式规范
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 全文复筛UI设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# UI设计规范
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,524 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>全文复筛原型 V2</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.1.3/24/outline/css/heroicons.min.css" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.sidebar { background-color: #f8fafc; }
.sidebar-link.active { background-color: #e0f2fe; color: #0c4a6e; font-weight: 600; }
.main-content { margin-left: 16rem; }
.view { display: none; }
.view.active { display: block; }
.modal-backdrop { background-color: rgba(0, 0, 0, 0.5); }
.table-conflict-row > td:not(.expanded-row-cell) { background-color: #fee2e2; }
.table-cell-judge { cursor: pointer; text-align: center; }
.abstract-highlight { background-color: #fef9c3; }
.nav-parent { font-weight: bold; color: #374151; }
.nav-child { padding-left: 1.5rem; }
.sub-nav-link { color: #6b7280; }
.sub-nav-link.active { color: #0c4a6e; font-weight: 600; background-color: #f0f9ff; }
.sidebar-link.disabled { color: #9ca3af; cursor: not-allowed; background-color: transparent !important; }
.sidebar-link.disabled:hover { background-color: transparent !important; }
.result-tab.active { border-bottom-color: #0ea5e9; color: #0ea5e9; font-weight: 600;}
.expand-row-btn { cursor: pointer; width: 20px; text-align: center; }
.expanded-row { display: none; background-color: #f9fafb; }
.expanded-row.shown { display: table-row; }
.expanded-row-cell { padding: 0.75rem 1rem; border-top: 1px solid #e5e7eb; }
.evidence-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
.evidence-grid dt { font-weight: 500; color: #4b5563; text-align: right;}
.evidence-grid dd { color: #1f2937; }
.abstract-cell .full-abstract { display: none; }
.abstract-cell.expanded .short-abstract { display: none; }
.abstract-cell.expanded .full-abstract { display: block; }
.results-pics-cell { font-size: 0.7rem; line-height: 1.2; color: #4b5563; }
.results-pics-cell strong { color: #1f2937; }
</style>
</head>
<body class="bg-gray-100 text-gray-800">
<div class="h-screen flex">
<!-- 模拟的左侧主导航 -->
<aside id="sidebar" class="sidebar w-64 border-r border-gray-200 p-4 flex-shrink-0 flex flex-col fixed h-full">
<div class="text-xl font-bold text-gray-800 mb-8">AI文献平台</div>
<nav class="flex-grow space-y-2">
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">1. 研究方案生成</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">2. 智能文献检索</a>
<!-- Title Screening Section (Not active) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">3. 标题摘要初筛</span>
<div class="space-y-1 nav-child">
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">初筛结果</a>
</div>
</div>
<!-- Full-text Rescreening Section (Active) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">4. 全文复筛</span>
<div class="space-y-1 nav-child">
<a href="#" id="nav-fulltext-settings" data-view="fulltext-settings" class="sidebar-link sub-nav-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" id="nav-fulltext-workbench" data-view="fulltext-workbench" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" id="nav-fulltext-results" data-view="fulltext-results" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">复筛结果</a>
</div>
</div>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">5. 全文解析与数据提取</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">6. 数据综合分析与报告</a>
</nav>
</aside>
<!-- 主内容区 -->
<div class="main-content flex-grow flex flex-col w-full">
<header class="bg-white shadow-sm flex-shrink-0 z-10 p-4 border-b">
<h1 id="header-title" class="text-xl font-bold">全文复筛 / 设置与启动</h1>
</header>
<div id="view-container" class="flex-grow p-6 overflow-auto">
<!-- Placeholder for Title views, not functional -->
<div id="title-settings-view" class="view"></div>
<div id="title-workbench-view" class="view"></div>
<div id="title-results-view" class="view"></div>
<!-- 全文复筛 - 设置与启动 -->
<div id="fulltext-settings-view" class="view active">
<h2 class="text-2xl font-bold mb-4">全文复筛 - 设置与启动</h2>
<div class="mb-6 bg-white p-6 rounded-lg shadow space-y-4">
<div class="flex justify-between items-center"><h3 class="text-lg font-semibold">筛选标准 (来自研究方案)</h3><button class="text-sm text-sky-600 hover:underline" onclick="alert('弹出模态框允许临时调整标准')">[调整本次筛选标准]</button></div>
<div class="text-sm text-gray-600 grid grid-cols-2 gap-4 pt-4 border-t"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="fulltext-criteria-adjustment-notice" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div>
</div>
<div id="fulltext-independent-start" class="hidden bg-white p-6 rounded-lg shadow mb-6">
<h3 class="text-lg font-semibold mb-4">导入待复筛文献</h3>
<div class="grid grid-cols-2 gap-6">
<div class="text-center p-6 border rounded-lg">
<h4 class="font-bold text-lg mb-3">上传 Excel 文件</h4>
<p class="text-sm text-gray-500 mb-4">上传包含文献ID和标题的列表。</p>
<div class="flex items-center justify-center space-x-2"><button class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文件</button><a href="#" class="text-sky-600 hover:text-sky-800 font-semibold text-sm">下载模板</a></div>
</div>
<div class="text-center p-6 border rounded-lg">
<h4 class="font-bold text-lg mb-3">从知识库添加</h4>
<p class="text-sm text-gray-500 mb-4">从您已有的知识库中选择文献。</p>
<button onclick="addFromKB()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文献</button>
</div>
</div>
</div>
<div id="fulltext-acquisition-step">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">全文获取与管理 (<span id="fulltext-count">0</span>篇)</h3>
<button onclick="addFromKB()" class="bg-blue-100 text-blue-700 font-semibold py-2 px-4 rounded-lg text-sm hover:bg-blue-200">+ 从知识库添加</button>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden mb-6">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">文献ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">文献标题</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">获取状态</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">操作</th></tr></thead>
<tbody id="fulltext-list-body"></tbody>
</table>
</div>
<div class="text-center">
<!-- V2: Button always enabled for prototype demonstration -->
<button id="start-fulltext-screening-btn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-8 rounded-lg shadow">开始全文复筛</button>
<p id="fulltext-ready-hint" class="hidden text-sm text-red-500 mt-2">(实际应用中需确保所有文献全文均已就绪)</p>
</div>
</div>
</div>
<!-- 全文复筛 - 审核工作台 -->
<div id="fulltext-workbench-view" class="view">
<details class="bg-white rounded-lg shadow p-4 mb-6" open> <summary class="font-semibold cursor-pointer text-lg">▼ 查看当前筛选标准</summary><div class="mt-4 pt-4 border-t text-sm text-gray-600 grid grid-cols-2 gap-4"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="fulltext-criteria-adjustment-notice-workbench" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div></details>
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th rowspan="2" class="px-2 py-3 w-8"></th>
<th rowspan="2" class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-24">文献ID</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-32">研究ID</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-1/4">文献来源</th>
<th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-l border-r">DS 判断</th>
<th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-r">Q3 判断</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">冲突状态</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th>
</tr>
<tr>
<th class="px-2 py-2 font-medium text-gray-500 border-l">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th>
<th class="px-2 py-2 font-medium text-gray-500">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th>
</tr>
</thead>
<tbody id="fulltext-screening-table-body" class="bg-white divide-y divide-gray-200"></tbody>
</table>
</div>
<div class="mt-6 flex justify-end">
<button onclick="completeFulltextScreening()" class="bg-green-600 text-white font-bold py-2 px-6 rounded-lg shadow hover:bg-green-700">完成全文复筛 (模拟)</button>
</div>
</div>
<!-- 全文复筛 - 复筛结果 -->
<div id="fulltext-results-view" class="view">
<h2 class="text-2xl font-bold mb-6">全文复筛 - 结果</h2>
<div class="grid grid-cols-3 gap-6 mb-8 text-center">
<div class="bg-white p-4 rounded-lg shadow"><div class="text-3xl font-bold" id="fulltext-reviewed-count">100</div><div class="text-gray-500">总计复筛</div></div>
<div class="bg-green-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-green-600" id="fulltext-included-count">55</div><div class="text-gray-500">最终纳入</div></div>
<div class="bg-red-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-red-600" id="fulltext-excluded-count">45</div><div class="text-gray-500">排除</div></div>
</div>
<div class="mb-8 bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">排除原因统计 (模拟)</h3>
<ul class="text-sm text-gray-600 space-y-1 list-disc list-inside">
<li>排除文献总数: n=<span id="fulltext-prisma-total-excluded">45</span></li>
<li>非随机对照研究: n=<span id="fulltext-prisma-s-excluded">5</span></li>
<li>非目标人群 (P): n=<span id="fulltext-prisma-p-excluded">7</span></li>
<li>干预/对照不符 (I/C): n=<span id="fulltext-prisma-ic-excluded">18</span></li>
<li>结局指标不符 (O): n=<span id="fulltext-prisma-o-excluded">9</span></li>
<li>其他原因 (如数据不全): n=<span id="fulltext-prisma-other-excluded">6</span></li>
</ul>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="border-b border-gray-200 mb-4">
<nav id="fulltext-result-tabs" class="-mb-px flex space-x-8" aria-label="Tabs">
<button data-tab="included" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-sky-500 text-sky-600 active" onclick="switchResultTab('fulltext', 'included')">最终纳入 (<span id="fulltext-included-tab-count">55</span>)</button>
<button data-tab="excluded" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" onclick="switchResultTab('fulltext', 'excluded')">排除 (<span id="fulltext-excluded-tab-count">45</span>)</button>
</nav>
</div>
<div class="flex justify-between items-center mb-4">
<input type="text" id="fulltext-result-search" placeholder="搜索文献..." class="w-1/3 p-2 border rounded text-sm">
<button class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-lg text-sm">↓ 导出为Excel</button>
</div>
<div class="overflow-x-auto">
<table id="fulltext-results-table" class="min-w-full divide-y divide-gray-200 text-sm">
<!-- Fix: Add ID to thead -->
<thead id="fulltext-results-table-head" class="bg-gray-50">
<!-- Header row dynamically generated -->
</thead>
<tbody id="fulltext-results-table-body" class="bg-white divide-y divide-gray-200">
<!-- V2: Simulated Result Rows -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 任务状态面板 (模态框) -->
<div id="task-status-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
<div class="modal-backdrop absolute inset-0"></div><div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-lg z-10"><h2 id="task-modal-title" class="text-2xl font-bold text-center mb-4">AI处理进行中...</h2><div id="progress-bar" class="w-full bg-gray-200 rounded-full h-4 mb-4"><div id="progress-bar-inner" class="bg-sky-500 h-4 rounded-full" style="width: 0%"></div></div><p id="progress-text" class="text-center text-gray-500 mb-6">已处理 0 / 0</p><button id="show-workbench-btn" class="hidden w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg">处理完成!进入审核工作台</button></div>
</div>
<!-- 双视图原文审查 (模态框 V7) -->
<div id="review-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-8">
<div class="modal-backdrop absolute inset-0" onclick="closeReviewModal()"></div><div class="bg-white rounded-lg shadow-xl w-full max-w-6xl h-[90vh] flex flex-col z-10"> <div class="flex justify-between items-center p-4 border-b"><h2 id="modal-title" class="text-xl font-bold">原文审查</h2><button onclick="closeReviewModal()" class="text-gray-500 hover:text-gray-800 text-2xl">&times;</button></div><div class="flex-grow flex overflow-hidden"><div class="w-1/2 p-6 overflow-y-auto border-r"><div id="modal-meta" class="mb-4 border-b pb-4"><h3 id="modal-doc-title" class="font-bold text-lg"></h3><p id="modal-doc-authors" class="text-sm text-gray-600"></p><p id="modal-doc-journal" class="text-sm text-gray-500"></p></div><h3 id="modal-text-source-title" class="font-bold mb-2">摘要/全文</h3><div id="modal-text-content" class="text-gray-700 leading-relaxed text-sm prose max-w-none"></div></div><div class="w-1/2 p-6 overflow-y-auto bg-gray-50"><h3 id="modal-dimension-title" class="font-bold mb-4 text-lg">PICO(S) 判断依据</h3><div id="modal-reasoning" class="space-y-6"></div></div></div></div>
</div>
<script>
// --- STATE MANAGEMENT ---
// Using placeholder data assuming title screening produced some results
let fulltextDocs = [
{ id: 1, pmid: `PMID1234501`, study_id: `AuthorA1 2021`, source: `J Med 2021;11(1):e124`, title: `第1篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, exclusion_reason: null},
{ id: 3, pmid: `PMID1234503`, study_id: `AuthorA3 2023`, source: `Lancet 2023;3(1):e125`, title: `第3篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'常规疗法',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'常规疗法',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."} , exclusion_reason: null},
{ id: 5, pmid: `PMID1234505`, study_id: `AuthorA5 2025`, source: `BMJ 2025;5(1):e126`, title: `第5篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✗', conclusion: '排除', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'观察性'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."} , exclusion_reason: "非RCT"},
{ id: 7, pmid: `PMID1234507`, study_id: `AuthorA7 2022`, source: `Diabetes Care 2022;45(2):e127`, title: `第7篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人T2DM',i:'药物A',c:'安慰剂',s:'RCT'}}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人T2DM',i:'药物A',c:'安慰剂',s:'RCT'}} , exclusion_reason: null},
{ id: 9, pmid: `PMID1234509`, study_id: `AuthorA9 2024`, source: `JAMA 2024;331(3):e128`, title: `第9篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✗', i: '✓', c:'✓', s:'✓', conclusion: '排除', evidence: {p:'青少年',i:'药物A',c:'安慰剂',s:'RCT'}}, model_b: {p: '✗', i: '✓', c:'✓', s:'✓', conclusion: '排除', evidence: {p:'青少年',i:'药物A',c:'安慰剂',s:'RCT'}} , exclusion_reason: "非目标人群"},
];
let currentViewId = 'fulltext-settings'; // Start at fulltext settings
let simulatedFulltextResults = []; // For results page simulation
// --- DOM ELEMENTS ---
const views = {};
const navLinks = {};
document.querySelectorAll('.view').forEach(v => {
if(v.id.includes('fulltext')) { views[v.id.replace('-view','')] = v; }
});
document.querySelectorAll('.sub-nav-link').forEach(l => {
if(l.id.includes('fulltext')) { navLinks[l.id.replace('nav-','')] = l; }
});
const headerTitle = document.getElementById('header-title');
const taskStatusModal = document.getElementById('task-status-modal');
const reviewModal = document.getElementById('review-modal');
const fulltextAcquisitionStep = document.getElementById('fulltext-acquisition-step');
const fulltextListBody = document.getElementById('fulltext-list-body');
const startFulltextScreeningBtn = document.getElementById('start-fulltext-screening-btn');
const fulltextIndependentStart = document.getElementById('fulltext-independent-start');
const fulltextReadyHint = document.getElementById('fulltext-ready-hint');
const fulltextScreeningTableBody = document.getElementById('fulltext-screening-table-body');
const fulltextReviewedCount = document.getElementById('fulltext-reviewed-count');
const fulltextIncludedCount = document.getElementById('fulltext-included-count');
const fulltextExcludedCount = document.getElementById('fulltext-excluded-count');
const fulltextIncludedTabCount = document.getElementById('fulltext-included-tab-count');
const fulltextExcludedTabCount = document.getElementById('fulltext-excluded-tab-count');
const fulltextResultsTableBody = document.getElementById('fulltext-results-table-body');
const ftPrismaTotalExcluded = document.getElementById('fulltext-prisma-total-excluded');
const ftPrismaSExcluded = document.getElementById('fulltext-prisma-s-excluded');
const ftPrismaPExcluded = document.getElementById('fulltext-prisma-p-excluded');
const ftPrismaICExcluded = document.getElementById('fulltext-prisma-ic-excluded');
const ftPrismaOExcluded = document.getElementById('fulltext-prisma-o-excluded');
const ftPrismaOtherExcluded = document.getElementById('fulltext-prisma-other-excluded');
// --- SIMULATED DATA ---
function generateSimulatedFulltextResultsData() { /* ... unchanged V2 version ... */ simulatedFulltextResults = []; const totalSimulated = 100; const includedSimulated = 55; for (let i = 1; i <= totalSimulated; i++) { const isIncluded = i <= includedSimulated; const final_decision = isIncluded ? '纳入' : '排除'; const decision_method = Math.random() > 0.6 ? '人工审核' : (isIncluded ? 'AI全文纳入' : 'AI全文排除'); let exclusion_reason = null; if (!isIncluded) { const reasons = ["非RCT", "非目标人群", "干预/对照不符", "结局指标不符", "数据不全"]; exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)]; } simulatedFulltextResults.push({ id: i + 3000, pmid: `PMID${3234500 + i}`, study_id: `FullSim${i} ${2022}`, source: `Full J ${2022};${i % 10 + 1}:f${i}`, title: `模拟全文文献 ${i}: 进一步研究`, final_decision: final_decision, decision_method: decision_method, exclusion_reason: exclusion_reason, }); } }
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('fulltext-count').textContent = fulltextDocs.length;
renderFulltextList();
generateSimulatedFulltextResultsData();
showView('fulltext-settings');
});
// --- NAVIGATION & VIEW MANAGEMENT ---
Object.keys(navLinks).forEach(key => {
navLinks[key].addEventListener('click', (e) => {
e.preventDefault();
const viewId = navLinks[key].getAttribute('data-view');
// V2: Allow navigating to results anytime for demo
// if(navLinks[key].classList.contains('disabled')) return;
showView(viewId);
});
});
function showView(viewId) {
currentViewId = viewId;
Object.values(views).forEach(v => v.classList.remove('active'));
if (views[viewId]) {
views[viewId].classList.add('active');
} else {
console.error("View not found:", viewId);
views['fulltext-settings'].classList.add('active'); // Fallback
viewId = 'fulltext-settings';
}
const stage = 'fulltext';
const subView = viewId.split('-')[1];
const stageTitles = { 'fulltext': '全文复筛' };
const subViewTitles = { 'settings': '设置与启动', 'workbench': '审核工作台', 'results': '复筛结果' };
headerTitle.textContent = `${stageTitles[stage]} / ${subViewTitles[subView]}`;
Object.values(navLinks).forEach(link => link.classList.remove('active'));
if (navLinks[viewId]) navLinks[viewId].classList.add('active');
if (viewId === 'fulltext-settings') {
renderFulltextList();
}
// V2: Render results page whenever navigated to
if (viewId === 'fulltext-results') renderResultsPage('fulltext');
}
// --- FULLTEXT SCREENING ---
function renderFulltextList() {
fulltextListBody.innerHTML = fulltextDocs.map((doc, index) => {
let status, action;
if (!doc.pdf_status) {
const statusRand = Math.random();
if (statusRand < 0.6) doc.pdf_status = 'ready';
else if (statusRand < 0.8) doc.pdf_status = 'fetching';
else doc.pdf_status = 'failed';
}
switch(doc.pdf_status) {
case 'ready': status = `<span class="text-green-600 font-semibold">获取成功</span>`; action = `<span class="text-gray-400">已就绪</span>`; break;
case 'fetching': status = `<span class="text-blue-500">获取中...</span>`; action = `<span class="text-gray-400">请稍候</span>`; break;
default: status = `<span class="text-red-600 font-semibold">获取失败</span>`; action = `<button class="text-sky-600 hover:underline" onclick="uploadFulltext(${doc.id})">上传全文</button>`;
}
return `<tr><td class="px-4 py-3">${doc.pmid}</td><td class="px-4 py-3">${doc.title}</td><td class="px-4 py-3">${status}</td><td class="px-4 py-3">${action}</td></tr>`;
}).join('');
checkFulltextReady();
}
function uploadFulltext(docId) {
const doc = fulltextDocs.find(d => d.id === docId);
if(doc) {
doc.pdf_status = 'ready';
alert(`模拟为文献 ${doc.pmid} 上传全文成功!`);
}
renderFulltextList();
}
function addFromKB() { /* ... unchanged V1 version ... */ const newDocIdStart = 2000; for(let i=1; i<=3; i++){ fulltextDocs.push({ id: newDocIdStart + i, pmid: `KB${newDocIdStart+i}`, study_id: `KB_Author ${2022+i}`, source: `Know Base J ${2022+i}`, title: `知识库文献 ${i}`, final_decision: '待定', decision_method: '待定', pdf_status: 'ready', model_a: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence:{p:'...',i:'...',c:'...',s:'...'}}, model_b: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence:{p:'...',i:'...',c:'...',s:'...'}} }); } document.getElementById('fulltext-count').textContent = fulltextDocs.length; renderFulltextList(); alert("已从知识库添加3篇文献。"); }
function checkFulltextReady() {
const allReady = fulltextDocs.every(doc => doc.pdf_status === 'ready');
// V2: Logic removed, button always enabled for demo
startFulltextScreeningBtn.disabled = false; // Always enable for demo
if(allReady) {
startFulltextScreeningBtn.classList.add('bg-green-600', 'hover:bg-green-700');
fulltextReadyHint.classList.add('hidden');
} else {
startFulltextScreeningBtn.classList.add('bg-gray-400', 'cursor-not-allowed'); // Keep visual cue
startFulltextScreeningBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
fulltextReadyHint.classList.remove('hidden');
}
}
startFulltextScreeningBtn.addEventListener('click', () => {
taskStatusModal.classList.remove('hidden');
document.getElementById('task-modal-title').textContent = 'AI全文复筛进行中...';
let progress = 0;
const totalToProcess = fulltextDocs.length;
document.getElementById('progress-text').textContent = `已处理 0 / ${totalToProcess}`;
document.getElementById('show-workbench-btn').onclick = () => {
taskStatusModal.classList.add('hidden');
showView('fulltext-workbench');
renderTable('fulltext');
};
const interval = setInterval(() => {
progress += Math.ceil(100 / (totalToProcess || 1));
if (progress > 100) progress = 100;
document.getElementById('progress-bar-inner').style.width = `${progress}%`;
document.getElementById('progress-text').textContent = `已处理 ${Math.ceil(progress/100 * totalToProcess)} / ${totalToProcess}`;
if (progress >= 100) {
clearInterval(interval);
fulltextDocs.forEach(doc => {
doc.final_decision = '待定';
doc.model_a.conclusion = Math.random() > 0.3 ? '纳入' : '排除';
doc.model_b.conclusion = Math.random() > 0.25 ? '纳入' : '排除';
doc.model_a.s = Math.random() > 0.1 ? '✓' : '✗';
doc.model_b.s = Math.random() > 0.15 ? '✓' : '✗';
if (doc.model_a.conclusion === '排除' || doc.model_b.conclusion === '排除') {
const reasons = ["非RCT", "非目标人群", "干预/对照不符", "结局指标不符", "数据不全"];
doc.exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)];
} else { doc.exclusion_reason = null; }
});
document.getElementById('show-workbench-btn').classList.remove('hidden');
}
}, 200);
});
// V7: Render Table with Original Header Structure (Applied to Fulltext)
function renderTable(stage) {
if (stage !== 'fulltext') return; // Only render for fulltext stage
const tableBodyId = 'fulltext-screening-table-body';
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataToRender = fulltextDocs; // Use fulltext data
tableBody.innerHTML = dataToRender.map(doc => {
const isConflict = doc.model_a.conclusion !== doc.model_b.conclusion;
const rowId = `${stage}-row-${doc.id}`;
const expandedRowId = `${stage}-expanded-row-${doc.id}`;
const judgeCell = (model, dim) => `<td class="table-cell-judge px-2 py-3 border-l" onclick="openReviewModal(${doc.id}, '${model}', '${dim}', '${stage}')">${{ '✓': '<span class="text-green-600 font-bold">✓</span>', '✗': '<span class="text-red-600 font-bold">✗</span>', '?': '<span class="text-yellow-600 font-bold">?</span>' }[doc[model][dim]]}</td>`;
return `
<tr id="${rowId}" class="${isConflict ? 'table-conflict-row' : ''}">
<td class="px-2 py-3 expand-row-btn" onclick="toggleRow('${expandedRowId}', this)">+</td>
<td class="px-3 py-3 font-mono text-xs text-gray-500">${doc.pmid}</td>
<td class="px-4 py-3 font-semibold text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs text-gray-500 truncate" title="${doc.source}">${doc.source.split('.')[0]}...</td>
<!-- DS Judgements -->
${judgeCell('model_a', 'p')} ${judgeCell('model_a', 'i')} ${judgeCell('model_a', 'c')} ${judgeCell('model_a', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_a.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_a.conclusion}</td>
<!-- Q3 Judgements -->
${judgeCell('model_b', 'p')} ${judgeCell('model_b', 'i')} ${judgeCell('model_b', 'c')} ${judgeCell('model_b', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_b.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_b.conclusion}</td>
<!-- Status & Decision -->
<td class="px-4 py-3 border-l">${isConflict ? '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">冲突</span>' : '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">一致</span>'}</td>
<td class="px-4 py-3 border-l"><select class="rounded-md border-gray-300 text-xs" data-id="${doc.id}" data-stage="${stage}" onchange="updateDecision(this)"><option value="待定" ${doc.final_decision === '待定' ? 'selected' : ''}>待定</option><option value="纳入" ${doc.final_decision === '纳入' ? 'selected' : ''}>纳入</option><option value="排除" ${doc.final_decision === '排除' ? 'selected' : ''}>排除</option></select></td>
</tr>
<tr id="${expandedRowId}" class="expanded-row">
<td colspan="15" class="expanded-row-cell text-xs">
<div class="grid grid-cols-2 gap-4">
<div><strong class="text-gray-600">DS 证据 (基于全文):</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_a.evidence.p} (全文)</dd><dt>I:</dt><dd>${doc.model_a.evidence.i} (全文)</dd><dt>C:</dt><dd>${doc.model_a.evidence.c} (全文)</dd><dt>S:</dt><dd>${doc.model_a.evidence.s} (全文)</dd></dl></div>
<div><strong class="text-gray-600">Q3 证据 (基于全文):</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_b.evidence.p} (全文)</dd><dt>I:</dt><dd>${doc.model_b.evidence.i} (全文)</dd><dt>C:</dt><dd>${doc.model_b.evidence.c} (全文)</dd><dt>S:</dt><dd>${doc.model_b.evidence.s} (全文)</dd></dl></div>
</div>
</td>
</tr>
`;
}).join('');
}
function toggleRow(rowId, btn) { /* ... unchanged ... */ const row = document.getElementById(rowId); const isShown = row.classList.toggle('shown'); btn.textContent = isShown ? '-' : '+'; }
function updateDecision(selectElement) { /* ... unchanged V7 version ... */ const docId = parseInt(selectElement.dataset.id); const stage = selectElement.dataset.stage; const dataSet = stage === 'title' ? literatureData : fulltextDocs; const doc = dataSet.find(d => d.id === docId); if (doc) { doc.final_decision = selectElement.value; if (doc.final_decision !== '待定') { doc.decision_method = '人工审核'; } else { doc.decision_method = '待定'; } } }
function openReviewModal(docId, modelKey, dimensionKey, stage) { /* ... unchanged V7 version ... */ const dataSet = stage === 'title' ? literatureData : fulltextDocs; const doc = dataSet.find(d => d.id === docId); if (!doc) return; const dimensionMap = { p: '人群 (P)', i: '干预 (I)', c: '对照 (C)', s: '研究设计 (S)' }; document.getElementById('modal-title').textContent = `审查: ${doc.title}`; document.getElementById('modal-doc-title').textContent = doc.title; document.getElementById('modal-doc-authors').textContent = doc.authors; document.getElementById('modal-doc-journal').textContent = `${doc.journal} (${doc.pmid})`; const textContentEl = document.getElementById('modal-text-content'); const textSourceTitle = document.getElementById('modal-text-source-title'); let textContent = ""; if (stage === 'fulltext') { textSourceTitle.textContent = "全文片段"; textContent = `<h4 class='font-semibold'>方法部分:</h4><p>...<span class='s-source'>随机对照试验(RCT)</span>...招募了<span class='p-source'>2型糖尿病成人患者</span>...</p> <h4 class='font-semibold mt-4'>干预部分:</h4><p>...患者接受<span class='i-source'>SGLT2药物</span>或<span class='c-source'>安慰剂</span>治疗...</p>`; } else { textSourceTitle.textContent = "摘要"; textContent = doc.abstract; } const sourceSpan = textContent.match(new RegExp(`<span class='${dimensionKey.toLowerCase()}-source'>(.*?)</span>`)); let sourceText = sourceSpan ? sourceSpan[1] : ""; textContent = textContent.replace(/<span class='.-source'>/g, '').replace(/<\/span>/g, ''); if (sourceText) { textContent = textContent.replace(sourceText, `<mark class="abstract-highlight">${sourceText}</mark>`); } textContentEl.innerHTML = textContent; setTimeout(() => { const mark = textContentEl.querySelector('mark'); if (mark) { mark.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); document.getElementById('modal-dimension-title').textContent = `PICO(S) 判断依据 (基于${stage==='title'?'摘要':'全文'})`; const reasoningContainer = document.getElementById('modal-reasoning'); let reasoningHTML = `<div class="grid grid-cols-2 gap-6">`; ['model_a', 'model_b'].forEach(mKey => { const modelName = mKey === 'model_a' ? 'DeepSeek' : 'Qwen3'; reasoningHTML += `<div class="border rounded-lg p-4 bg-white"><h4 class="font-bold text-gray-700 mb-3">${modelName} 判断</h4><div class="space-y-2 text-sm mb-3">`; ['p', 'i', 'c', 's'].forEach(dim => { const icon = { '✓': '<span class="text-green-600 font-bold">✓ 符合</span>', '✗': '<span class="text-red-600 font-bold">✗ 不符</span>', '?': '<span class="text-yellow-600 font-bold">? 不确定</span>' }[doc[mKey][dim]]; reasoningHTML += `<p><strong>${dimensionMap[dim]}:</strong> ${icon}</p>`; }); reasoningHTML += `</div><p class="text-sm mb-2"><strong>总览结论:</strong> <span class="font-semibold ${doc[mKey].conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc[mKey].conclusion}</span></p><p class="text-sm text-gray-600"><strong>"${dimensionMap[dimensionKey]}" 判断理由:</strong> ${doc[mKey][`${dimensionKey}_reason`]}</p></div>`; }); reasoningHTML += `</div>`; reasoningContainer.innerHTML = reasoningHTML; reviewModal.classList.remove('hidden'); }
function closeReviewModal() { reviewModal.classList.add('hidden'); }
// --- RESULTS PAGES (V2 Independent Data) ---
function renderResultsPage(stage) {
// V2: Use simulated data for counts and table FOR FULLTEXT as well
const dataSet = stage === 'title' ? simulatedResultsData : simulatedFulltextResults;
const included = dataSet.filter(doc => doc.final_decision === '纳入');
const excluded = dataSet.filter(doc => doc.final_decision === '排除');
const totalDocsInStage = dataSet.length;
if (stage === 'title') {
// ... (title result logic using simulatedResultsData - unchanged from V1 of this specific prototype) ...
} else { // fulltext
fulltextReviewedCount.textContent = totalDocsInStage;
fulltextIncludedCount.textContent = included.length;
fulltextExcludedCount.textContent = excluded.length;
fulltextIncludedTabCount.textContent = included.length;
fulltextExcludedTabCount.textContent = excluded.length;
// Simulate Prisma for fulltext results
ftPrismaTotalExcluded.textContent = excluded.length;
ftPrismaSExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非RCT').length;
ftPrismaPExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非目标人群').length;
ftPrismaICExcluded.textContent = excluded.filter(d => d.exclusion_reason === '干预/对照不符').length;
ftPrismaOExcluded.textContent = excluded.filter(d => d.exclusion_reason === '结局指标不符').length;
ftPrismaOtherExcluded.textContent = excluded.filter(d => !["非RCT", "非目标人群", "干预/对照不符", "结局指标不符"].includes(d.exclusion_reason)).length;
renderResultsTable('fulltext', document.querySelector('#fulltext-result-tabs .result-tab.active')?.dataset.tab || 'included'); // V2: Added fallback
}
}
function switchResultTab(stage, tab) {
if (stage !== 'fulltext') return; // Only handle fulltext stage
const tabContainerId = 'fulltext-result-tabs';
document.querySelectorAll(`#${tabContainerId} button`).forEach(btn => { btn.classList.remove('active', 'border-sky-500', 'text-sky-600'); btn.classList.add('border-transparent', 'text-gray-500'); });
const activeBtn = document.querySelector(`#${tabContainerId} button[data-tab="${tab}"]`);
if (activeBtn) { // V2: Check if button exists
activeBtn.classList.add('active', 'border-sky-500', 'text-sky-600');
activeBtn.classList.remove('border-transparent', 'text-gray-500');
renderResultsTable('fulltext', tab);
}
}
// V7: Render Enhanced Results Table (Now uses appropriate data source)
function renderResultsTable(stage, tab) {
if (stage !== 'fulltext') return; // Only handle fulltext stage
const tableBodyId = 'fulltext-results-table-body';
const tableBody = document.getElementById(tableBodyId);
const tableHead = document.getElementById('fulltext-results-table-head'); // V2: Get head by ID
if (!tableBody || !tableHead) return;
// V2 Logic: Use simulated data
const dataSet = simulatedFulltextResults;
const filteredData = dataSet.filter(doc => doc.final_decision === (tab === 'included' ? '纳入' : '排除'));
// Simplified header for fulltext results
let headerHTML = `<tr><th class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">研究ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献来源</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">标题</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">决策方式</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">排除理由</th></tr>`;
tableHead.innerHTML = headerHTML;
tableBody.innerHTML = filteredData.map(doc => {
return `<tr>
<td class="px-3 py-3 font-mono text-xs">${doc.pmid}</td><td class="px-4 py-3 text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs truncate" title="${doc.source}">${doc.source}</td><td class="px-4 py-3 text-xs font-medium">${doc.title}</td>
<td class="px-4 py-3"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${doc.final_decision === '纳入' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${doc.final_decision}</span></td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.decision_method || 'N/A'}</td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.final_decision === '排除' ? (doc.exclusion_reason || '全文排除-未指定') : 'N/A'}</td></tr>`;
}).join('');
}
function toggleAbstract(link, event) { /* ... unchanged ... */ event.preventDefault(); const cell = link.closest('.abstract-cell'); const isExpanded = cell.classList.toggle('expanded'); link.textContent = isExpanded ? '收起' : '显示更多'; }
// V2: Simplified completion logic for demo purposes
function completeFulltextScreening() {
// Just navigate to results page which uses simulated data
showView('fulltext-results');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,450 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标题摘要初筛原型 V4</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.1.3/24/outline/css/heroicons.min.css" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.sidebar { background-color: #f8fafc; }
.sidebar-link.active { background-color: #e0f2fe; color: #0c4a6e; font-weight: 600; }
.main-content { margin-left: 16rem; }
.view { display: none; }
.view.active { display: block; }
.modal-backdrop { background-color: rgba(0, 0, 0, 0.5); }
.table-conflict-row > td:not(.expanded-row-cell) { background-color: #fee2e2; }
.table-cell-judge { cursor: pointer; text-align: center; }
.abstract-highlight { background-color: #fef9c3; }
.nav-parent { font-weight: bold; color: #374151; }
.nav-child { padding-left: 1.5rem; }
.sub-nav-link { color: #6b7280; }
.sub-nav-link.active { color: #0c4a6e; font-weight: 600; background-color: #f0f9ff; }
.sidebar-link.disabled { color: #9ca3af; cursor: not-allowed; background-color: transparent !important; }
.sidebar-link.disabled:hover { background-color: transparent !important; }
.result-tab.active { border-bottom-color: #0ea5e9; color: #0ea5e9; font-weight: 600;}
.expand-row-btn { cursor: pointer; width: 20px; text-align: center; }
.expanded-row { display: none; background-color: #f9fafb; }
.expanded-row.shown { display: table-row; }
.expanded-row-cell { padding: 0.75rem 1rem; border-top: 1px solid #e5e7eb; }
.evidence-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
.evidence-grid dt { font-weight: 500; color: #4b5563; text-align: right;}
.evidence-grid dd { color: #1f2937; }
.abstract-cell .full-abstract { display: none; }
.abstract-cell.expanded .short-abstract { display: none; }
.abstract-cell.expanded .full-abstract { display: block; }
/* V4 specific style for results table PICS */
.results-pics-cell { font-size: 0.7rem; line-height: 1.2; color: #4b5563; }
.results-pics-cell strong { color: #1f2937; }
</style>
</head>
<body class="bg-gray-100 text-gray-800">
<div class="h-screen flex">
<!-- 模拟的左侧主导航 -->
<aside id="sidebar" class="sidebar w-64 border-r border-gray-200 p-4 flex-shrink-0 flex flex-col fixed h-full">
<div class="text-xl font-bold text-gray-800 mb-8">AI文献平台</div>
<nav class="flex-grow space-y-2">
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">1. 研究方案生成</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">2. 智能文献检索</a>
<!-- 标题摘要初筛 Section -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">3. 标题摘要初筛</span>
<div class="space-y-1 nav-child">
<a href="#" id="nav-title-settings" data-view="title-settings" class="sidebar-link sub-nav-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" id="nav-title-workbench" data-view="title-workbench" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" id="nav-title-results" data-view="title-results" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">初筛结果</a>
</div>
</div>
<!-- Full-text Rescreening Section (Disabled Placeholder) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-400">4. 全文复筛</span>
<div class="space-y-1 nav-child">
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">复筛结果</a>
</div>
</div>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">5. 全文解析与数据提取</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">6. 数据综合分析与报告</a>
</nav>
</aside>
<!-- 主内容区 -->
<div class="main-content flex-grow flex flex-col w-full">
<header class="bg-white shadow-sm flex-shrink-0 z-10 p-4 border-b">
<h1 id="header-title" class="text-xl font-bold">标题摘要初筛 / 设置与启动</h1>
</header>
<div id="view-container" class="flex-grow p-6 overflow-auto">
<!-- 标题摘要初筛 - 设置与启动 -->
<div id="title-settings-view" class="view active">
<!-- content from v7, unchanged -->
<h2 class="text-2xl font-bold mb-4">标题摘要初筛 - 设置与启动</h2><div class="mb-6 bg-white p-6 rounded-lg shadow space-y-4"><div class="flex justify-between items-center"><h3 class="text-lg font-semibold">筛选标准 (来自研究方案)</h3><button class="text-sm text-sky-600 hover:underline" onclick="alert('弹出模态框允许临时调整标准')">[调整本次筛选标准]</button></div><div class="text-sm text-gray-600 grid grid-cols-2 gap-4 pt-4 border-t"><div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul></div></div></div><div class="bg-white p-6 rounded-lg shadow"><h3 class="text-lg font-semibold mb-4">导入文献</h3><div class="text-center p-6 border rounded-lg max-w-md mx-auto"><h4 class="font-bold text-lg mb-3">上传 Excel 文件</h4><p class="text-sm text-gray-500 mb-4">请按模板上传包含文献标题和摘要的列表。</p><div class="flex items-center justify-center space-x-2"><button onclick="importLiterature('excel')" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文件</button><a href="#" class="text-sky-600 hover:text-sky-800 font-semibold text-sm">下载模板</a></div></div><div class="mt-8 text-center"><button id="start-title-screening-btn" disabled class="bg-gray-400 text-white font-bold py-3 px-8 rounded-lg cursor-not-allowed">开始AI初筛</button><p id="title-import-hint" class="text-sm text-red-500 mt-2">请先导入文献</p></div></div>
</div>
<!-- 标题摘要初筛 - 审核工作台 -->
<div id="title-workbench-view" class="view">
<!-- content from v7, unchanged -->
<details class="bg-white rounded-lg shadow p-4 mb-6" open> <summary class="font-semibold cursor-pointer text-lg">▼ 查看当前筛选标准</summary><div class="mt-4 pt-4 border-t text-sm text-gray-600 grid grid-cols-2 gap-4"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="title-criteria-adjustment-notice" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div></details><div class="bg-white rounded-lg shadow overflow-x-auto"><table class="min-w-full divide-y divide-gray-200 text-sm"><thead class="bg-gray-50"><tr><th rowspan="2" class="px-2 py-3 w-8"></th><th rowspan="2" class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-24">文献ID</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-32">研究ID</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-1/4">文献来源</th><th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-l border-r">DS 判断</th><th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-r">Q3 判断</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">冲突状态</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th></tr><tr><th class="px-2 py-2 font-medium text-gray-500 border-l">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th><th class="px-2 py-2 font-medium text-gray-500">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th></tr></thead><tbody id="title-screening-table-body" class="bg-white divide-y divide-gray-200"></tbody></table></div><div class="mt-6 flex justify-end"><button id="complete-title-screening-btn" onclick="showView('title-results')" class="bg-green-600 text-white font-bold py-2 px-6 rounded-lg shadow hover:bg-green-700">完成标题摘要初筛 (模拟)</button></div>
</div>
<!-- 标题摘要初筛 - 初筛结果 (V7 Enhanced & V4 Update) -->
<div id="title-results-view" class="view">
<h2 class="text-2xl font-bold mb-6">标题摘要初筛 - 结果</h2>
<div class="grid grid-cols-3 gap-6 mb-8 text-center">
<div class="bg-white p-4 rounded-lg shadow"><div class="text-3xl font-bold" id="title-total-count">1000</div><div class="text-gray-500">总计筛选</div></div>
<div class="bg-green-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-green-600" id="title-included-count">120</div><div class="text-gray-500">初步纳入</div></div>
<div class="bg-red-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-red-600" id="title-excluded-count">880</div><div class="text-gray-500">排除</div></div>
</div>
<div class="mb-8 bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">排除原因统计 (模拟)</h3>
<ul class="text-sm text-gray-600 space-y-1 list-disc list-inside">
<li>排除文献总数: n=<span id="prisma-total-excluded">880</span></li>
<li>非随机对照研究: n=<span id="prisma-s-excluded">250</span></li>
<li>非目标人群 (P): n=<span id="prisma-p-excluded">180</span></li>
<li>干预措施不符 (I): n=<span id="prisma-i-excluded">100</span></li>
<li>对照措施不符 (C): n=<span id="prisma-c-excluded">90</span></li>
<li>重复研究: n=<span id="prisma-duplicate-excluded">150</span></li>
<li>其他 (综述、病例报告等): n=<span id="prisma-other-excluded">110</span></li>
</ul>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="border-b border-gray-200 mb-4">
<nav id="title-result-tabs" class="-mb-px flex space-x-8" aria-label="Tabs">
<button data-tab="included" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-sky-500 text-sky-600 active" onclick="switchResultTab('title', 'included')">初步纳入 (<span id="title-included-tab-count">120</span>)</button>
<button data-tab="excluded" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" onclick="switchResultTab('title', 'excluded')">排除 (<span id="title-excluded-tab-count">880</span>)</button>
</nav>
</div>
<div class="flex justify-between items-center mb-4">
<input type="text" id="title-result-search" placeholder="搜索文献..." class="w-1/3 p-2 border rounded text-sm">
<button class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-lg text-sm">↓ 导出为Excel</button>
</div>
<div class="overflow-x-auto">
<!-- V4: Updated Results Table Header -->
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献ID</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">研究ID</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献来源</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">标题</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">摘要</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">P</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">I</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">C</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">S</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">结论</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">排除理由</th>
</tr>
</thead>
<tbody id="title-results-table-body" class="bg-white divide-y divide-gray-200">
<!-- V4: Simulated Result Rows -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Placeholder for Fulltext views, not functional in this prototype -->
<div id="fulltext-settings-view" class="view"></div>
<div id="fulltext-workbench-view" class="view"></div>
<div id="fulltext-results-view" class="view"></div>
</div>
</div>
</div>
<!-- 任务状态面板 (模态框) -->
<div id="task-status-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
<!-- content from v7, unchanged -->
<div class="modal-backdrop absolute inset-0"></div><div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-lg z-10"><h2 id="task-modal-title" class="text-2xl font-bold text-center mb-4">AI初筛进行中...</h2><div id="progress-bar" class="w-full bg-gray-200 rounded-full h-4 mb-4"><div id="progress-bar-inner" class="bg-sky-500 h-4 rounded-full" style="width: 0%"></div></div><p id="progress-text" class="text-center text-gray-500 mb-6">已处理 0 / 1000</p><button id="show-workbench-btn" class="hidden w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg">处理完成!进入审核工作台</button></div>
</div>
<!-- 双视图原文审查 (模态框 V7) -->
<div id="review-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-8">
<!-- content from v7, unchanged -->
<div class="modal-backdrop absolute inset-0" onclick="closeReviewModal()"></div><div class="bg-white rounded-lg shadow-xl w-full max-w-6xl h-full flex flex-col z-10"><div class="flex justify-between items-center p-4 border-b"><h2 id="modal-title" class="text-xl font-bold">原文审查</h2><button onclick="closeReviewModal()" class="text-gray-500 hover:text-gray-800 text-2xl">&times;</button></div><div class="flex-grow flex overflow-hidden"><div class="w-1/2 p-6 overflow-y-auto border-r"><div id="modal-meta" class="mb-4 border-b pb-4"><h3 id="modal-doc-title" class="font-bold text-lg"></h3><p id="modal-doc-authors" class="text-sm text-gray-600"></p><p id="modal-doc-journal" class="text-sm text-gray-500"></p></div><h3 class="font-bold mb-2">摘要</h3><p id="modal-abstract" class="text-gray-700 leading-relaxed text-sm"></p></div><div class="w-1/2 p-6 overflow-y-auto bg-gray-50"><h3 id="modal-dimension-title" class="font-bold mb-4 text-lg">PICO(S) 判断依据</h3><div id="modal-reasoning" class="space-y-6"></div></div></div></div>
</div>
<script>
// --- STATE MANAGEMENT ---
let literatureData = [];
let simulatedResultsData = [];
let currentViewId = 'title-settings';
// --- DOM ELEMENTS ---
// (Simplified for brevity - assuming all elements defined in V7 are captured)
const views = {
'title-settings': document.getElementById('title-settings-view'),
'title-workbench': document.getElementById('title-workbench-view'),
'title-results': document.getElementById('title-results-view'),
};
const navLinks = {
'title-settings': document.getElementById('nav-title-settings'),
'title-workbench': document.getElementById('nav-title-workbench'),
'title-results': document.getElementById('nav-title-results'),
};
const headerTitle = document.getElementById('header-title');
const startTitleScreeningBtn = document.getElementById('start-title-screening-btn');
const titleImportHint = document.getElementById('title-import-hint');
const taskStatusModal = document.getElementById('task-status-modal');
const screeningTableBody = document.getElementById('title-screening-table-body');
const reviewModal = document.getElementById('review-modal');
const completeTitleScreeningBtn = document.getElementById('complete-title-screening-btn');
// Result page elements
const titleIncludedCount = document.getElementById('title-included-count');
const titleExcludedCount = document.getElementById('title-excluded-count');
const titleIncludedTabCount = document.getElementById('title-included-tab-count');
const titleExcludedTabCount = document.getElementById('title-excluded-tab-count');
const titleResultsTableBody = document.getElementById('title-results-table-body');
const titleTotalCount = document.getElementById('title-total-count');
const prismaTotalExcluded = document.getElementById('prisma-total-excluded');
const prismaSExcluded = document.getElementById('prisma-s-excluded');
const prismaPExcluded = document.getElementById('prisma-p-excluded');
const prismaIExcluded = document.getElementById('prisma-i-excluded');
const prismaCExcluded = document.getElementById('prisma-c-excluded');
const prismaDuplicateExcluded = document.getElementById('prisma-duplicate-excluded');
const prismaOtherExcluded = document.getElementById('prisma-other-excluded');
// --- DATA SIMULATION ---
function generateData() { /* ... unchanged V7 version ... */ literatureData = [];for (let i = 1; i <= 10; i++) { const conflict = Math.random() < 0.3; const model_a_p = Math.random() > 0.2 ? '✓' : '✗'; const model_a_i = Math.random() > 0.1 ? '✓' : '✗'; const model_a_c = Math.random() > 0.3 ? '✓' : '?'; const model_a_s = Math.random() > 0.1 ? '✓' : '✗'; let model_b_s = model_a_s; if (conflict && model_a_s === '✓') model_b_s = '✗'; else if (conflict && model_a_s === '✗') model_b_s = '✓'; const model_a_conclusion = [model_a_p, model_a_i, model_a_s].includes('✗') ? '排除' : '纳入'; const model_b_conclusion = [model_a_p, model_a_i, model_b_s].includes('✗') ? '排除' : '纳入'; const evidence_a = { p: "2型糖尿病成人患者", i: "SGLT2药物", c: "安慰剂", s: "RCT" }; const evidence_b = { p: "成人T2DM患者", i: "SGLT2抑制剂", c: "安慰剂组", s: "随机对照" }; let exclusion_reason = null; if (model_a_conclusion === '排除' || model_b_conclusion === '排除') { const reasons = ["非RCT", "非目标人群", "干预不符", "对照不符", "综述", "重复研究"]; exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)]; } literatureData.push({ id: i, pmid: `PMID${1234500 + i}`, study_id: `AuthorA${i} ${2020 + i}`, source: `J Med ${2020 + i};${10 + i}(${i}):e${123 + i}`, title: `${i}篇研究: SGLT2抑制剂对2型糖尿病心血管结局的影响`, authors: `Author A${i}, Author B${i}`, journal: `Journal of Medicine ${2020 + i}`, abstract: `背景: SGLT2抑制剂是一类新型降糖药物。本研究旨在评估其在<span class='p-source'>2型糖尿病成人患者</span>中的心血管保护作用。方法: 我们进行了一项多中心、双盲、<span class='s-source'>随机对照试验(RCT)</span>纳入了7020名患有心血管疾病的2型糖尿病患者。患者被随机分配接受每日10mg或25mg的<span class='i-source'>SGLT2药物</span>或<span class='c-source'>安慰剂</span>治疗。主要终点为主要心血管不良事件(MACE)。结论: 与安慰剂相比SGLT2抑制剂显著降低了MACE风险。`, model_a: { p: model_a_p, i: model_a_i, c: model_a_c, s: model_a_s, conclusion: model_a_conclusion, p_reason: "摘要明确提到研究对象为'2型糖尿病成人患者'。", i_reason: "干预措施为'SGLT2药物',符合要求。", c_reason: "对照组为'安慰剂',符合标准。", s_reason: "研究设计明确说明为'随机对照试验(RCT)'。", evidence: evidence_a }, model_b: { p: model_a_p, i: model_a_i, c: model_a_c, s: model_b_s, conclusion: model_b_conclusion, p_reason: "文中确认患者群体为'2型糖尿病成人'。", i_reason: "治疗方案包含'SGLT2药物'。", c_reason: "对照组为'安慰剂'。", s_reason: model_b_s === '✓' ? "方法部分描述了'随机对照试验'。" : "研究方法非RCT。", evidence: evidence_b }, final_decision: '待定', decision_method: '待定', exclusion_reason: exclusion_reason });}}
// V3: Generate separate simulated data for the results page
function generateSimulatedResultsData() {
simulatedResultsData = [];
const totalSimulated = 1000;
const includedSimulated = 120;
for (let i = 1; i <= totalSimulated; i++) {
const isIncluded = i <= includedSimulated;
const final_decision = isIncluded ? '纳入' : '排除';
const decision_method = Math.random() > 0.7 ? '人工审核' : (isIncluded ? 'AI自动纳入' : 'AI自动排除'); // More specific method
let exclusion_reason = null;
if (!isIncluded) {
const reasons = ["非RCT", "非目标人群", "干预不符", "对照不符", "综述", "重复研究", "语言不符"];
exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)];
}
// Simulate PICS content based on decision (more realistic)
const sim_p = (isIncluded || Math.random() > 0.2) ? "2型糖尿病成人患者" : "1型糖尿病";
const sim_i = (isIncluded || Math.random() > 0.1) ? "SGLT2抑制剂" : "二甲双胍";
const sim_c = (isIncluded || Math.random() > 0.3) ? "安慰剂" : "生活方式干预";
const sim_s = (isIncluded || Math.random() > 0.1) ? "RCT" : "观察性研究";
simulatedResultsData.push({
id: i + 1000,
pmid: `PMID${2234500 + i}`,
study_id: `SimAuthor${i} ${2021}`,
source: `Sim J ${2021};${i % 10 + 1}:s${i}`,
title: `模拟文献 ${i}: 关于SGLT2的研究`,
abstract: `这是一个模拟的摘要内容,长度可能会比较长,用于演示结果页面的摘要展示和展开收起功能...模拟摘要结束。`,
final_decision: final_decision,
decision_method: decision_method,
exclusion_reason: exclusion_reason,
// V4: Store simulated PICS content
sim_p: sim_p, sim_i: sim_i, sim_c: sim_c, sim_s: sim_s
});
}
}
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
generateData(); // For workbench
generateSimulatedResultsData(); // V3: For results page
showView('title-settings');
});
// --- NAVIGATION & VIEW MANAGEMENT ---
Object.keys(navLinks).forEach(key => { /* ... unchanged ... */ navLinks[key].addEventListener('click', (e) => { e.preventDefault(); const viewId = navLinks[key].getAttribute('data-view'); showView(viewId); }); });
function showView(viewId) {
currentViewId = viewId;
Object.values(views).forEach(v => v.classList.remove('active'));
if (views[viewId]) {
views[viewId].classList.add('active');
} else {
console.error("View not found:", viewId);
views['title-settings'].classList.add('active');
viewId = 'title-settings';
}
const stage = 'title';
const subView = viewId.split('-')[1];
const stageTitles = { 'title': '标题摘要初筛' };
const subViewTitles = { 'settings': '设置与启动', 'workbench': '审核工作台', 'results': '初筛结果' };
headerTitle.textContent = `${stageTitles[stage]} / ${subViewTitles[subView]}`;
Object.values(navLinks).forEach(link => link.classList.remove('active'));
if (navLinks[viewId]) navLinks[viewId].classList.add('active');
// V3: Always render results page with simulated data when viewed
if (viewId === 'title-results') {
renderResultsPage('title');
}
}
// --- TITLE SCREENING ---
function importLiterature(source) { /* ... unchanged ... */ startTitleScreeningBtn.disabled = false; startTitleScreeningBtn.classList.remove('bg-gray-400', 'cursor-not-allowed'); startTitleScreeningBtn.classList.add('bg-green-600', 'hover:bg-green-700'); titleImportHint.classList.add('hidden'); alert("模拟导入10篇文献成功"); literatureData.forEach(d=>d.final_decision='待定'); }
startTitleScreeningBtn.addEventListener('click', () => { /* ... unchanged ... */ taskStatusModal.classList.remove('hidden'); document.getElementById('task-modal-title').textContent = 'AI标题摘要初筛进行中...'; let progress = 0; const interval = setInterval(() => { progress += 10; document.getElementById('progress-bar-inner').style.width = `${progress}%`; document.getElementById('progress-text').textContent = `已处理 ${progress * (literatureData.length / 10)} / ${literatureData.length}`; if (progress >= 100) { clearInterval(interval); document.getElementById('show-workbench-btn').classList.remove('hidden'); } }, 100); });
document.getElementById('show-workbench-btn').addEventListener('click', () => {
taskStatusModal.classList.add('hidden');
showView('title-workbench');
renderTable('title');
});
// V7: Render Table with Original Header Structure
function renderTable(stage) {
const tableBodyId = 'title-screening-table-body'; // Only title stage
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataToRender = literatureData; // Workbench uses the interactive data
tableBody.innerHTML = dataToRender.map(doc => {
const isConflict = doc.model_a.conclusion !== doc.model_b.conclusion;
const rowId = `${stage}-row-${doc.id}`;
const expandedRowId = `${stage}-expanded-row-${doc.id}`;
const judgeCell = (model, dim) => `<td class="table-cell-judge px-2 py-3 border-l" onclick="openReviewModal(${doc.id}, '${model}', '${dim}', '${stage}')">${{ '✓': '<span class="text-green-600 font-bold">✓</span>', '✗': '<span class="text-red-600 font-bold">✗</span>', '?': '<span class="text-yellow-600 font-bold">?</span>' }[doc[model][dim]]}</td>`;
return `
<tr id="${rowId}" class="${isConflict ? 'table-conflict-row' : ''}">
<td class="px-2 py-3 expand-row-btn" onclick="toggleRow('${expandedRowId}', this)">+</td>
<td class="px-3 py-3 font-mono text-xs text-gray-500">${doc.pmid}</td>
<td class="px-4 py-3 font-semibold text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs text-gray-500 truncate" title="${doc.source}">${doc.source.split('.')[0]}...</td>
<!-- DS Judgements -->
${judgeCell('model_a', 'p')} ${judgeCell('model_a', 'i')} ${judgeCell('model_a', 'c')} ${judgeCell('model_a', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_a.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_a.conclusion}</td>
<!-- Q3 Judgements -->
${judgeCell('model_b', 'p')} ${judgeCell('model_b', 'i')} ${judgeCell('model_b', 'c')} ${judgeCell('model_b', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_b.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_b.conclusion}</td>
<!-- Status & Decision -->
<td class="px-4 py-3 border-l">${isConflict ? '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">冲突</span>' : '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">一致</span>'}</td>
<td class="px-4 py-3 border-l"><select class="rounded-md border-gray-300 text-xs" data-id="${doc.id}" data-stage="${stage}" onchange="updateDecision(this)"><option value="待定" ${doc.final_decision === '待定' ? 'selected' : ''}>待定</option><option value="纳入" ${doc.final_decision === '纳入' ? 'selected' : ''}>纳入</option><option value="排除" ${doc.final_decision === '排除' ? 'selected' : ''}>排除</option></select></td>
</tr>
<tr id="${expandedRowId}" class="expanded-row">
<td colspan="15" class="expanded-row-cell text-xs">
<div class="grid grid-cols-2 gap-4">
<div><strong class="text-gray-600">DS 证据:</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_a.evidence.p}</dd><dt>I:</dt><dd>${doc.model_a.evidence.i}</dd><dt>C:</dt><dd>${doc.model_a.evidence.c}</dd><dt>S:</dt><dd>${doc.model_a.evidence.s}</dd></dl></div>
<div><strong class="text-gray-600">Q3 证据:</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_b.evidence.p}</dd><dt>I:</dt><dd>${doc.model_b.evidence.i}</dd><dt>C:</dt><dd>${doc.model_b.evidence.c}</dd><dt>S:</dt><dd>${doc.model_b.evidence.s}</dd></dl></div>
</div>
</td>
</tr>
`;
}).join('');
}
function toggleRow(rowId, btn) { /* ... unchanged ... */ const row = document.getElementById(rowId); const isShown = row.classList.toggle('shown'); btn.textContent = isShown ? '-' : '+'; }
function updateDecision(selectElement) { /* ... unchanged ... */ const docId = parseInt(selectElement.dataset.id); const stage = selectElement.dataset.stage; const dataSet = literatureData; /* Only title data here */ const doc = dataSet.find(d => d.id === docId); if (doc) { doc.final_decision = selectElement.value; if (doc.final_decision !== '待定') { doc.decision_method = '人工审核'; } else { doc.decision_method = '待定'; } } }
function openReviewModal(docId, modelKey, dimensionKey, stage) { /* ... unchanged V7 version ... */ const dataSet = literatureData; /* Only title data */ const doc = dataSet.find(d => d.id === docId); if (!doc) return; const dimensionMap = { p: '人群 (P)', i: '干预 (I)', c: '对照 (C)', s: '研究设计 (S)' }; document.getElementById('modal-title').textContent = `审查: ${doc.title}`; document.getElementById('modal-doc-title').textContent = doc.title; document.getElementById('modal-doc-authors').textContent = doc.authors; document.getElementById('modal-doc-journal').textContent = `${doc.journal} (${doc.pmid})`; const abstractEl = document.getElementById('modal-abstract'); let abstractContent = doc.abstract; const sourceSpan = doc.abstract.match(new RegExp(`<span class='${dimensionKey.toLowerCase()}-source'>(.*?)</span>`)); let sourceText = sourceSpan ? sourceSpan[1] : ""; abstractContent = abstractContent.replace(/<span class='.-source'>/g, '').replace(/<\/span>/g, ''); if (sourceText) { abstractContent = abstractContent.replace(sourceText, `<mark class="abstract-highlight">${sourceText}</mark>`); } abstractEl.innerHTML = abstractContent; setTimeout(() => { const mark = abstractEl.querySelector('mark'); if (mark) { mark.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); document.getElementById('modal-dimension-title').textContent = `PICO(S) 判断依据`; const reasoningContainer = document.getElementById('modal-reasoning'); let reasoningHTML = `<div class="grid grid-cols-2 gap-6">`; ['model_a', 'model_b'].forEach(mKey => { const modelName = mKey === 'model_a' ? 'DeepSeek' : 'Qwen3'; reasoningHTML += `<div class="border rounded-lg p-4 bg-white"><h4 class="font-bold text-gray-700 mb-3">${modelName} 判断</h4><div class="space-y-2 text-sm mb-3">`; ['p', 'i', 'c', 's'].forEach(dim => { const icon = { '✓': '<span class="text-green-600 font-bold">✓ 符合</span>', '✗': '<span class="text-red-600 font-bold">✗ 不符</span>', '?': '<span class="text-yellow-600 font-bold">? 不确定</span>' }[doc[mKey][dim]]; reasoningHTML += `<p><strong>${dimensionMap[dim]}:</strong> ${icon}</p>`; }); reasoningHTML += `</div><p class="text-sm mb-2"><strong>总览结论:</strong> <span class="font-semibold ${doc[mKey].conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc[mKey].conclusion}</span></p><p class="text-sm text-gray-600"><strong>"${dimensionMap[dimensionKey]}" 判断理由:</strong> ${doc[mKey][`${dimensionKey}_reason`]}</p></div>`; }); reasoningHTML += `</div>`; reasoningContainer.innerHTML = reasoningHTML; reviewModal.classList.remove('hidden'); }
function closeReviewModal() { reviewModal.classList.add('hidden'); }
// V3: Simplified button action - just go to results view
completeTitleScreeningBtn.addEventListener('click', () => {
showView('title-results');
});
// --- TITLE RESULTS PAGE (V7 Enhanced & V3 Independent Data) ---
function renderResultsPage(stage) {
if (stage !== 'title') return;
// V3: Use simulated data for counts and table
const included = simulatedResultsData.filter(doc => doc.final_decision === '纳入');
const excluded = simulatedResultsData.filter(doc => doc.final_decision === '排除');
const totalSimulated = simulatedResultsData.length;
titleTotalCount.textContent = totalSimulated;
titleIncludedCount.textContent = included.length;
titleExcludedCount.textContent = excluded.length;
titleIncludedTabCount.textContent = included.length;
titleExcludedTabCount.textContent = excluded.length;
// V7: Prisma Summary Simulation based on simulated excluded data
prismaTotalExcluded.textContent = excluded.length;
prismaSExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非RCT').length;
prismaPExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非目标人群').length;
prismaIExcluded.textContent = excluded.filter(d => d.exclusion_reason === '干预不符').length;
prismaCExcluded.textContent = excluded.filter(d => d.exclusion_reason === '对照不符').length;
prismaDuplicateExcluded.textContent = excluded.filter(d => d.exclusion_reason === '重复研究').length;
prismaOtherExcluded.textContent = excluded.filter(d => !["非RCT", "非目标人群", "干预不符", "对照不符", "重复研究"].includes(d.exclusion_reason)).length;
renderResultsTable('title', 'included'); // Default view uses simulated data now
}
function switchResultTab(stage, tab) {
if (stage !== 'title') return;
const tabContainerId = 'title-result-tabs';
document.querySelectorAll(`#${tabContainerId} button`).forEach(btn => { btn.classList.remove('active', 'border-sky-500', 'text-sky-600'); btn.classList.add('border-transparent', 'text-gray-500'); });
const activeBtn = document.querySelector(`#${tabContainerId} button[data-tab="${tab}"]`);
activeBtn.classList.add('active', 'border-sky-500', 'text-sky-600');
activeBtn.classList.remove('border-transparent', 'text-gray-500');
renderResultsTable('title', tab); // Will use simulated data
}
// V7: Render Enhanced Results Table (Now uses simulatedResultsData)
// V4 Update: Display content instead of icons for PICS
function renderResultsTable(stage, tab) {
if (stage !== 'title') return;
const tableBodyId = 'title-results-table-body';
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataSet = simulatedResultsData;
const filteredData = dataSet.filter(doc => doc.final_decision === (tab === 'included' ? '纳入' : '排除'));
tableBody.innerHTML = filteredData.map(doc => {
// V4: Get content for PICS instead of icons
const pContent = doc.sim_p === '✓' ? "2型糖尿病成人患者" : "非目标人群";
const iContent = doc.sim_i === '✓' ? "SGLT2抑制剂" : "非SGLT2";
const cContent = doc.sim_c === '✓' ? "安慰剂" : (doc.sim_c === '?' ? "不适用/未报告" : "非安慰剂");
const sContent = doc.sim_s === '✓' ? "RCT" : "非RCT";
const shortAbstract = doc.abstract.substring(0, 100) + '...';
const fullAbstract = doc.abstract;
return `<tr>
<td class="px-3 py-3 font-mono text-xs">${doc.pmid}</td>
<td class="px-4 py-3 text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs truncate" title="${doc.source}">${doc.source}</td>
<td class="px-4 py-3 text-xs font-medium">${doc.title}</td>
<td class="px-4 py-3 text-xs text-gray-500 abstract-cell">
<span class="short-abstract">${shortAbstract}</span>
<span class="full-abstract">${fullAbstract}</span>
<a href="#" class="text-sky-600 text-[10px] block mt-1" onclick="toggleAbstract(this, event)">显示更多</a>
</td>
<td class="px-2 py-3 text-center results-pics-cell">${pContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${iContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${cContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${sContent}</td>
<td class="px-4 py-3">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${doc.final_decision === '纳入' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${doc.final_decision}</span>
</td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.final_decision === '排除' ? (doc.exclusion_reason || '未指定') : 'N/A'}</td>
</tr>`;
}).join('');
}
// V7: Toggle Abstract in results table
function toggleAbstract(link, event) { /* ... unchanged ... */ event.preventDefault(); const cell = link.closest('.abstract-cell'); const isExpanded = cell.classList.toggle('expanded'); link.textContent = isExpanded ? '收起' : '显示更多'; }
// Remove fulltext related functions as they are not needed in this prototype
</script>
</body>
</html>