feat: 测试信息增加图表视图(ECharts),表格/图表一键切换
- B2视图:峰峰值/频率/距离/速度 趋势折线图,三Y轴 - B4视图:工作频率/距离/速度 趋势折线图,三Y轴 - dataZoom时间范围缩放,图例可切换系列显隐 - 新增 /api/test-data/chart 接口返回全量数据
This commit is contained in:
@@ -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"""
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user