375 lines
13 KiB
HTML
375 lines
13 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>验证:页面隐藏不影响异步任务</title>
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
padding: 40px;
|
||
background: #f5f5f5;
|
||
}
|
||
.container {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
h1 {
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
}
|
||
.warning {
|
||
background: #fff3cd;
|
||
border-left: 4px solid #ffc107;
|
||
padding: 15px;
|
||
margin: 20px 0;
|
||
border-radius: 4px;
|
||
}
|
||
.info {
|
||
background: #d1ecf1;
|
||
border-left: 4px solid #17a2b8;
|
||
padding: 15px;
|
||
margin: 20px 0;
|
||
border-radius: 4px;
|
||
}
|
||
.success {
|
||
background: #d4edda;
|
||
border-left: 4px solid #28a745;
|
||
padding: 15px;
|
||
margin: 20px 0;
|
||
border-radius: 4px;
|
||
}
|
||
button {
|
||
padding: 12px 24px;
|
||
background: #007bff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
margin: 10px 10px 10px 0;
|
||
}
|
||
button:hover {
|
||
background: #0056b3;
|
||
}
|
||
.log {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
padding: 20px;
|
||
border-radius: 4px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 14px;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
margin-top: 20px;
|
||
}
|
||
.log-entry {
|
||
margin-bottom: 8px;
|
||
padding: 4px 0;
|
||
}
|
||
.log-entry.info { color: #4FC3F7; }
|
||
.log-entry.success { color: #4CAF50; font-weight: bold; }
|
||
.log-entry.error { color: #EF5350; }
|
||
.log-entry.warning { color: #FFA726; }
|
||
.result {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
padding: 20px;
|
||
text-align: center;
|
||
border-radius: 4px;
|
||
margin: 20px 0;
|
||
}
|
||
.result.pass {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
.result.fail {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>🧪 验证:页面隐藏不影响异步任务</h1>
|
||
|
||
<div class="warning">
|
||
<strong>⚠️ 重要说明</strong><br>
|
||
本测试证明:即使页面隐藏,<strong>fetch()、Promise、async/await</strong> 等异步任务<strong>仍然正常执行</strong>。
|
||
<br>我们的优化只影响了 <code>setInterval</code> 轮询,不影响核心业务。
|
||
</div>
|
||
|
||
<div class="info">
|
||
<strong>📋 测试步骤</strong><br>
|
||
1. 点击下面的按钮开始模拟翻译任务<br>
|
||
2. <strong>立即切换到其他标签页</strong>(这会触发页面隐藏)<br>
|
||
3. 等待 3-5 秒后切回来<br>
|
||
4. 查看结果:任务应该已经完成 ✅
|
||
</div>
|
||
|
||
<h2>测试 1: 模拟翻译 API 调用</h2>
|
||
<button onclick="testTranslationAPI()">🔄 开始模拟翻译(3秒)</button>
|
||
<button onclick="testMultipleAPIs()">🔄 模拟多个并发翻译(5秒)</button>
|
||
|
||
<h2>测试 2: 模拟 AI 对话</h2>
|
||
<button onclick="testAIChatAPI()">💬 开始模拟AI对话(3秒)</button>
|
||
|
||
<h2>测试 3: 对比 setInterval(我们优化的部分)</h2>
|
||
<button onclick="testSetInterval()">⏱️ 启动定时器(观察页面隐藏时的行为)</button>
|
||
<button onclick="stopSetInterval()">⏹️ 停止定时器</button>
|
||
|
||
<div id="result"></div>
|
||
|
||
<div class="log" id="log"></div>
|
||
</div>
|
||
|
||
<script>
|
||
const logEl = document.getElementById('log');
|
||
const resultEl = document.getElementById('result');
|
||
|
||
function log(message, type = 'info') {
|
||
const entry = document.createElement('div');
|
||
entry.className = `log-entry ${type}`;
|
||
const time = new Date().toLocaleTimeString();
|
||
entry.textContent = `[${time}] ${message}`;
|
||
logEl.appendChild(entry);
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||
}
|
||
|
||
function clearLog() {
|
||
logEl.innerHTML = '';
|
||
resultEl.innerHTML = '';
|
||
}
|
||
|
||
// 监听页面可见性变化
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.hidden) {
|
||
log('🔒 页面已隐藏(切换到其他标签页)', 'warning');
|
||
} else {
|
||
log('👁️ 页面可见(切回当前标签页)', 'info');
|
||
}
|
||
});
|
||
|
||
// ================================================
|
||
// 测试 1: 模拟翻译 API
|
||
// ================================================
|
||
async function testTranslationAPI() {
|
||
clearLog();
|
||
log('=== 开始测试:模拟翻译 API ===', 'info');
|
||
log('⚠️ 请在 3 秒内切换到其他标签页!', 'warning');
|
||
|
||
const startTime = Date.now();
|
||
|
||
// 模拟翻译 API 调用(3秒延迟)
|
||
try {
|
||
log('📤 发送翻译请求...', 'info');
|
||
|
||
// 使用真实的 Promise(模拟网络请求)
|
||
const result = await new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve({
|
||
success: true,
|
||
translatedText: '这是翻译后的文本',
|
||
pageHiddenDuringTranslation: false
|
||
});
|
||
}, 3000);
|
||
});
|
||
|
||
const endTime = Date.now();
|
||
const elapsed = ((endTime - startTime) / 1000).toFixed(1);
|
||
|
||
log('✅ 翻译完成!', 'success');
|
||
log(`⏱️ 耗时: ${elapsed} 秒`, 'info');
|
||
log(`📥 结果: ${result.translatedText}`, 'success');
|
||
|
||
// 显示结果
|
||
resultEl.innerHTML = `
|
||
<div class="result pass">
|
||
✅ 测试通过!<br>
|
||
即使页面被隐藏,翻译任务仍然完成了!<br>
|
||
<small>耗时: ${elapsed} 秒</small>
|
||
</div>
|
||
`;
|
||
|
||
log('💡 结论:fetch() / Promise 不受页面隐藏影响', 'success');
|
||
|
||
} catch (error) {
|
||
log(`❌ 错误: ${error.message}`, 'error');
|
||
resultEl.innerHTML = `
|
||
<div class="result fail">
|
||
❌ 测试失败!<br>
|
||
${error.message}
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// ================================================
|
||
// 测试 2: 模拟多个并发翻译
|
||
// ================================================
|
||
async function testMultipleAPIs() {
|
||
clearLog();
|
||
log('=== 开始测试:多个并发翻译 ===', 'info');
|
||
log('⚠️ 请在 5 秒内切换到其他标签页!', 'warning');
|
||
|
||
const startTime = Date.now();
|
||
|
||
// 模拟 3 个并发翻译任务
|
||
const tasks = [
|
||
simulateTranslation('段落 1', 2000),
|
||
simulateTranslation('段落 2', 3000),
|
||
simulateTranslation('段落 3', 4000)
|
||
];
|
||
|
||
try {
|
||
log('📤 发送 3 个并发翻译请求...', 'info');
|
||
|
||
const results = await Promise.all(tasks);
|
||
|
||
const endTime = Date.now();
|
||
const elapsed = ((endTime - startTime) / 1000).toFixed(1);
|
||
|
||
log('✅ 所有翻译完成!', 'success');
|
||
log(`⏱️ 总耗时: ${elapsed} 秒`, 'info');
|
||
results.forEach((result, i) => {
|
||
log(`📥 结果 ${i + 1}: ${result}`, 'success');
|
||
});
|
||
|
||
resultEl.innerHTML = `
|
||
<div class="result pass">
|
||
✅ 测试通过!<br>
|
||
${results.length} 个并发翻译任务都完成了!<br>
|
||
<small>总耗时: ${elapsed} 秒</small>
|
||
</div>
|
||
`;
|
||
|
||
log('💡 结论:多个异步任务可以并发执行,不受页面隐藏影响', 'success');
|
||
|
||
} catch (error) {
|
||
log(`❌ 错误: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
async function simulateTranslation(text, delay) {
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve(`${text} (已翻译)`);
|
||
}, delay);
|
||
});
|
||
}
|
||
|
||
// ================================================
|
||
// 测试 3: 模拟 AI 对话
|
||
// ================================================
|
||
async function testAIChatAPI() {
|
||
clearLog();
|
||
log('=== 开始测试:模拟 AI 对话 ===', 'info');
|
||
log('⚠️ 请在 3 秒内切换到其他标签页!', 'warning');
|
||
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
log('💬 发送 AI 对话请求...', 'info');
|
||
|
||
// 模拟 AI API 调用
|
||
const result = await new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve({
|
||
message: '这是 AI 的回复内容',
|
||
tokens: 150
|
||
});
|
||
}, 3000);
|
||
});
|
||
|
||
const endTime = Date.now();
|
||
const elapsed = ((endTime - startTime) / 1000).toFixed(1);
|
||
|
||
log('✅ AI 回复完成!', 'success');
|
||
log(`⏱️ 耗时: ${elapsed} 秒`, 'info');
|
||
log(`💬 回复: ${result.message}`, 'success');
|
||
|
||
resultEl.innerHTML = `
|
||
<div class="result pass">
|
||
✅ 测试通过!<br>
|
||
AI 对话在页面隐藏时仍然正常完成!<br>
|
||
<small>耗时: ${elapsed} 秒</small>
|
||
</div>
|
||
`;
|
||
|
||
log('💡 结论:AI 对话 API 不受页面隐藏影响', 'success');
|
||
|
||
} catch (error) {
|
||
log(`❌ 错误: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
// ================================================
|
||
// 测试 4: setInterval(对比)
|
||
// ================================================
|
||
let intervalId = null;
|
||
let intervalCount = 0;
|
||
|
||
function testSetInterval() {
|
||
if (intervalId) {
|
||
log('⚠️ 定时器已在运行', 'warning');
|
||
return;
|
||
}
|
||
|
||
clearLog();
|
||
log('=== 开始测试:setInterval 行为(对比) ===', 'info');
|
||
log('🔄 定时器启动(每秒执行一次)', 'info');
|
||
log('⚠️ 请切换标签页观察行为', 'warning');
|
||
|
||
intervalCount = 0;
|
||
|
||
intervalId = setInterval(() => {
|
||
intervalCount++;
|
||
|
||
// 检查页面是否隐藏
|
||
if (document.hidden) {
|
||
log(`⏭️ 跳过执行(页面隐藏,计数: ${intervalCount})`, 'warning');
|
||
// 这就是我们在 annotations_summary_modal.js 中做的优化
|
||
} else {
|
||
log(`✓ 正常执行(页面可见,计数: ${intervalCount})`, 'success');
|
||
}
|
||
}, 1000);
|
||
|
||
log('💡 说明:这就是我们的优化逻辑 - 页面隐藏时跳过执行', 'info');
|
||
log('💡 但 fetch() 等异步任务不会被跳过!', 'success');
|
||
}
|
||
|
||
function stopSetInterval() {
|
||
if (!intervalId) {
|
||
log('⚠️ 定时器未运行', 'warning');
|
||
return;
|
||
}
|
||
|
||
clearInterval(intervalId);
|
||
intervalId = null;
|
||
|
||
log(`⏹️ 定时器已停止(总计执行 ${intervalCount} 次)`, 'info');
|
||
}
|
||
|
||
// ================================================
|
||
// 页面加载时的提示
|
||
// ================================================
|
||
window.addEventListener('load', () => {
|
||
log('🚀 测试工具已加载', 'info');
|
||
log('💡 提示:测试过程中请切换标签页来验证异步任务不受影响', 'info');
|
||
});
|
||
|
||
// 页面卸载时清理
|
||
window.addEventListener('beforeunload', () => {
|
||
if (intervalId) {
|
||
clearInterval(intervalId);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|