diff --git a/edc-web/app/static/js/test_op.js b/edc-web/app/static/js/test_op.js index 474ca88..7928db5 100644 --- a/edc-web/app/static/js/test_op.js +++ b/edc-web/app/static/js/test_op.js @@ -1,4 +1,4 @@ -// 测试操作页 +// 测试操作页 — 间隔/超时机制 let autoRunning = false; let autoTotal = 0; @@ -7,9 +7,15 @@ let autoFailed = 0; let autoRemaining = 0; let autoStartTime = ""; let localSinceStr = ""; + let pollInterval = null; -let timeoutTimers = {}; // record_id → timer -const TIMEOUT_MS = 10000; +let nextCmdTimer = null; // 间隔等待定时器 +let timeoutTimer = null; // 超时定时器 +let timeoutAt = 0; // 超时时刻 (Date.now() + timeoutMs) +let lastDoneCount = 0; // 上一轮 done 数,检测新响应 +let cmdSentAt = 0; // 最近一次发送 0xB0 时间 +let intervalMs = 3000; // 默认 3s +let timeoutMs = 10000; // 默认 10s // ─── 手动指令 ───────────────────────────────── @@ -43,14 +49,20 @@ async function startAuto() { const count = parseInt(document.getElementById("test-count").value) || 10; if (count < 1) return; + // 读取参数 + intervalMs = (parseFloat(document.getElementById("interval-sec").value) || 3) * 1000; + timeoutMs = (parseFloat(document.getElementById("timeout-sec").value) || 10) * 1000; + if (timeoutMs < 1000) timeoutMs = 1000; + // 重置 autoRunning = true; autoTotal = count; autoDone = 0; autoFailed = 0; autoRemaining = count; - autoStartTime = new Date().toISOString(); // 记录开始时间 (UTC) - // MySQL 存的是本地时间,需要转本地格式传给后端过滤 + lastDoneCount = 0; + autoStartTime = new Date().toISOString(); + const now = new Date(); localSinceStr = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + @@ -73,8 +85,8 @@ async function startAuto() { document.getElementById("stat-done").textContent = "0"; document.getElementById("stat-failed").textContent = "0"; document.getElementById("stat-remaining").textContent = count; + document.getElementById("auto-status").textContent = ""; - // 清空测试明细 document.querySelector("#records-table tbody").innerHTML = ""; document.getElementById("records-empty").style.display = "block"; @@ -82,7 +94,7 @@ async function startAuto() { btn.textContent = "结束"; btn.className = "btn-stop"; - // 插入第一条 0xB0 + // 清除旧记录 + 插入第一条 try { const resp = await fetch("/api/automation/start", { method: "POST", @@ -91,8 +103,10 @@ async function startAuto() { }); const data = await resp.json(); if (data.ok) { - // 启动超时计时器 - startTimeout(data.first_record_id); + cmdSentAt = Date.now(); + timeoutAt = cmdSentAt + timeoutMs; + armTimeout(); + setStatus(`已发送,超时 ${timeoutMs/1000}s...`); } } catch (e) { console.error("启动失败:", e); @@ -100,26 +114,84 @@ async function startAuto() { } // 启动轮询 - pollInterval = setInterval(pollProgress, 1000); + pollInterval = setInterval(pollProgress, 500); } function stopAuto() { autoRunning = false; clearInterval(pollInterval); pollInterval = null; - // 记录结束时间 + clearTimeout(nextCmdTimer); + nextCmdTimer = null; + clearTimeout(timeoutTimer); + timeoutTimer = null; + document.getElementById("auto-status").textContent = ""; document.getElementById("time-end").textContent = new Date().toLocaleString(); - // 清除所有超时计时器 - for (const id in timeoutTimers) { - clearTimeout(timeoutTimers[id]); - } - timeoutTimers = {}; const btn = document.getElementById("btn-auto"); btn.textContent = "开始"; btn.className = "btn-start"; } +// ─── 发送下一条 0xB0 ──────────────────────────── + +async function sendNextCmd() { + if (!autoRunning) return; + if (autoRemaining <= 0) { + // 检查是否还有等待完成的 + if (autoDone + autoFailed >= autoTotal) { + stopAuto(); + return; + } + } + + try { + const r = await fetch("/api/command", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ dnt_id: DNT_ID, cmd: "B0" }), + }); + const rd = await r.json(); + if (rd.ok) { + cmdSentAt = Date.now(); + timeoutAt = cmdSentAt + timeoutMs; + armTimeout(); + setStatus(`已发送,超时 ${timeoutMs/1000}s...`); + } + } catch (e) { + console.error("发送下一条失败:", e); + } +} + +// ─── 超时定时器 ───────────────────────────────── + +function armTimeout() { + clearTimeout(timeoutTimer); + timeoutTimer = setTimeout(onTimeout, timeoutMs + 500); // +500ms 容差 +} + +function onTimeout() { + if (!autoRunning) return; + // 超时:即使没收到回复也计数失败,立即发下一条 + autoFailed++; + autoRemaining = autoTotal - autoDone - autoFailed; + if (autoRemaining < 0) autoRemaining = 0; + updateUI(); + setStatus(`超时!立即发下一条...`); + console.log(`超时 (${timeoutMs}ms),失败数: ${autoFailed}`); + + clearTimeout(nextCmdTimer); + nextCmdTimer = null; + + if (autoRemaining > 0) { + sendNextCmd(); + } else { + stopAuto(); + } +} + +// ─── 轮询 ────────────────────────────────────── + async function pollProgress() { if (!autoRunning) return; @@ -129,67 +201,64 @@ async function pollProgress() { const stats = data.stats; // 更新计数 - autoDone = stats.done || 0; - autoFailed = stats.failed || 0; - autoRemaining = autoTotal - autoDone - autoFailed; - if (autoRemaining < 0) autoRemaining = 0; + const newDone = stats.done || 0; + const newFailed = stats.failed || 0; - updateUI(); + if (newDone > lastDoneCount) { + // 收到新回复 → 清除超时,开始间隔等待 + const delta = newDone - lastDoneCount; + lastDoneCount = newDone; + autoDone = newDone; + autoFailed = newFailed; + autoRemaining = autoTotal - autoDone - autoFailed; + if (autoRemaining < 0) autoRemaining = 0; - // 显示最新结果 - if (data.latest) { - renderLatest(data.latest); - } + clearTimeout(timeoutTimer); + timeoutTimer = null; + clearTimeout(nextCmdTimer); + nextCmdTimer = null; - // 显示平均值 - if (data.averages) { - renderAverages(data.averages); - } + updateUI(); - // 显示波动测试数据 - if (data.latest_wave) { - renderLatestWave(data.latest_wave); - } - - // 显示本轮测试明细 - if (data.records) { - renderRecords(data.records); - } - - // 自动插入下一条 0xB0 - if (autoRemaining > 0) { - // 检查是否还有 pending 的记录,没有则插入新的 - if (stats.pending === 0 && stats.sent === 0) { - try { - const r = await fetch("/api/command", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ dnt_id: DNT_ID, cmd: "B0" }), - }); - const rd = await r.json(); - if (rd.ok) { - startTimeout(rd.record_id); - } - } catch (e) { - console.error("插入下一条失败:", e); - } + if (autoRemaining > 0) { + const wait = (intervalMs / 1000).toFixed(0); + setStatus(`收到 ${delta} 条回复,等待 ${wait}s...`); + nextCmdTimer = setTimeout(() => { + setStatus("发送中..."); + sendNextCmd(); + }, intervalMs); + } else { + setStatus("全部完成"); + stopAuto(); + return; } } else { - // 全部完成 - stopAuto(); + // 没有新回复,更新超时计数 + autoFailed = newFailed; + autoRemaining = autoTotal - autoDone - autoFailed; + if (autoRemaining < 0) autoRemaining = 0; + // 检查是否全部完成 + if (autoRemaining <= 0 && autoDone + autoFailed >= autoTotal) { + stopAuto(); + return; + } } + + // 显示最新结果 + if (data.latest) renderLatest(data.latest); + if (data.averages) renderAverages(data.averages); + if (data.latest_wave) renderLatestWave(data.latest_wave); + if (data.records) renderRecords(data.records); + } catch (e) { console.error("轮询失败:", e); } } -function startTimeout(recordId) { - timeoutTimers[recordId] = setTimeout(async () => { - // 超时:检查 record 的状态,如果还是 1 → 视为失败 - // 后端串行轮询会自动处理超时标记为 state=3 - // 前端稍后通过 pollProgress 更新计数 - console.log(`记录 ${recordId} 可能已超时`); - }, TIMEOUT_MS); +// ─── UI ──────────────────────────────────────── + +function setStatus(msg) { + document.getElementById("auto-status").textContent = msg; } function updateUI() { diff --git a/edc-web/app/templates/test_op.html b/edc-web/app/templates/test_op.html index a76646c..d66ad42 100644 --- a/edc-web/app/templates/test_op.html +++ b/edc-web/app/templates/test_op.html @@ -26,6 +26,14 @@ 测试次数: + +
@@ -36,6 +44,7 @@ 失败:0 剩余:0
+