Files
vd_test_fixture/edc-web/app/static/js/test_op.js
wangfq a69d7ab1d0 feat: 波动测试模式前端适配 — tb_state_tst扩展+0xB4存库+页面更新
- edc_server/models.py: tb_state_tst DDL增加test_mode/data_source + B4字段
  + ALTER TABLE自动迁移 + insert_test_result扩展 + insert_wave_data
- edc_server/handlers.py: 0xB2处理传test_mode、0xB4处理调用insert_wave_data存库
- edc-web/models.py: 新增get_latest_wave_data/get_wave_records + test_mode筛选
- edc-web/routes: test_op返回wave数据、test_data支持test_mode筛选
- 前端: test_op页面增加波动数据显示区+测试模式列
  test_data页面增加test_mode下拉筛选+B4字段列+CSV导出适配
2026-06-03 14:14:52 +08:00

302 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 测试操作页
let autoRunning = false;
let autoTotal = 0;
let autoDone = 0;
let autoFailed = 0;
let autoRemaining = 0;
let autoStartTime = "";
let localSinceStr = "";
let pollInterval = null;
let timeoutTimers = {}; // record_id → timer
const TIMEOUT_MS = 10000;
// ─── 手动指令 ─────────────────────────────────
async function sendCmd(cmd) {
try {
const resp = await fetch("/api/command", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dnt_id: DNT_ID, cmd }),
});
const data = await resp.json();
if (data.ok) {
console.log(`指令 ${cmd} 已下发: ${data.send_pkg}`);
}
} catch (e) {
alert("发送失败: " + e.message);
}
}
// ─── 自动化 ────────────────────────────────────
async function toggleAuto() {
if (!autoRunning) {
await startAuto();
} else {
stopAuto();
}
}
async function startAuto() {
const count = parseInt(document.getElementById("test-count").value) || 10;
if (count < 1) return;
// 重置
autoRunning = true;
autoTotal = count;
autoDone = 0;
autoFailed = 0;
autoRemaining = count;
autoStartTime = new Date().toISOString(); // 记录开始时间 (UTC)
// MySQL 存的是本地时间,需要转本地格式传给后端过滤
const now = new Date();
localSinceStr = now.getFullYear() + "-" +
String(now.getMonth() + 1).padStart(2, "0") + "-" +
String(now.getDate()).padStart(2, "0") + " " +
String(now.getHours()).padStart(2, "0") + ":" +
String(now.getMinutes()).padStart(2, "0") + ":" +
String(now.getSeconds()).padStart(2, "0");
// 显示开始时间
document.getElementById("auto-time").style.display = "block";
document.getElementById("time-start").textContent = new Date().toLocaleString();
document.getElementById("time-end").textContent = "-";
// 清空显示
resetAverages();
document.getElementById("latest-result").innerHTML = '<p class="placeholder">等待测试...</p>';
document.getElementById("latest-wave").innerHTML = '<p class="placeholder">暂无波动数据...</p>';
document.getElementById("progress-bar").style.width = "0%";
document.getElementById("progress-text").textContent = "0/" + count + " (0 失败)";
document.getElementById("stat-done").textContent = "0";
document.getElementById("stat-failed").textContent = "0";
document.getElementById("stat-remaining").textContent = count;
// 清空测试明细
document.querySelector("#records-table tbody").innerHTML = "";
document.getElementById("records-empty").style.display = "block";
const btn = document.getElementById("btn-auto");
btn.textContent = "结束";
btn.className = "btn-stop";
// 插入第一条 0xB0
try {
const resp = await fetch("/api/automation/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dnt_id: DNT_ID, count }),
});
const data = await resp.json();
if (data.ok) {
// 启动超时计时器
startTimeout(data.first_record_id);
}
} catch (e) {
console.error("启动失败:", e);
stopAuto();
}
// 启动轮询
pollInterval = setInterval(pollProgress, 1000);
}
function stopAuto() {
autoRunning = false;
clearInterval(pollInterval);
pollInterval = null;
// 记录结束时间
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";
}
async function pollProgress() {
if (!autoRunning) return;
try {
const resp = await fetch(`/api/automation/${DNT_ID}/progress?since=${encodeURIComponent(localSinceStr)}`);
const data = await resp.json();
const stats = data.stats;
// 更新计数
autoDone = stats.done || 0;
autoFailed = stats.failed || 0;
autoRemaining = autoTotal - autoDone - autoFailed;
if (autoRemaining < 0) autoRemaining = 0;
updateUI();
// 显示最新结果
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);
}
// 自动插入下一条 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);
}
}
} else {
// 全部完成
stopAuto();
}
} catch (e) {
console.error("轮询失败:", e);
}
}
function startTimeout(recordId) {
timeoutTimers[recordId] = setTimeout(async () => {
// 超时:检查 record 的状态,如果还是 1 → 视为失败
// 后端串行轮询会自动处理超时标记为 state=3
// 前端稍后通过 pollProgress 更新计数
console.log(`记录 ${recordId} 可能已超时`);
}, TIMEOUT_MS);
}
function updateUI() {
document.getElementById("stat-done").textContent = autoDone;
document.getElementById("stat-failed").textContent = autoFailed;
document.getElementById("stat-remaining").textContent = autoRemaining;
const total = autoTotal || 1;
const pct = Math.round((autoDone / total) * 100);
document.getElementById("progress-bar").style.width = pct + "%";
document.getElementById("progress-text").textContent =
`${autoDone}/${autoTotal} (${autoFailed} 失败)`;
}
function toSpeed(v) {
if (v === null || v === undefined || v === '') return '-';
return (parseFloat(v) / 10).toFixed(1);
}
// ─── 显示最新结果 ──────────────────────────────
function renderLatest(data) {
const div = document.getElementById("latest-result");
div.innerHTML = `
<p>设备型号:<strong>${data.str_type || '-'}</strong></p>
<p>测试模式:<strong>${data.test_mode === 1 ? '波动测试' : '灵敏度测试'}</strong></p>
<p>峰峰值:${data.ppvalue?.toFixed(2) || '-'} V</p>
<p>开始工作频率:${data.idle_freq || '-'} Hz</p>
<p>进入工作频率:${data.enter_freq || '-'} Hz</p>
<p>离开工作频率:${data.exit_freq || '-'} Hz</p>
<p>进入距离:${data.enter_dist || '-'} mm</p>
<p>离开距离:${data.exit_dist || '-'} mm</p>
<p>进入速度:${toSpeed(data.enter_speed)} m/s</p>
<p>离开速度:${toSpeed(data.exit_speed)} m/s</p>
<p>是否完成:${data.iffinish === '1' ? '是' : '否'}</p>
<p>故障信息:${data.fault_info || '无'}</p>
<p>时间:${data.create_time || '-'}</p>
`;
}
// ─── 显示平均值 ────────────────────────────────
function renderAverages(data) {
document.getElementById("avg-ppvalue").textContent = data.avg_ppvalue || "-";
document.getElementById("avg-idle-freq").textContent = data.avg_idle_freq || "-";
document.getElementById("avg-enter-freq").textContent = data.avg_enter_freq || "-";
document.getElementById("avg-enter-dist").textContent = data.avg_enter_dist || "-";
document.getElementById("avg-exit-dist").textContent = data.avg_exit_dist || "-";
document.getElementById("avg-enter-speed").textContent = data.avg_enter_speed || "-";
document.getElementById("avg-exit-speed").textContent = data.avg_exit_speed || "-";
}
function resetAverages() {
["ppvalue", "idle-freq", "enter-freq", "enter-dist", "exit-dist",
"enter-speed", "exit-speed"].forEach(id => {
document.getElementById("avg-" + id).textContent = "-";
});
}
// ─── 显示波动测试数据 ──────────────────────────
function renderLatestWave(data) {
const div = document.getElementById("latest-wave");
if (!data || !data.work_freq) {
div.innerHTML = '<p class="placeholder">暂无波动数据...</p>';
return;
}
div.innerHTML = `
<p>剩余次数:<strong>${data.remain_count || 0}</strong></p>
<p>工作频率:${data.work_freq || '-'} Hz</p>
<p>当前距离:${data.curr_dist || '-'} mm</p>
<p>当前速度:${toSpeed(data.speed)} m/s</p>
<p>最近距离:${data.near_dist || '-'} mm</p>
<p>最远距离:${data.far_dist || '-'} mm</p>
<p>进入高度 (B4)${data.b4_enter_dist || '-'} mm</p>
<p>离开高度 (B4)${data.b4_leave_dist || '-'} mm</p>
<p>继电器:${data.relay_out || '无'}</p>
<p>时间:${data.create_time || '-'}</p>
`;
}
// ─── 显示本轮测试明细 ──────────────────────────
function renderRecords(records) {
if (!records || !records.length) {
document.getElementById("records-empty").style.display = "block";
document.getElementById("records-table").style.display = "none";
return;
}
document.getElementById("records-empty").style.display = "none";
document.getElementById("records-table").style.display = "";
const tbody = document.querySelector("#records-table tbody");
tbody.innerHTML = records.map((r, i) => `
<tr>
<td>${i + 1}</td>
<td style="color:${r.sn_state === 2 ? '#27ae60' : r.sn_state === 3 ? '#e74c3c' : '#888'}">
${r.sn_state === 2 ? 'OK' : r.sn_state === 3 ? '超时' : '?'}
</td>
<td>${r.test_mode === 1 ? '波动' : '灵敏度'}</td>
<td>${r.ppvalue?.toFixed(2) || '-'}</td>
<td>${r.idle_freq || '-'}</td>
<td>${r.enter_dist || '-'}</td>
<td>${r.exit_dist || '-'}</td>
<td>${toSpeed(r.enter_speed)}</td>
<td>${r.create_time || '-'}</td>
</tr>
`).join("");
}