refactor: 测试信息页拆为三视图(全部/B2/B4),按data_source自动切换列

- 移除混杂的24列大表,改为三个标签页切换
- 全部视图: 精简核心字段
- B2视图: 峰峰值、频率、距离、速度、故障、完成状态
- B4视图: 剩余次数、当前距离、速度、波动范围、进入/离开高度
- 后端增加data_source查询/导出参数支持
This commit is contained in:
wangfq
2026-06-03 17:02:27 +08:00
parent a69d7ab1d0
commit d00d199558
4 changed files with 168 additions and 77 deletions

View File

@@ -151,10 +151,12 @@ 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 = "", test_mode: str = "") -> tuple[list[dict], int]:
date_to: str = "", test_mode: str = "",
data_source: str = "") -> tuple[list[dict], int]:
"""分页查询测试数据JOIN dnt_info返回 (records, total)
test_mode: ''=全部, '0'=灵敏度, '1'=波动
data_source: ''=全部, 'B2', 'B4'
"""
conn = get_conn()
try:
@@ -173,6 +175,9 @@ def get_test_data(page: int = 1, per_page: int = 20,
if test_mode:
where.append("t.test_mode = %s")
params.append(int(test_mode))
if data_source:
where.append("t.data_source = %s")
params.append(data_source)
where_clause = " AND ".join(where) if where else "1=1"
@@ -201,10 +206,12 @@ 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 = "", test_mode: str = "") -> list[dict]:
date_to: str = "", test_mode: str = "",
data_source: str = "") -> list[dict]:
"""导出全部数据
test_mode: ''=全部, '0'=灵敏度, '1'=波动
data_source: ''=全部, 'B2', 'B4'
"""
conn = get_conn()
try:
@@ -223,6 +230,9 @@ def get_all_test_data_for_export(serial: str = "", date_from: str = "",
if test_mode:
where.append("t.test_mode = %s")
params.append(int(test_mode))
if data_source:
where.append("t.data_source = %s")
params.append(data_source)
where_clause = " AND ".join(where) if where else "1=1"
cur.execute(

View File

@@ -24,8 +24,10 @@ def api_test_data():
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, total = get_test_data(page, per_page, serial, date_from, date_to, test_mode)
records, total = get_test_data(page, per_page, serial, date_from, date_to,
test_mode, data_source)
return jsonify({
"records": records,
"total": total,
@@ -42,8 +44,10 @@ def api_export():
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)
records = get_all_test_data_for_export(serial, date_from, date_to,
test_mode, data_source)
output = io.StringIO()
writer = csv.writer(output)

View File

@@ -1,25 +1,112 @@
// 测试信息页
// 测试信息页 — 三视图 (全部 / B2 / B4)
// ─── 视图定义 ───────────────────────────────────
const VIEWS = {
all: {
label: '全部数据',
data_source: '', // '' = 不过滤
cols: [
{ key: 'id', title: 'ID' },
{ key: 'serial', title: '设备编码' },
{ key: 'dpg430_addr', title: '地址' },
{ key: 'model', title: '型号', render: r => r.sub_type === 1 ? 'PD132' : r.sub_type === 2 ? 'DLD110' : '-' },
{ key: 'str_type', title: '类型' },
{ key: 'data_source', title: '来源' },
{ key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
{ key: 'ppvalue', title: '峰峰值(V)', render: r => r.ppvalue?.toFixed(2) || '-' },
{ key: 'idle_freq', title: '开始频率' },
{ key: 'enter_dist', title: '进入距离' },
{ key: 'exit_dist', title: '离开距离' },
{ key: 'remain_count', title: '剩余次数' },
{ key: 'curr_dist', title: '当前距离' },
{ key: 'create_time', title: '时间' },
],
},
b2: {
label: '灵敏度测试',
data_source: 'B2',
cols: [
{ key: 'id', title: 'ID' },
{ key: 'serial', title: '设备编码' },
{ key: 'dpg430_addr', title: '地址' },
{ key: 'model', title: '型号', render: r => r.sub_type === 1 ? 'PD132' : r.sub_type === 2 ? 'DLD110' : '-' },
{ key: 'str_type', title: '类型' },
{ key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
{ key: 'iffinish', title: '完成', render: r => r.iffinish === '1' ? '是' : '否' },
{ key: 'fault_info', title: '故障信息' },
{ key: 'relay_out', title: '继电器' },
{ key: 'ppvalue', title: '峰峰值(V)', render: r => r.ppvalue?.toFixed(2) || '-' },
{ key: 'idle_freq', title: '开始频率' },
{ key: 'enter_freq', title: '进入频率' },
{ key: 'exit_freq', title: '离开频率' },
{ key: 'enter_dist', title: '进入距离' },
{ key: 'exit_dist', title: '离开距离' },
{ key: 'enter_speed', title: '进入速度', render: r => toSpeed(r.enter_speed) },
{ key: 'exit_speed', title: '离开速度', render: r => toSpeed(r.exit_speed) },
{ key: 'create_time', title: '时间' },
],
},
b4: {
label: '波动测试',
data_source: 'B4',
cols: [
{ key: 'id', title: 'ID' },
{ key: 'serial', title: '设备编码' },
{ key: 'dpg430_addr', title: '地址' },
{ key: 'remain_count', title: '剩余次数' },
{ key: 'work_freq', title: '工作频率(Hz)' },
{ key: 'curr_dist', title: '当前距离(mm)' },
{ key: 'speed', title: '速度(dm/s)' },
{ key: 'near_dist', title: '最近距离(mm)' },
{ key: 'far_dist', title: '最远距离(mm)' },
{ key: 'b4_enter_dist', title: '进入高度(mm)' },
{ key: 'b4_leave_dist', title: '离开高度(mm)' },
{ key: 'relay_out', title: '继电器' },
{ key: 'create_time', title: '时间' },
],
},
};
// ─── 状态 ───────────────────────────────────────
let currentView = 'all';
let currentPage = 1;
let totalPages = 1;
function toSpeed(v) {
if (v === null || v === undefined || v === '') return '-';
return (parseFloat(v) / 10).toFixed(1);
}
let currentPage = 1;
let totalPages = 1;
// ─── 视图切换 ────────────────────────────────────
function switchView(view) {
currentView = view;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + view).classList.add('active');
// 重置分页
currentPage = 1;
searchData(1);
}
// ─── 查询 ────────────────────────────────────────
async function searchData(page = 1) {
currentPage = page;
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 v = VIEWS[currentView];
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);
// 按 data_source 过滤(全部不过滤)
if (v.data_source) {
params.set("data_source", v.data_source);
}
try {
const resp = await fetch(`/api/test-data?${params}`);
@@ -32,42 +119,39 @@ async function searchData(page = 1) {
}
}
// ─── 渲染表头 ────────────────────────────────────
function renderHead() {
const thead = document.querySelector("#test-data-table thead");
const v = VIEWS[currentView];
thead.innerHTML = '<tr>' +
v.cols.map(c => `<th>${c.title}</th>`).join('') +
'</tr>';
}
// ─── 渲染数据行 ──────────────────────────────────
function renderTable(records) {
renderHead();
const tbody = document.querySelector("#test-data-table tbody");
const v = VIEWS[currentView];
const nCols = v.cols.length;
if (!records.length) {
tbody.innerHTML = '<tr><td colspan="24" style="text-align:center;color:#999;">暂无数据</td></tr>';
tbody.innerHTML = `<tr><td colspan="${nCols}" style="text-align:center;color:#999;">暂无数据</td></tr>`;
return;
}
tbody.innerHTML = records.map(r => `
<tr>
<td>${r.id}</td>
<td>${r.serial || '-'}</td>
<td>${r.dpg430_addr}</td>
<td>${r.sub_type === 1 ? 'PD132' : r.sub_type === 2 ? 'DLD110' : '-'}</td>
<td>${r.str_type || '-'}</td>
<td>${r.test_mode === 1 ? '波动测试' : '灵敏度测试'}</td>
<td>${r.data_source || '-'}</td>
<td>${r.iffinish === '1' ? '是' : '否'}</td>
<td>${r.fault_info || '无'}</td>
<td>${r.relay_out || '无'}</td>
<td>${r.ppvalue?.toFixed(2) || '-'}</td>
<td>${r.idle_freq || '-'}</td>
<td>${r.enter_freq || '-'}</td>
<td>${r.exit_freq || '-'}</td>
<td>${r.enter_dist || '-'}</td>
<td>${r.exit_dist || '-'}</td>
<td>${toSpeed(r.enter_speed)}</td>
<td>${toSpeed(r.exit_speed)}</td>
<td>${r.remain_count || '-'}</td>
<td>${r.curr_dist || '-'}</td>
<td>${r.speed || '-'}</td>
<td>${r.near_dist || '-'}</td>
<td>${r.far_dist || '-'}</td>
<td>${r.create_time || '-'}</td>
</tr>
`).join("");
tbody.innerHTML = records.map(r =>
'<tr>' + v.cols.map(c => {
if (c.render) return `<td>${c.render(r)}</td>`;
const val = r[c.key];
return `<td>${val !== null && val !== undefined && val !== '' ? val : '-'}</td>`;
}).join('') + '</tr>'
).join("");
}
// ─── 分页 ────────────────────────────────────────
function renderPagination() {
const div = document.getElementById("pagination");
let html = "";
@@ -83,20 +167,24 @@ function renderPagination() {
div.innerHTML = html;
}
// ─── 导出 ────────────────────────────────────────
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 v = VIEWS[currentView];
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);
if (v.data_source) params.set("data_source", v.data_source);
window.location.href = `/api/test-data/export?${params}`;
}
// 初始加载
// ─── 初始加载 ────────────────────────────────────
renderHead();
searchData(1);

View File

@@ -4,19 +4,17 @@
{% block content %}
<h2>测试信息</h2>
<div class="view-tabs">
<button id="tab-all" class="tab-btn active" onclick="switchView('all')">全部数据</button>
<button id="tab-b2" class="tab-btn" onclick="switchView('b2')">灵敏度测试 (0xB2)</button>
<button id="tab-b4" class="tab-btn" onclick="switchView('b4')">波动测试 (0xB4)</button>
</div>
<div class="search-bar">
<label>
设备编码:
<input type="text" id="search-serial" placeholder="输入设备编码搜索...">
</label>
<label>
测试模式:
<select id="search-test-mode">
<option value="">全部</option>
<option value="0">灵敏度测试</option>
<option value="1">波动测试</option>
</select>
</label>
<label>
日期范围:
<input type="date" id="search-date-from">
@@ -28,34 +26,7 @@
</div>
<table id="test-data-table">
<thead>
<tr>
<th>ID</th>
<th>设备编码</th>
<th>DG430地址</th>
<th>设备型号</th>
<th>类型</th>
<th>测试模式</th>
<th>数据来源</th>
<th>是否完成</th>
<th>故障信息</th>
<th>继电器</th>
<th>峰峰值(V)</th>
<th>开始频率(Hz)</th>
<th>进入频率(Hz)</th>
<th>离开频率(Hz)</th>
<th>进入距离(mm)</th>
<th>离开距离(mm)</th>
<th>进入速度(m/s)</th>
<th>离开速度(m/s)</th>
<th>剩余次数</th>
<th>当前距离(mm)</th>
<th>速度(dm/s)</th>
<th>最近距离(mm)</th>
<th>最远距离(mm)</th>
<th>时间</th>
</tr>
</thead>
<thead></thead>
<tbody></tbody>
</table>
@@ -63,5 +34,23 @@
{% endblock %}
{% block scripts %}
<style>
.view-tabs { margin-bottom: 16px; display: flex; gap: 8px; }
.tab-btn {
padding: 8px 20px;
border: 1px solid #ccc;
background: #f5f5f5;
cursor: pointer;
border-radius: 4px 4px 0 0;
font-size: 14px;
transition: all .2s;
}
.tab-btn.active {
background: #2c3e50;
color: #fff;
border-color: #2c3e50;
}
.tab-btn:hover:not(.active) { background: #e0e0e0; }
</style>
<script src="{{ url_for('static', filename='js/test_data.js') }}"></script>
{% endblock %}