diff --git a/docs/plans/2026-05-31-wave-test-frontend.md b/docs/plans/2026-05-31-wave-test-frontend.md new file mode 100644 index 0000000..edd3509 --- /dev/null +++ b/docs/plans/2026-05-31-wave-test-frontend.md @@ -0,0 +1,248 @@ +# 波动测试模式前端适配 实施计划 + +> **For Hermes:** 直接执行各 Task。 + +**Goal:** 将波动测试模式(0xB2 的 test_mode + 0xB4 上报数据)接入测试操作页面、查询导出页面,扩展 `tb_state_tst` 表字段。 + +**Architecture:** 单表方案 — `tb_state_tst` 增加 `test_mode`、`data_source` 和 B4 专属字段,B2 和 B4 记录共存同表,通过 `data_source` 区分。后端 edc_server 存储、edc-web 查询/导出均扩展。 + +**Tech Stack:** Python/aiomysql (edc_server) + Flask/pymysql (edc-web) + vanilla JS 前端 + +--- + +### 涉及文件 + +| 层 | 文件 | 变更 | +|---|---|---| +| 数据库 | `edc_server/src/models.py` | DDL + ALTER 迁移 + `insert_test_result` 扩展 + 新增 `insert_wave_data` | +| 解析 | `edc_server/src/handlers.py` | 0xB2 传 test_mode、0xB4 调 insert_wave_data | +| 查询 | `edc-web/app/models.py` | 新增查询函数、导出扩展 | +| API | `edc-web/app/routes/test_op.py` | progress 端点返回波动数据 | +| API | `edc-web/app/routes/test_data.py` | 查询/导出增加新字段 | +| 前端 | `edc-web/app/templates/test_op.html` | 增加波动数据显示区 | +| 前端 | `edc-web/app/static/js/test_op.js` | renderLatest/renderRecords 扩展 + B4 轮询 | +| 前端 | `edc-web/app/templates/test_data.html` | 表头增加列 + test_mode 筛选 | +| 前端 | `edc-web/app/static/js/test_data.js` | renderTable 扩展 | + +--- + +### Task 1: 扩展 tb_state_tst DDL + 迁移 (edc_server) + +**文件:** `edc_server/src/models.py` + +**Step 1:** 修改 CREATE TABLE 增加 9 列 + +在现有 `tb_state_tst` DDL 中增加: + +```sql +`test_mode` TINYINT DEFAULT 0 COMMENT '0 灵敏度测试, 1 波动测试', +`data_source` CHAR(2) DEFAULT 'B2' COMMENT '数据来源 B2/B4', +`remain_count` INT DEFAULT 0 COMMENT '剩余波动次数 (B4)', +`work_freq` FLOAT DEFAULT 0 COMMENT '工作频率 Hz (B4)', +`curr_dist` INT DEFAULT 0 COMMENT '当前距离 mm (B4)', +`speed` INT DEFAULT 0 COMMENT '当前速度 dm/s (B4)', +`near_dist` INT DEFAULT 0 COMMENT '波动最近距离 mm (B4)', +`far_dist` INT DEFAULT 0 COMMENT '波动最远距离 mm (B4)', +`b4_enter_dist` INT DEFAULT 0 COMMENT 'B4 进入高度 mm', +`b4_leave_dist` INT DEFAULT 0 COMMENT 'B4 离开高度 mm' +``` + +**Step 2:** 在 `_create_tables` 末尾增加 ALTER TABLE 迁移逻辑(参照已有的 `tb_fixture_param` 迁移模式) + +```python +# V2.0.4 迁移:tb_state_tst 增加波动测试字段 +for col, col_def in [ + ("test_mode", "TINYINT DEFAULT 0 COMMENT '0 灵敏度, 1 波动测试'"), + ("data_source", "CHAR(2) DEFAULT 'B2' COMMENT 'B2/B4'"), + ("remain_count", "INT DEFAULT 0 COMMENT '剩余波动次数'"), + ("work_freq", "FLOAT DEFAULT 0 COMMENT '工作频率 Hz'"), + ("curr_dist", "INT DEFAULT 0 COMMENT '当前距离 mm'"), + ("speed", "INT DEFAULT 0 COMMENT '当前速度 dm/s'"), + ("near_dist", "INT DEFAULT 0 COMMENT '波动最近距离 mm'"), + ("far_dist", "INT DEFAULT 0 COMMENT '波动最远距离 mm'"), + ("b4_enter_dist", "INT DEFAULT 0 COMMENT 'B4 进入高度 mm'"), + ("b4_leave_dist", "INT DEFAULT 0 COMMENT 'B4 离开高度 mm'"), +]: + try: + await cur.execute(f"ALTER TABLE `tb_state_tst` ADD COLUMN `{col}` {col_def}") + except Exception: + pass +``` + +**Step 3:** 修改 `insert_test_result` 函数签名,增加 `test_mode`、`data_source` 参数 + +```python +async def insert_test_result(dnt_id, dpg430_addr, pcnum, serialnum, + sub_type, str_type, iffinish, fault_info, + relay_out, ppvalue, idle_freq, enter_freq, + exit_freq, enter_dist, exit_dist, + enter_speed, exit_speed, + test_mode=0, data_source='B2'): +``` + +INSERT 语句增加 `test_mode`, `data_source` 列。 + +**Step 4:** 新增 `insert_wave_data` 函数 + +```python +async def insert_wave_data(dnt_id, dpg430_addr, remain_count, relay_out, + work_freq, curr_dist, speed, near_dist, far_dist, + enter_dist, leave_dist): + """插入 0xB4 波动测试上报数据到 tb_state_tst""" +``` + +--- + +### Task 2: handlers.py 适配 + +**文件:** `edc_server/src/handlers.py` + +**Step 1:** 0xB2 处理 — `insert_test_result` 调用增加 `test_mode=status.test_mode` + +```python +await insert_test_result( + ... + test_mode=status.test_mode, +) +``` + +**Step 2:** 0xB4 处理 — 调用 `insert_wave_data` 存库 + +```python +from src.models import insert_wave_data # 增加导入 + +# 在 0xB4 分支末尾 +await insert_wave_data( + dnt_id=dnt_id, + dpg430_addr=wave.addr, + remain_count=wave.remain_count, + relay_out=wave.relay_out, + work_freq=wave.work_freq, + curr_dist=wave.curr_dist, + speed=wave.speed, + near_dist=wave.near_dist, + far_dist=wave.far_dist, + enter_dist=wave.enter_dist, + leave_dist=wave.leave_dist, +) +``` + +--- + +### Task 3: edc-web models.py 查询扩展 + +**文件:** `edc-web/app/models.py` + +**Step 1:** 新增 `get_latest_wave_data(dnt_id)` — 获取最新一条 B4 数据 + +**Step 2:** 新增 `get_wave_records(dnt_id, since)` — 获取本轮 B4 明细 + +查询条件: `data_source='B4' AND create_time >= since` + +**Step 3:** 修改 `get_automation_averages` — 不涉及波动字段,保持不变 + +--- + +### Task 4: edc-web test_op.py 路由扩展 + +**文件:** `edc-web/app/routes/test_op.py` + +在 `api_automation_progress` 中增加返回 B4 数据: + +```python +latest_wave = get_latest_wave_data(dnt_id) +wave_records = get_wave_records(dnt_id, since) if since else [] +return jsonify({ + ... + "latest_wave": latest_wave, + "wave_records": wave_records, +}) +``` + +导入新增的 `get_latest_wave_data`, `get_wave_records`。 + +--- + +### Task 5: edc-web test_data.py 路由扩展 + +**文件:** `edc-web/app/routes/test_data.py` + +**Step 1:** `api_test_data` — 增加 `test_mode` 筛选参数 + +```python +test_mode = request.args.get("test_mode", "", type=str) # '' = 全部, '0' = 灵敏度, '1' = 波动 +``` + +传给 `get_test_data`,在 SQL WHERE 中增加 `AND t.test_mode = %s` 条件。 + +**Step 2:** 修改 `get_test_data` 函数签名和 SQL(在 models.py 中) + +**Step 3:** CSV 导出同样支持 `test_mode` 筛选 + +--- + +### Task 6: 前端 test_op.html + test_op.js + +**文件:** `edc-web/app/templates/test_op.html` + `static/js/test_op.js` + +**Step 1:** HTML — 在 `latest-result` 区域增加波动数据显示 div + +```html +

波动测试数据

+
+

暂无波动数据...

+
+``` + +**Step 2:** JS — `renderLatest` 增加 test_mode 显示 + +在现有显示中增加: +```js +

测试模式:${data.test_mode === 1 ? '波动测试' : '灵敏度测试'}

+``` + +**Step 3:** JS — 新增 `renderLatestWave(data)` 函数 + +显示 B4 上报数据:剩余次数、当前距离、速度、最近/最远距离、进入/离开高度。 + +**Step 4:** JS — `pollProgress` 中调用 `renderLatestWave(data.latest_wave)` + +**Step 5:** JS — 明细表 `renderRecords` 增加测试模式列 + +--- + +### Task 7: 前端 test_data.html + test_data.js + +**文件:** `edc-web/app/templates/test_data.html` + `static/js/test_data.js` + +**Step 1:** HTML — 搜索栏增加测试模式下拉筛选 + +```html + +``` + +**Step 2:** HTML — 表头增加列:测试模式、数据来源、剩余次数、工作频率、当前距离、速度、最近距离、最远距离 + +**Step 3:** JS — `searchData` 传 `test_mode` 参数 + +**Step 4:** JS — `renderTable` 增加新列渲染 + +**Step 5:** JS — `exportCSV` 传 `test_mode` 参数 + +--- + +### Task 8: 提交推送 + +```bash +cd /home/wfq/projects/vd_test_fixture +git add -A +git commit -m "feat: 波动测试模式前端适配 — tb_state_tst扩展+0xB4存库+页面更新" +git push origin main +``` diff --git a/edc-web/app/models.py b/edc-web/app/models.py index 863c791..7ee80db 100644 --- a/edc-web/app/models.py +++ b/edc-web/app/models.py @@ -151,8 +151,11 @@ def get_latest_test_state(dnt_id: int) -> dict | None: def get_test_data(page: int = 1, per_page: int = 20, serial: str = "", date_from: str = "", - date_to: str = "") -> tuple[list[dict], int]: - """分页查询测试数据(JOIN dnt_info),返回 (records, total)""" + date_to: str = "", test_mode: str = "") -> tuple[list[dict], int]: + """分页查询测试数据(JOIN dnt_info),返回 (records, total) + + test_mode: ''=全部, '0'=灵敏度, '1'=波动 + """ conn = get_conn() try: with conn.cursor() as cur: @@ -167,6 +170,9 @@ def get_test_data(page: int = 1, per_page: int = 20, if date_to: where.append("t.create_time <= %s") params.append(date_to + " 23:59:59") + if test_mode: + where.append("t.test_mode = %s") + params.append(int(test_mode)) where_clause = " AND ".join(where) if where else "1=1" @@ -195,8 +201,11 @@ def get_test_data(page: int = 1, per_page: int = 20, def get_all_test_data_for_export(serial: str = "", date_from: str = "", - date_to: str = "") -> list[dict]: - """导出全部数据""" + date_to: str = "", test_mode: str = "") -> list[dict]: + """导出全部数据 + + test_mode: ''=全部, '0'=灵敏度, '1'=波动 + """ conn = get_conn() try: with conn.cursor() as cur: @@ -211,6 +220,9 @@ def get_all_test_data_for_export(serial: str = "", date_from: str = "", if date_to: where.append("t.create_time <= %s") params.append(date_to + " 23:59:59") + if test_mode: + where.append("t.test_mode = %s") + params.append(int(test_mode)) where_clause = " AND ".join(where) if where else "1=1" cur.execute( @@ -285,6 +297,37 @@ def get_automation_records(dnt_id: int, since: str) -> list[dict]: conn.close() +def get_latest_wave_data(dnt_id: int) -> dict | None: + """获取设备最新一条 B4 波动测试数据""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute( + "SELECT * FROM tb_state_tst WHERE dnt_id=%s AND data_source='B4' " + "ORDER BY id DESC LIMIT 1", + (dnt_id,), + ) + return cur.fetchone() + finally: + conn.close() + + +def get_wave_records(dnt_id: int, since: str) -> list[dict]: + """获取本轮 B4 波动测试明细""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute( + "SELECT * FROM tb_state_tst " + "WHERE dnt_id=%s AND data_source='B4' AND create_time >= %s " + "ORDER BY id ASC", + (dnt_id, since), + ) + return cur.fetchall() + finally: + conn.close() + + # ─── 用户管理 ────────────────────────────────────────────────────── def get_user_by_username(username: str) -> dict | None: diff --git a/edc-web/app/routes/test_data.py b/edc-web/app/routes/test_data.py index 2540291..cdf1057 100644 --- a/edc-web/app/routes/test_data.py +++ b/edc-web/app/routes/test_data.py @@ -23,8 +23,9 @@ def api_test_data(): serial = request.args.get("serial", "", type=str) date_from = request.args.get("date_from", "", type=str) date_to = request.args.get("date_to", "", type=str) + test_mode = request.args.get("test_mode", "", type=str) - records, total = get_test_data(page, per_page, serial, date_from, date_to) + records, total = get_test_data(page, per_page, serial, date_from, date_to, test_mode) return jsonify({ "records": records, "total": total, @@ -40,8 +41,9 @@ def api_export(): serial = request.args.get("serial", "", type=str) date_from = request.args.get("date_from", "", type=str) date_to = request.args.get("date_to", "", type=str) + test_mode = request.args.get("test_mode", "", type=str) - records = get_all_test_data_for_export(serial, date_from, date_to) + records = get_all_test_data_for_export(serial, date_from, date_to, test_mode) output = io.StringIO() writer = csv.writer(output) diff --git a/edc-web/app/routes/test_op.py b/edc-web/app/routes/test_op.py index 797691b..bf3b41d 100644 --- a/edc-web/app/routes/test_op.py +++ b/edc-web/app/routes/test_op.py @@ -9,6 +9,8 @@ from app.models import ( get_latest_test_state, get_automation_averages, get_automation_records, + get_latest_wave_data, + get_wave_records, clear_serialnet_records, insert_log, ) @@ -118,9 +120,13 @@ def api_automation_progress(dnt_id): latest = get_latest_test_state(dnt_id) averages = get_automation_averages(dnt_id, since if since else None) records = get_automation_records(dnt_id, since) if since else [] + latest_wave = get_latest_wave_data(dnt_id) + wave_records = get_wave_records(dnt_id, since) if since else [] return jsonify({ "stats": stats, "latest": latest, "averages": averages, "records": records, + "latest_wave": latest_wave, + "wave_records": wave_records, }) diff --git a/edc-web/app/static/js/test_data.js b/edc-web/app/static/js/test_data.js index 20519bd..7ed758c 100644 --- a/edc-web/app/static/js/test_data.js +++ b/edc-web/app/static/js/test_data.js @@ -13,11 +13,13 @@ async function searchData(page = 1) { const serial = document.getElementById("search-serial").value; const dateFrom = document.getElementById("search-date-from").value; const dateTo = document.getElementById("search-date-to").value; + const testMode = document.getElementById("search-test-mode").value; const params = new URLSearchParams({ page, per_page: 20 }); if (serial) params.set("serial", serial); if (dateFrom) params.set("date_from", dateFrom); if (dateTo) params.set("date_to", dateTo); + if (testMode) params.set("test_mode", testMode); try { const resp = await fetch(`/api/test-data?${params}`); @@ -33,7 +35,7 @@ async function searchData(page = 1) { function renderTable(records) { const tbody = document.querySelector("#test-data-table tbody"); if (!records.length) { - tbody.innerHTML = '暂无数据'; + tbody.innerHTML = '暂无数据'; return; } tbody.innerHTML = records.map(r => ` @@ -43,6 +45,8 @@ function renderTable(records) { ${r.dpg430_addr} ${r.sub_type === 1 ? 'PD132' : r.sub_type === 2 ? 'DLD110' : '-'} ${r.str_type || '-'} + ${r.test_mode === 1 ? '波动测试' : '灵敏度测试'} + ${r.data_source || '-'} ${r.iffinish === '1' ? '是' : '否'} ${r.fault_info || '无'} ${r.relay_out || '无'} @@ -54,6 +58,11 @@ function renderTable(records) { ${r.exit_dist || '-'} ${toSpeed(r.enter_speed)} ${toSpeed(r.exit_speed)} + ${r.remain_count || '-'} + ${r.curr_dist || '-'} + ${r.speed || '-'} + ${r.near_dist || '-'} + ${r.far_dist || '-'} ${r.create_time || '-'} `).join(""); @@ -78,11 +87,13 @@ function exportCSV() { const serial = document.getElementById("search-serial").value; const dateFrom = document.getElementById("search-date-from").value; const dateTo = document.getElementById("search-date-to").value; + const testMode = document.getElementById("search-test-mode").value; const params = new URLSearchParams(); if (serial) params.set("serial", serial); if (dateFrom) params.set("date_from", dateFrom); if (dateTo) params.set("date_to", dateTo); + if (testMode) params.set("test_mode", testMode); window.location.href = `/api/test-data/export?${params}`; } diff --git a/edc-web/app/static/js/test_op.js b/edc-web/app/static/js/test_op.js index 014fbee..474ca88 100644 --- a/edc-web/app/static/js/test_op.js +++ b/edc-web/app/static/js/test_op.js @@ -67,6 +67,7 @@ async function startAuto() { // 清空显示 resetAverages(); document.getElementById("latest-result").innerHTML = '

等待测试...

'; + document.getElementById("latest-wave").innerHTML = '

暂无波动数据...

'; document.getElementById("progress-bar").style.width = "0%"; document.getElementById("progress-text").textContent = "0/" + count + " (0 失败)"; document.getElementById("stat-done").textContent = "0"; @@ -145,6 +146,11 @@ async function pollProgress() { renderAverages(data.averages); } + // 显示波动测试数据 + if (data.latest_wave) { + renderLatestWave(data.latest_wave); + } + // 显示本轮测试明细 if (data.records) { renderRecords(data.records); @@ -209,6 +215,7 @@ function renderLatest(data) { const div = document.getElementById("latest-result"); div.innerHTML = `

设备型号:${data.str_type || '-'}

+

测试模式:${data.test_mode === 1 ? '波动测试' : '灵敏度测试'}

峰峰值:${data.ppvalue?.toFixed(2) || '-'} V

开始工作频率:${data.idle_freq || '-'} Hz

进入工作频率:${data.enter_freq || '-'} Hz

@@ -242,6 +249,28 @@ function resetAverages() { }); } +// ─── 显示波动测试数据 ────────────────────────── + +function renderLatestWave(data) { + const div = document.getElementById("latest-wave"); + if (!data || !data.work_freq) { + div.innerHTML = '

暂无波动数据...

'; + return; + } + div.innerHTML = ` +

剩余次数:${data.remain_count || 0}

+

工作频率:${data.work_freq || '-'} Hz

+

当前距离:${data.curr_dist || '-'} mm

+

当前速度:${toSpeed(data.speed)} m/s

+

最近距离:${data.near_dist || '-'} mm

+

最远距离:${data.far_dist || '-'} mm

+

进入高度 (B4):${data.b4_enter_dist || '-'} mm

+

离开高度 (B4):${data.b4_leave_dist || '-'} mm

+

继电器:${data.relay_out || '无'}

+

时间:${data.create_time || '-'}

+ `; +} + // ─── 显示本轮测试明细 ────────────────────────── function renderRecords(records) { @@ -260,6 +289,7 @@ function renderRecords(records) { ${r.sn_state === 2 ? 'OK' : r.sn_state === 3 ? '超时' : '?'} + ${r.test_mode === 1 ? '波动' : '灵敏度'} ${r.ppvalue?.toFixed(2) || '-'} ${r.idle_freq || '-'} ${r.enter_dist || '-'} diff --git a/edc-web/app/templates/test_data.html b/edc-web/app/templates/test_data.html index f408745..11433f7 100644 --- a/edc-web/app/templates/test_data.html +++ b/edc-web/app/templates/test_data.html @@ -9,6 +9,14 @@ 设备编码: +