feat: 测试信息增加图表视图(ECharts),表格/图表一键切换

- B2视图:峰峰值/频率/距离/速度 趋势折线图,三Y轴
- B4视图:工作频率/距离/速度 趋势折线图,三Y轴
- dataZoom时间范围缩放,图例可切换系列显隐
- 新增 /api/test-data/chart 接口返回全量数据
This commit is contained in:
wangfq
2026-06-05 11:49:35 +08:00
parent 470c148861
commit 877770aeab
3 changed files with 179 additions and 0 deletions

View File

@@ -37,6 +37,19 @@ def api_test_data():
}) })
@bp.route("/api/test-data/chart")
def api_chart_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)
data_source = request.args.get("data_source", "", type=str)
records = get_all_test_data_for_export(serial, date_from, date_to,
test_mode, data_source)
return jsonify({"records": records, "total": len(records)})
@bp.route("/api/test-data/export") @bp.route("/api/test-data/export")
def api_export(): def api_export():
"""导出测试数据为 CSV""" """导出测试数据为 CSV"""

View File

@@ -199,6 +199,157 @@ function exportCSV() {
window.location.href = `/api/test-data/export?${params}`; window.location.href = `/api/test-data/export?${params}`;
} }
// ─── 图表 ────────────────────────────────────────
let chartMode = false;
let chartInstance = null;
// 图表系列定义
const CHART_SERIES = {
b2: [
{ key: 'ppvalue', name: '峰峰值', unit: 'V', yAxisIndex: 0 },
{ key: 'idle_freq', name: '开始频率', unit: 'Hz', yAxisIndex: 0 },
{ key: 'enter_freq', name: '进入频率', unit: 'Hz', yAxisIndex: 0 },
{ key: 'exit_freq', name: '离开频率', unit: 'Hz', yAxisIndex: 0 },
{ key: 'enter_dist', name: '进入距离', unit: 'mm', yAxisIndex: 1 },
{ key: 'exit_dist', name: '离开距离', unit: 'mm', yAxisIndex: 1 },
{ key: 'enter_speed', name: '进入速度', unit: 'dm/s',yAxisIndex: 2 },
{ key: 'exit_speed', name: '离开速度', unit: 'dm/s',yAxisIndex: 2 },
],
b4: [
{ key: 'work_freq', name: '工作频率', unit: 'Hz', yAxisIndex: 0 },
{ key: 'curr_dist', name: '当前距离', unit: 'mm', yAxisIndex: 1 },
{ key: 'speed', name: '速度', unit: 'dm/s',yAxisIndex: 2 },
{ key: 'near_dist', name: '最近距离', unit: 'mm', yAxisIndex: 1 },
{ key: 'far_dist', name: '最远距离', unit: 'mm', yAxisIndex: 1 },
{ key: 'b4_enter_dist', name: '进入高度', unit: 'mm', yAxisIndex: 1 },
{ key: 'b4_leave_dist', name: '离开高度', unit: 'mm', yAxisIndex: 1 },
],
};
function toggleChart() {
const container = document.getElementById('chart-container');
const btn = document.getElementById('btn-chart');
const table = document.getElementById('test-data-table');
const pagination = document.getElementById('pagination');
chartMode = !chartMode;
if (chartMode) {
container.style.display = 'block';
table.style.display = 'none';
pagination.style.display = 'none';
btn.textContent = '📋 表格';
btn.classList.add('active');
// 只对 B2/B4 视图显示图表
if (currentView === 'all') switchView('b2');
loadChart();
} else {
container.style.display = 'none';
table.style.display = '';
pagination.style.display = '';
btn.textContent = '📈 图表';
btn.classList.remove('active');
if (chartInstance) { chartInstance.dispose(); chartInstance = null; }
}
}
async function loadChart() {
const container = document.getElementById('chart-container');
if (!container || container.style.display === 'none') return;
const serial = document.getElementById('search-serial').value;
const dateFrom = document.getElementById('search-date-from').value;
const dateTo = document.getElementById('search-date-to').value;
const v = VIEWS[currentView];
// 全部视图不适用,用 B2 或 B4
const ds = v.data_source || (currentView === 'all' ? 'B2' : v.data_source);
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 (ds) params.set('data_source', ds);
let resp, data;
try {
resp = await fetch(`/api/test-data/chart?${params}`);
data = await resp.json();
} catch (e) {
console.error('加载图表数据失败:', e);
return;
}
const records = data.records || [];
if (!records.length) {
container.innerHTML = '<p style="text-align:center;color:#999;padding:100px;">暂无数据</p>';
return;
}
// 选系列定义
const seriesDef = CHART_SERIES[ds === 'B4' ? 'b4' : 'b2'] || CHART_SERIES.b2;
// 时间轴
const times = records.map(r => r.create_time);
// 构建 series
const series = seriesDef.map(def => ({
name: `${def.name}(${def.unit})`,
type: 'line',
yAxisIndex: def.yAxisIndex,
symbol: 'circle',
symbolSize: 4,
data: records.map(r => r[def.key] ?? null),
connectNulls: false,
}));
// 渲染 ECharts
if (chartInstance) chartInstance.dispose();
chartInstance = echarts.init(container);
const option = {
title: {
text: ds === 'B4' ? '波动测试 (0xB4) 数据趋势' : '灵敏度测试 (0xB2) 数据趋势',
left: 'center',
textStyle: { fontSize: 14 },
},
tooltip: {
trigger: 'axis',
},
legend: {
type: 'scroll',
bottom: 0,
},
grid: { left: 60, right: 140, top: 60, bottom: 80 },
xAxis: {
type: 'category',
data: times,
axisLabel: {
formatter: v => fmtTime(v).substring(5, 16), // MM-dd HH:mm
rotate: 30,
},
},
yAxis: [
{ type: 'value', name: '频率/电压', nameTextStyle: { fontSize: 11 } },
{ type: 'value', name: '距离(mm)', nameTextStyle: { fontSize: 11 } },
{ type: 'value', name: '速度(dm/s)',nameTextStyle: { fontSize: 11 },
offset: 80 },
],
dataZoom: [
{ type: 'slider', start: 0, end: 100, height: 20, bottom: 30 },
{ type: 'inside' },
],
series: series,
};
chartInstance.setOption(option);
// 窗口 resize 时自适应
window.addEventListener('resize', () => {
if (chartInstance) chartInstance.resize();
}, { once: false });
}
// ─── 初始加载 ──────────────────────────────────── // ─── 初始加载 ────────────────────────────────────
renderHead(); renderHead();

View File

@@ -31,8 +31,11 @@
<option value="100">100</option> <option value="100">100</option>
</select> </select>
</label> </label>
<button id="btn-chart" class="btn-chart" onclick="toggleChart()">📈 图表</button>
</div> </div>
<div id="chart-container" style="display:none; width:100%; height:500px; margin-bottom:16px;"></div>
<table id="test-data-table"> <table id="test-data-table">
<thead></thead> <thead></thead>
<tbody></tbody> <tbody></tbody>
@@ -59,6 +62,18 @@
border-color: #2c3e50; border-color: #2c3e50;
} }
.tab-btn:hover:not(.active) { background: #e0e0e0; } .tab-btn:hover:not(.active) { background: #e0e0e0; }
.btn-chart {
margin-left: 16px;
padding: 6px 14px;
border: 1px solid #27ae60;
background: #fff;
color: #27ae60;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
}
.btn-chart.active { background: #27ae60; color: #fff; }
</style> </style>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script src="{{ url_for('static', filename='js/test_data.js') }}"></script> <script src="{{ url_for('static', filename='js/test_data.js') }}"></script>
{% endblock %} {% endblock %}