Sprint 1-3 Completed (Backend + Frontend): Backend (Sprint 1-2): - Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection) - Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules) - Create protocol_schema with 2 tables (protocol_contexts, protocol_generations) - Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder) - Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude) - 6 API endpoints with full authentication - 10/10 API tests passed Frontend (Sprint 3): - Add Protocol Agent entry in AgentHub (indigo theme card) - Implement ProtocolAgentPage with 3-column layout - Collapsible sidebar (Gemini style, 48px <-> 280px) - StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints) - ChatArea with sync button and action cards integration - 100% prototype design restoration (608 lines CSS) - Detailed endpoints structure: baseline, exposure, outcomes, confounders Features: - 5-stage dialogue flow for research protocol design - Conversation-driven interaction with sync-to-protocol button - Real-time context state management - One-click protocol generation button (UI ready, backend pending) Database: - agent_schema: 6 tables for reusable Agent framework - protocol_schema: 2 tables for Protocol Agent - Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules Code Stats: - Backend: 13 files, 4338 lines - Frontend: 14 files, 2071 lines - Total: 27 files, 6409 lines Status: MVP core functionality completed, pending frontend-backend integration testing Next: Sprint 4 - One-click protocol generation + Word export
442 lines
23 KiB
HTML
442 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Protocol Agent V2.0 - 产品原型</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<!-- 引入 Lucide 图标 -->
|
||
<script src="https://unpkg.com/lucide@latest"></script>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||
|
||
body { font-family: 'Inter', 'Noto Sans SC', sans-serif; }
|
||
|
||
/* 模拟 Ant Design X 的 ultramodern 风格 */
|
||
.chat-bubble-ai {
|
||
background-color: #F3F4F6;
|
||
border-top-left-radius: 2px;
|
||
border-top-right-radius: 12px;
|
||
border-bottom-right-radius: 12px;
|
||
border-bottom-left-radius: 12px;
|
||
}
|
||
|
||
.chat-bubble-user {
|
||
background-color: #4F46E5; /* Indigo-600 */
|
||
color: white;
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 2px;
|
||
border-bottom-right-radius: 12px;
|
||
border-bottom-left-radius: 12px;
|
||
}
|
||
|
||
.reflexion-border {
|
||
border-left: 4px solid #9333EA; /* Purple-600 */
|
||
background: #F3E8FF; /* Purple-100 */
|
||
}
|
||
|
||
/* 状态面板动画 */
|
||
.flash-update {
|
||
animation: flash 1.5s ease-out;
|
||
}
|
||
|
||
@keyframes flash {
|
||
0% { background-color: #dbeafe; } /* blue-100 */
|
||
100% { background-color: transparent; }
|
||
}
|
||
|
||
/* 滚动条美化 */
|
||
::-webkit-scrollbar { width: 6px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
|
||
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
||
</style>
|
||
</head>
|
||
<body class="bg-gray-100 h-screen flex flex-col overflow-hidden">
|
||
|
||
<!-- Top Navigation -->
|
||
<header class="bg-white border-b border-gray-200 h-14 flex items-center justify-between px-4 shadow-sm z-10">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold shadow-lg shadow-indigo-200">
|
||
<i data-lucide="bot" class="w-5 h-5"></i>
|
||
</div>
|
||
<div>
|
||
<h1 class="font-bold text-gray-800 text-sm">研究方案制定 Agent</h1>
|
||
<div class="flex items-center gap-1.5">
|
||
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
||
<span class="text-xs text-gray-500">Orchestrator Online</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-4">
|
||
<div class="hidden md:flex items-center gap-2 px-3 py-1.5 bg-gray-50 rounded-full border border-gray-200">
|
||
<span class="text-xs font-medium text-gray-500">当前阶段:</span>
|
||
<span class="text-xs font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded border border-indigo-100">Step 5: 样本量计算</span>
|
||
</div>
|
||
<button class="p-2 hover:bg-gray-100 rounded-full text-gray-600">
|
||
<i data-lucide="settings" class="w-5 h-5"></i>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Workspace -->
|
||
<main class="flex-1 flex overflow-hidden">
|
||
|
||
<!-- Left: Chat Interface (70%) -->
|
||
<section class="flex-1 flex flex-col bg-white relative">
|
||
|
||
<!-- Chat History -->
|
||
<div id="chat-container" class="flex-1 overflow-y-auto p-4 md:p-6 space-y-6 pb-20">
|
||
|
||
<!-- Welcome Message -->
|
||
<div class="flex gap-4">
|
||
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
|
||
<i data-lucide="sparkles" class="w-4 h-4 text-indigo-600"></i>
|
||
</div>
|
||
<div class="space-y-1 max-w-[80%]">
|
||
<div class="text-xs text-gray-400 ml-1">Protocol Agent • 10:00 AM</div>
|
||
<div class="chat-bubble-ai p-4 text-sm text-gray-800 shadow-sm">
|
||
<p>您好!我是您的研究方案制定助手。</p>
|
||
<p class="mt-2">我已经记录了您关于<strong>“阿司匹林预防老年高血压患者中风”</strong>的科学问题和 PICO 信息。</p>
|
||
<p class="mt-2">根据当前的 RCT 设计,我们需要进行<strong>样本量计算</strong>以支撑伦理审查。您准备好开始了吗?</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- User Message -->
|
||
<div class="flex gap-4 flex-row-reverse">
|
||
<div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center shrink-0">
|
||
<i data-lucide="user" class="w-4 h-4 text-gray-500"></i>
|
||
</div>
|
||
<div class="space-y-1 max-w-[80%]">
|
||
<div class="text-xs text-gray-400 text-right mr-1">User • 10:02 AM</div>
|
||
<div class="chat-bubble-user p-4 text-sm shadow-sm">
|
||
好的,帮我计算一下样本量。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI Plan & Action Card (Deep Link) -->
|
||
<div class="flex gap-4">
|
||
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
|
||
<i data-lucide="brain-circuit" class="w-4 h-4 text-indigo-600"></i>
|
||
</div>
|
||
<div class="space-y-1 max-w-[80%]">
|
||
<div class="text-xs text-gray-400 ml-1">Protocol Agent • 10:02 AM</div>
|
||
|
||
<!-- The Action Card Component -->
|
||
<div class="border border-indigo-200 bg-indigo-50/50 rounded-xl p-4 shadow-sm w-[380px]">
|
||
<div class="flex items-start gap-3 mb-3">
|
||
<div class="bg-indigo-100 p-2 rounded-lg">
|
||
<i data-lucide="calculator" class="w-5 h-5 text-indigo-600"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="font-bold text-gray-900 text-sm">建议执行:样本量计算</h3>
|
||
<p class="text-xs text-gray-500 mt-0.5">基于双侧两样本均数比较 (RCT)</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Prefilled Parameters Preview -->
|
||
<div class="bg-white rounded border border-indigo-100 p-2 mb-3 space-y-1">
|
||
<div class="flex justify-between text-xs">
|
||
<span class="text-gray-500">Alpha (α):</span>
|
||
<span class="font-mono font-medium">0.05</span>
|
||
</div>
|
||
<div class="flex justify-between text-xs">
|
||
<span class="text-gray-500">Power (1-β):</span>
|
||
<span class="font-mono font-medium">0.80</span>
|
||
</div>
|
||
<div class="flex justify-between text-xs">
|
||
<span class="text-gray-500">Effect Size:</span>
|
||
<span class="font-mono font-medium">预估 0.15 vs 0.10</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button onclick="openToolModal()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium py-2 rounded-lg transition flex items-center justify-center gap-2 shadow-sm shadow-indigo-200">
|
||
<span>🚀 前往计算工具</span>
|
||
<i data-lucide="external-link" class="w-3 h-3"></i>
|
||
</button>
|
||
<p class="text-[10px] text-center text-gray-400 mt-2">点击将打开工具面板,参数已自动预填</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dynamic Content Will Be Appended Here -->
|
||
|
||
</div>
|
||
|
||
<!-- Input Area -->
|
||
<div class="p-4 border-t border-gray-100 bg-white absolute bottom-0 w-full">
|
||
<div class="relative">
|
||
<input type="text" placeholder="输入您的指令..." class="w-full pl-4 pr-12 py-3 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition text-sm">
|
||
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
|
||
<i data-lucide="send" class="w-4 h-4"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Right: Protocol State Panel (30%) -->
|
||
<aside class="w-[350px] bg-gray-50 border-l border-gray-200 flex flex-col hidden md:flex">
|
||
<div class="p-4 border-b border-gray-200 bg-white flex justify-between items-center">
|
||
<h2 class="font-bold text-gray-800 text-sm flex items-center gap-2">
|
||
<i data-lucide="file-text" class="w-4 h-4 text-gray-500"></i>
|
||
方案状态 (Context)
|
||
</h2>
|
||
<span class="text-[10px] bg-green-100 text-green-700 px-2 py-0.5 rounded-full font-medium">Synced</span>
|
||
</div>
|
||
|
||
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
||
|
||
<!-- Section 1: Scientific Question -->
|
||
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
|
||
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2 tracking-wider">01 科学问题</h3>
|
||
<p class="text-xs text-gray-700 leading-relaxed font-medium">
|
||
阿司匹林预防老年高血压患者中风的疗效与安全性研究
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Section 2: PICO -->
|
||
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
|
||
<div class="flex justify-between items-center mb-2">
|
||
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider">02 PICO</h3>
|
||
<button class="text-[10px] text-indigo-600 hover:underline">编辑</button>
|
||
</div>
|
||
<div class="space-y-2">
|
||
<div class="flex gap-2">
|
||
<span class="bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">P</span>
|
||
<span class="text-xs text-gray-600">≥65岁原发性高血压患者</span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<span class="bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">I</span>
|
||
<span class="text-xs text-gray-600">阿司匹林肠溶片 100mg/d</span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<span class="bg-orange-100 text-orange-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">C</span>
|
||
<span class="text-xs text-gray-600">安慰剂</span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<span class="bg-green-100 text-green-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">O</span>
|
||
<span class="text-xs text-gray-600">5年内缺血性脑卒中发生率</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 3: Study Design -->
|
||
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
|
||
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2 tracking-wider">03 研究设计</h3>
|
||
<div class="flex flex-wrap gap-2">
|
||
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">RCT</span>
|
||
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">双盲</span>
|
||
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">多中心</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 4: Sample Size (The Active One) -->
|
||
<div id="state-sample-size" class="bg-white p-3 rounded-lg border-2 border-indigo-100 shadow-sm transition-colors duration-500">
|
||
<h3 class="text-xs font-bold text-indigo-600 uppercase mb-2 tracking-wider flex justify-between">
|
||
04 样本量
|
||
<i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i>
|
||
</h3>
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-xs text-gray-500">总样本量 (N)</span>
|
||
<span id="n-value-display" class="text-lg font-bold text-gray-300 font-mono">待计算...</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</aside>
|
||
|
||
</main>
|
||
|
||
<!-- Modal: Simulated External Tool (Deep Link Destination) -->
|
||
<div id="tool-modal" class="fixed inset-0 bg-black/50 hidden z-50 flex items-center justify-center backdrop-blur-sm opacity-0 transition-opacity duration-300">
|
||
<div class="bg-white rounded-xl shadow-2xl w-[600px] overflow-hidden transform scale-95 transition-transform duration-300" id="tool-modal-content">
|
||
<!-- Tool Header -->
|
||
<div class="bg-gray-800 text-white p-4 flex justify-between items-center">
|
||
<div class="flex items-center gap-2">
|
||
<i data-lucide="calculator" class="w-5 h-5 text-indigo-400"></i>
|
||
<span class="font-bold">统计工具箱 v3.0 | 样本量计算器</span>
|
||
</div>
|
||
<button onclick="closeToolModal()" class="text-gray-400 hover:text-white">
|
||
<i data-lucide="x" class="w-5 h-5"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tool Body (Simulated Form) -->
|
||
<div class="p-6 bg-gray-50">
|
||
<div class="bg-blue-50 border border-blue-200 text-blue-800 text-xs p-3 rounded mb-4 flex gap-2">
|
||
<i data-lucide="info" class="w-4 h-4 shrink-0"></i>
|
||
已从 Protocol Agent 自动填充参数。修改参数后请点击“重新计算”。
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-bold text-gray-500 mb-1">检验类型</label>
|
||
<select class="w-full text-sm border-gray-300 rounded p-2 border">
|
||
<option>双侧两样本均数比较</option>
|
||
<option>卡方检验</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-bold text-gray-500 mb-1">检验水准 (α)</label>
|
||
<input type="number" value="0.05" class="w-full text-sm border-gray-300 rounded p-2 border">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-bold text-gray-500 mb-1">把握度 (1-β)</label>
|
||
<input type="number" value="0.80" class="w-full text-sm border-gray-300 rounded p-2 border">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-bold text-gray-500 mb-1">脱落率 (%)</label>
|
||
<input type="number" value="10" class="w-full text-sm border-gray-300 rounded p-2 border">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Effect Size Params -->
|
||
<div class="bg-white p-3 rounded border border-gray-200 mb-4">
|
||
<h4 class="text-xs font-bold text-gray-800 mb-2">效应量参数 (Effect Size)</h4>
|
||
<div class="flex gap-4">
|
||
<div class="flex-1">
|
||
<label class="block text-[10px] text-gray-400">实验组预估发生率</label>
|
||
<input type="number" value="0.10" class="w-full font-mono text-sm border-gray-300 rounded p-1 border">
|
||
</div>
|
||
<div class="flex-1">
|
||
<label class="block text-[10px] text-gray-400">对照组预估发生率</label>
|
||
<input type="number" value="0.15" class="w-full font-mono text-sm border-gray-300 rounded p-1 border">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Result Area -->
|
||
<div class="flex items-center justify-between bg-gray-100 p-4 rounded-lg mb-6">
|
||
<span class="text-sm font-bold text-gray-600">计算结果 (N):</span>
|
||
<span id="tool-result" class="text-2xl font-bold text-indigo-600 font-mono">---</span>
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
<div class="flex justify-end gap-3">
|
||
<button onclick="calculate()" class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm font-medium rounded hover:bg-gray-50">
|
||
试算
|
||
</button>
|
||
<button id="btn-sync" onclick="syncAndClose()" disabled class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2">
|
||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||
确认并同步到方案
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 初始化 Lucide 图标
|
||
lucide.createIcons();
|
||
|
||
// Modal 控制逻辑
|
||
const modal = document.getElementById('tool-modal');
|
||
const modalContent = document.getElementById('tool-modal-content');
|
||
const btnSync = document.getElementById('btn-sync');
|
||
const toolResult = document.getElementById('tool-result');
|
||
|
||
function openToolModal() {
|
||
modal.classList.remove('hidden');
|
||
// 简单的淡入动画
|
||
setTimeout(() => {
|
||
modal.classList.remove('opacity-0');
|
||
modalContent.classList.remove('scale-95');
|
||
}, 10);
|
||
}
|
||
|
||
function closeToolModal() {
|
||
modal.classList.add('opacity-0');
|
||
modalContent.classList.add('scale-95');
|
||
setTimeout(() => {
|
||
modal.classList.add('hidden');
|
||
}, 300);
|
||
}
|
||
|
||
// 模拟计算过程
|
||
function calculate() {
|
||
toolResult.innerText = "Calculing...";
|
||
setTimeout(() => {
|
||
toolResult.innerText = "386";
|
||
btnSync.disabled = false;
|
||
btnSync.classList.add('animate-pulse');
|
||
}, 600);
|
||
}
|
||
|
||
// 核心:同步回写逻辑 (Reflexion Loop)
|
||
function syncAndClose() {
|
||
closeToolModal();
|
||
|
||
// 1. 更新右侧状态面板 (State Panel Update)
|
||
const stateCard = document.getElementById('state-sample-size');
|
||
const nValueDisplay = document.getElementById('n-value-display');
|
||
|
||
// 视觉反馈:闪烁一下
|
||
stateCard.classList.add('flash-update');
|
||
stateCard.classList.remove('border-indigo-100');
|
||
stateCard.classList.add('border-green-500'); // 变成绿色边框
|
||
|
||
// 移除 Loading 图标
|
||
stateCard.querySelector('i').remove();
|
||
|
||
// 更新数值
|
||
setTimeout(() => {
|
||
nValueDisplay.innerText = "N = 386";
|
||
nValueDisplay.classList.remove('text-gray-300');
|
||
nValueDisplay.classList.add('text-indigo-600');
|
||
}, 500);
|
||
|
||
// 2. 更新聊天界面 (Chat Update with Reflexion)
|
||
const chatContainer = document.getElementById('chat-container');
|
||
|
||
// 插入系统消息
|
||
const systemMsgHTML = `
|
||
<div class="flex justify-center my-4">
|
||
<span class="text-[10px] bg-gray-100 text-gray-500 px-2 py-1 rounded-full border border-gray-200">
|
||
<i data-lucide="check-circle" class="w-3 h-3 inline mr-1 text-green-500"></i>
|
||
系统消息: 用户已同步样本量结果 N=386
|
||
</span>
|
||
</div>
|
||
`;
|
||
chatContainer.insertAdjacentHTML('beforeend', systemMsgHTML);
|
||
|
||
// 模拟 AI 思考 (Reflexion)
|
||
setTimeout(() => {
|
||
const aiReflexionHTML = `
|
||
<div class="flex gap-4 animate-fade-in-up">
|
||
<div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center shrink-0 border border-purple-200">
|
||
<i data-lucide="check-check" class="w-4 h-4 text-purple-600"></i>
|
||
</div>
|
||
<div class="space-y-1 max-w-[80%]">
|
||
<div class="text-xs text-purple-500 ml-1 font-bold">Reflexion Guard • 刚刚</div>
|
||
<div class="chat-bubble-ai p-4 text-sm text-gray-800 shadow-sm reflexion-border">
|
||
<p class="font-bold text-purple-700 text-xs mb-1 mb-2 flex items-center gap-1">
|
||
<i data-lucide="shield-check" class="w-3 h-3"></i>
|
||
质量校验通过
|
||
</p>
|
||
<p>我已校验您的计算结果:</p>
|
||
<ul class="list-disc list-inside mt-1 text-gray-600 text-xs space-y-1">
|
||
<li>N=386 满足 RCT 设计对统计效能 (Power=0.8) 的要求。</li>
|
||
<li>参数设置符合心血管预防研究的常规标准。</li>
|
||
</ul>
|
||
<p class="mt-3">数值已同步到方案中。接下来,建议我们进行<strong>第五步:CRF (病例报告表) 设计</strong>。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
chatContainer.insertAdjacentHTML('beforeend', aiReflexionHTML);
|
||
lucide.createIcons(); // 重新渲染新插入的图标
|
||
|
||
// 自动滚动到底部
|
||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||
}, 1000);
|
||
}
|
||
|
||
// 点击遮罩关闭
|
||
modal.addEventListener('click', (e) => {
|
||
if (e.target === modal) closeToolModal();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |