Compare commits

...

4 Commits

Author SHA1 Message Date
wangfq
8148aef332 docs: V2.0.4 周报 (6/1-6/5) — 波动测试+继电器重构+页面增强+Bug修复 2026-06-05 17:53:35 +08:00
wangfq
87752f12e6 feat: 图表视图增加继电器输出状态系列
- 新增 buildRelaySeries() 函数构建继电器阶梯线系列
  (type=line, step=end, 红色三角标记)
- 新增第4 Y轴(继电器输出),刻度 0-3,标签解码为
  无输出/存在信号/脉冲信号/存在+脉冲
- tooltip 中继电器值自动解码为可读文本
- grid right 扩大到 200px 容纳第4 Y轴
2026-06-05 15:03:17 +08:00
wangfq
aadd498851 fix: 修复时间显示时区偏移8小时 + 自动化完成时跳过渲染
问题1(时区):Flask jsonify 将 MySQL DATETIME 输出为
'Fri, 05 Jun 2026 14:37:52 GMT',但实际值是服务器本地时间
(UTC+8)。JS new Date() 把 GMT 当真,getHours() 加 8 小时。

修复:fmtTime() 先 strip ' GMT' 后缀再解析,让 JS 按本地
时间处理。

问题2(跳过渲染):pollProgress 中'全部完成'时 stopAuto()
+ return 跳过了 renderAverages/renderRecords,导致自动化
平均值和本轮明细永远不显示最后一轮数据。

修复:将 4 个 render 调用移到所有 return 之前执行。
2026-06-05 14:44:13 +08:00
wangfq
86c6046fbc fix: 测试操作页三个数据显示区因 relay_code 改动不显示
问题原因:
1. pollProgress 中所有 render 调用共用一个 try-catch,
   一处报错会导致后续所有渲染被跳过(级联失败)
2. 页面加载时没有初始化数据查询——不启动自动化就永远显示
   占位符

修复:
- 每个 render 调用独立 try-catch,互不影响
- 页面加载时调用 loadInitialData(),自动显示最新测试数据
   和波动测试数据
- records 增加 length>0 判断,避免空数组误触发渲染
2026-06-05 14:34:13 +08:00
3 changed files with 169 additions and 9 deletions

View File

@@ -0,0 +1,108 @@
# V2.0.4 周报 — 车检器测试工装项目
**报告周期**2026年6月1日 ~ 6月5日
**项目**vd_test_fixture车检器自动化测试工装
**作者**wangfq
---
## 一、概述
本周完成 V2.0.3 波动测试模式全链路实现、继电器状态存储重构、测试操作/信息页面多项增强,以及若干关键 Bug 修复。共提交 **24 个 commits**(主仓库 19 + edc_server 子模块 4 + edc-web 1
---
## 二、波动测试模式V2.0.3
### 2.1 协议文档
- DG430 串口协议升级至 V2.0.3
- 扩展 0x4B/0x4C 字段:新增 FarTol、NearTol、StepTol、BackForth、NearStay、FarStay6 个波动参数)
- 新增 0xB4 波动测试上报指令定义
- 第 6 章拆分为 6.1 灵敏度测试流程 + 6.2~6.4 波动测试流程
### 2.2 后端 edc_server
| 模块 | 变更 |
|------|------|
| `dg430.py` | 新增 `DG430WaveStatus` dataclass、`parse_b4_wave_status()` 解析器;扩展 `DG430FixtureParams` + `parse_4c_params()` 支持 6 个新字段 |
| `models.py` | DDL 增加 10 个新列test_mode、data_source、remain_count、work_freq、b4_enter_dist 等);新增 `insert_wave_data()` 存库函数ALTER TABLE 自动迁移逻辑 |
| `handlers.py` | parse_loop 增加 0xB4 分支波动数据路由入库0x4C 处理传递新参数字段 |
### 2.3 前端 edc-web
- **工装参数页**:表单增加 6 个波动参数输入框JS 适配新字段
- **测试操作页**:右侧新增「波动测试数据」显示区,实时展示 B4 上报数据
- **测试信息页**:拆为三视图标签页(全部/灵敏度测试 B2/波动测试 B4`data_source` 自动切换列布局
- 波动测试数据支持 Excel 导出
---
## 三、继电器输出状态重构
### 3.1 存储层
- `tb_state_tst` 新增 `relay_code TINYINT` 列,存储原始 hex 值
- `0x00` = 无输出,`0x01` = 存在信号,`0x02` = 脉冲信号,`0x03` = 存在+脉冲
- `relay_out` VARCHAR 列保留,兼容历史数据
- B2/B4 解析后直接以 int 值写入 `relay_code`
### 3.2 前端
- 新增 `decodeRelay()` 函数:整数 → 可读文本
- 测试操作页、测试信息页(表格视图)统一使用解码显示
- **图表视图**:新增第 4 Y 轴继电器输出红色三角阶梯线tooltip 自动解码
---
## 四、测试操作页面增强
| 功能 | 说明 |
|------|------|
| 自动化间隔/超时 | 新增间隔时间(秒)和超时时间(秒)参数;重写为状态机驱动,收到回复后等待间隔再发下一条,超时立即发下一条 |
| 渲染容错 | 每个 render 调用独立 try-catch避免一处报错级联导致全部数据显示失败 |
| 初始数据加载 | 页面打开时自动请求最新测试数据,无需等待启动自动化 |
| 时间格式统一 | 所有区域统一显示 `yyyy-MM-dd HH:mm:ss`,修复 Flask jsonify "GMT" 导致的 UTC+8 时区偏移 |
---
## 五、测试信息页面增强
| 功能 | 说明 |
|------|------|
| 三视图标签页 | 全部 / 灵敏度测试 (B2) / 波动测试 (B4),独立列布局 |
| ECharts 图表 | 表格/图表一键切换B2 显示 8 条线(峰峰值/频率/距离/速度B4 显示 7+1 条线(含继电器),三 Y 轴dataZoom 缩放,保存为 2x PNG |
| 分页条数 | 搜索栏增加「每页20/50/100」下拉框 |
| Admin 删除 | 仅 admin 可见,按设备编码/日期范围/数据来源筛选删除,确认框防误删,`tb_log` 留痕 |
---
## 六、Bug 修复
| 问题 | 根因 | 修复 |
|------|------|------|
| FarStay 字段长度 | 用户纠正NearStay 和 FarStay 均为 2 字节 | 全量回退 1 字节改动,恢复协议+代码 2 字节设计 |
| 数据库缺列 | 旧表缺少 V2.0.3 新增字段 | 服务启动时 ALTER TABLE ADD COLUMN IF NOT EXISTS 自动迁移 |
| 时间显示偏移 8 小时 | Flask jsonify 给本地时间加 "GMT" 后缀JS 误当 UTC 解析 | `fmtTime()` 先 strip "GMT" 再解析 |
| 测试操作三个数据区不显示 | pollProgress 共用一个 try-catch一处报错跳过后续渲染全部完成时 return 跳过最终渲染 | 独立 try-catch渲染代码移到 return 之前 |
| 自动化平均值/明细不显示 | 同上(渲染代码在 return 之后) | 同上 |
---
## 七、Git 提交统计
**主仓库**vd_test_fixture19 commits
**子模块**edc_server4 commits
**合计**23 commits
| 日期 | 主题 |
|------|------|
| 6/1 | 培训手册 V1.0、精简 requirements.txt |
| 6/2 | DG430 V2.0.3 协议文档、后端实现、前端同步、ALTER TABLE 迁移 |
| 6/3 | FarStay 字节修正、波动测试前端适配、三视图重构 |
| 6/4 | 自动化间隔/超时、协议文档补充灵敏度流程 |
| 6/5 | 时间格式化、分页条数、ECharts 图表、图表保存图片、admin 删除、继电器重构、时区修复、渲染容错、图表继电器系列 |
---
## 八、待办事项
- [ ] 重启 edc_server 使数据库迁移和新字段生效
- [ ] edc-web 重启(用户已手动停止)
- [ ] 端到端测试完整波动测试流程(参数设置 → 查询 → 执行 → B4 上报 → 前端展示)
- [ ] 验证图表功能在不同数据量下的表现

View File

@@ -81,7 +81,10 @@ function toSpeed(v) {
function fmtTime(v) {
if (!v) return '-';
const d = new Date(v);
// Flask jsonify 给 MySQL DATETIME 加 "GMT" 后缀但实际值是服务器本地时间UTC+8
// 去掉 "GMT" 让 JS 按本地时间解析,避免时区偏移 8 小时
const cleaned = String(v).replace(/ GMT$/, '');
const d = new Date(cleaned);
if (isNaN(d.getTime())) return String(v).substring(0, 19);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
@@ -238,6 +241,27 @@ const CHART_SERIES = {
],
};
// 继电器系列(添加到任意视图的图表末尾)
function buildRelaySeries(records) {
return {
name: '继电器输出',
type: 'line',
step: 'end',
yAxisIndex: 3,
symbol: 'triangle',
symbolSize: 8,
lineStyle: { type: 'dotted', width: 2, color: '#e74c3c' },
itemStyle: { color: '#e74c3c' },
data: records.map(r => r.relay_code ?? null),
// tooltip 中显示解码后的文本
tooltip: {
valueFormatter: function (value) {
return RELAY_MAP[value] || `未知(${value})`;
}
},
};
}
function toggleChart() {
const container = document.getElementById('chart-container');
const btn = document.getElementById('btn-chart');
@@ -314,6 +338,9 @@ async function loadChart() {
connectNulls: false,
}));
// 添加继电器状态系列
series.push(buildRelaySeries(records));
// 渲染 ECharts
if (chartInstance) chartInstance.dispose();
chartInstance = echarts.init(container);
@@ -341,7 +368,7 @@ async function loadChart() {
},
},
},
grid: { left: 60, right: 140, top: 60, bottom: 80 },
grid: { left: 60, right: 200, top: 60, bottom: 80 },
xAxis: {
type: 'category',
data: times,
@@ -355,6 +382,15 @@ async function loadChart() {
{ type: 'value', name: '距离(mm)', nameTextStyle: { fontSize: 11 } },
{ type: 'value', name: '速度(dm/s)',nameTextStyle: { fontSize: 11 },
offset: 80 },
{ type: 'value', name: '继电器输出', nameTextStyle: { fontSize: 11 },
min: -0.5, max: 3.5, interval: 1,
offset: 160,
axisLabel: {
formatter: function (v) {
return RELAY_MAP[v] || '';
},
fontSize: 10,
}},
],
dataZoom: [
{ type: 'slider', start: 0, end: 100, height: 20, bottom: 30 },

View File

@@ -200,6 +200,12 @@ async function pollProgress() {
const data = await resp.json();
const stats = data.stats;
// ── 先渲染数据(放在所有 return 之前,避免完成时跳过渲染)──
try { if (data.latest) renderLatest(data.latest); } catch (e) { console.error("renderLatest:", e); }
try { if (data.averages) renderAverages(data.averages); } catch (e) { console.error("renderAverages:", e); }
try { if (data.latest_wave) renderLatestWave(data.latest_wave); } catch (e) { console.error("renderLatestWave:", e); }
try { if (data.records && data.records.length) renderRecords(data.records); } catch (e) { console.error("renderRecords:", e); }
// 更新计数
const newDone = stats.done || 0;
const newFailed = stats.failed || 0;
@@ -244,17 +250,24 @@ async function pollProgress() {
}
}
// 显示最新结果
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);
}
}
// ─── 页面加载时获取初始数据 ──────────────────────
async function loadInitialData() {
try {
const resp = await fetch(`/api/automation/${DNT_ID}/progress`);
const data = await resp.json();
if (data.latest) renderLatest(data.latest);
if (data.latest_wave) renderLatestWave(data.latest_wave);
} catch (e) {
// 初始加载静默失败
}
}
loadInitialData();
// ─── UI ────────────────────────────────────────
function setStatus(msg) {
@@ -280,7 +293,10 @@ function toSpeed(v) {
function fmtTime(v) {
if (!v) return '-';
const d = new Date(v);
// Flask jsonify 给 MySQL DATETIME 加 "GMT" 后缀但实际值是服务器本地时间UTC+8
// 去掉 "GMT" 让 JS 按本地时间解析,避免时区偏移 8 小时
const cleaned = String(v).replace(/ GMT$/, '');
const d = new Date(cleaned);
if (isNaN(d.getTime())) return String(v).substring(0, 19);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');