测试信息页面优化: 限制6000条、字段调整、车检器序列号搜索

- 查询/导出/图表统一 LIMIT 6000 条
- 列顺序: 时间→第一列, 测试模式→最后一列, 隐藏ID
- 设备编码只显示后6位, 默认每页100条
- 新增车检器序列号搜索 (detector_serial LIKE)
- 四个文件同步修改: models.py, test_data.py, test_data.js, test_data.html
This commit is contained in:
wangfq
2026-06-18 09:44:18 +08:00
parent f0ec79ca2f
commit 8a6b5c6d07
4 changed files with 57 additions and 31 deletions

View File

@@ -149,11 +149,12 @@ def get_latest_test_state(dnt_id: int) -> dict | None:
conn.close() conn.close()
def get_test_data(page: int = 1, per_page: int = 20, def get_test_data(page: int = 1, per_page: int = 100,
serial: str = "", date_from: str = "", serial: str = "", date_from: str = "",
date_to: str = "", test_mode: str = "", date_to: str = "", test_mode: str = "",
data_source: str = "") -> tuple[list[dict], int]: data_source: str = "",
"""分页查询测试数据JOIN dnt_info返回 (records, total) detector_serial: str = "") -> tuple[list[dict], int]:
"""分页查询测试数据JOIN dnt_info最多返回最近 6000 条,返回 (records, total)
test_mode: ''=全部, '0'=灵敏度, '1'=波动 test_mode: ''=全部, '0'=灵敏度, '1'=波动
data_source: ''=全部, 'B2', 'B4' data_source: ''=全部, 'B2', 'B4'
@@ -166,6 +167,9 @@ def get_test_data(page: int = 1, per_page: int = 20,
if serial: if serial:
where.append("d.serial LIKE %s") where.append("d.serial LIKE %s")
params.append(f"%{serial}%") params.append(f"%{serial}%")
if detector_serial:
where.append("t.detector_serial LIKE %s")
params.append(f"%{detector_serial}%")
if date_from: if date_from:
where.append("t.create_time >= %s") where.append("t.create_time >= %s")
params.append(date_from if len(date_from) > 10 else date_from) params.append(date_from if len(date_from) > 10 else date_from)
@@ -181,17 +185,21 @@ def get_test_data(page: int = 1, per_page: int = 20,
where_clause = " AND ".join(where) if where else "1=1" where_clause = " AND ".join(where) if where else "1=1"
# count # count — 最多 6000 条
cur.execute( cur.execute(
f"SELECT COUNT(*) as total FROM tb_state_tst t " f"SELECT COUNT(*) as total FROM ("
f"JOIN dnt_info d ON t.dnt_id = d.id WHERE {where_clause}", f"SELECT 1 FROM tb_state_tst t "
f"JOIN dnt_info d ON t.dnt_id = d.id "
f"WHERE {where_clause} ORDER BY t.id DESC LIMIT 6000"
f") sub",
params, params,
) )
total = cur.fetchone()["total"] total = cur.fetchone()["total"]
# data # data — 子查询限 6000 后再分页
offset = (page - 1) * per_page offset = (page - 1) * per_page
cur.execute( cur.execute(
f"SELECT * FROM ("
f"SELECT t.*, d.serial, " f"SELECT t.*, d.serial, "
f"c.coil_num, c.name as coil_name, " f"c.coil_num, c.name as coil_name, "
f"sc.simulate_num, sc.name as car_name " f"sc.simulate_num, sc.name as car_name "
@@ -200,7 +208,8 @@ def get_test_data(page: int = 1, per_page: int = 20,
f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id " f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id "
f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id " f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id "
f"WHERE {where_clause} " f"WHERE {where_clause} "
f"ORDER BY t.id DESC LIMIT %s OFFSET %s", f"ORDER BY t.id DESC LIMIT 6000"
f") sub ORDER BY id DESC LIMIT %s OFFSET %s",
params + [per_page, offset], params + [per_page, offset],
) )
records = cur.fetchall() records = cur.fetchall()
@@ -212,8 +221,9 @@ def get_test_data(page: int = 1, per_page: int = 20,
def get_all_test_data_for_export(serial: str = "", date_from: str = "", def get_all_test_data_for_export(serial: str = "", date_from: str = "",
date_to: str = "", test_mode: str = "", date_to: str = "", test_mode: str = "",
data_source: str = "") -> list[dict]: data_source: str = "",
"""导出全部数据 detector_serial: str = "") -> list[dict]:
"""导出全部数据(最多最近 6000 条)
test_mode: ''=全部, '0'=灵敏度, '1'=波动 test_mode: ''=全部, '0'=灵敏度, '1'=波动
data_source: ''=全部, 'B2', 'B4' data_source: ''=全部, 'B2', 'B4'
@@ -226,6 +236,9 @@ def get_all_test_data_for_export(serial: str = "", date_from: str = "",
if serial: if serial:
where.append("d.serial LIKE %s") where.append("d.serial LIKE %s")
params.append(f"%{serial}%") params.append(f"%{serial}%")
if detector_serial:
where.append("t.detector_serial LIKE %s")
params.append(f"%{detector_serial}%")
if date_from: if date_from:
where.append("t.create_time >= %s") where.append("t.create_time >= %s")
params.append(date_from if len(date_from) > 10 else date_from) params.append(date_from if len(date_from) > 10 else date_from)
@@ -248,7 +261,7 @@ def get_all_test_data_for_export(serial: str = "", date_from: str = "",
f"JOIN dnt_info d ON t.dnt_id = d.id " f"JOIN dnt_info d ON t.dnt_id = d.id "
f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id " f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id "
f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id " f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id "
f"WHERE {where_clause} ORDER BY t.id DESC", f"WHERE {where_clause} ORDER BY t.id DESC LIMIT 6000",
params, params,
) )
return cur.fetchall() return cur.fetchall()

View File

@@ -21,15 +21,16 @@ def test_data_page():
def api_test_data(): def api_test_data():
"""分页查询测试数据""" """分页查询测试数据"""
page = request.args.get("page", 1, type=int) page = request.args.get("page", 1, type=int)
per_page = request.args.get("per_page", 20, type=int) per_page = request.args.get("per_page", 100, type=int)
serial = request.args.get("serial", "", type=str) serial = request.args.get("serial", "", type=str)
detector_serial = request.args.get("detector_serial", "", type=str)
date_from = request.args.get("date_from", "", type=str) date_from = request.args.get("date_from", "", type=str)
date_to = request.args.get("date_to", "", type=str) date_to = request.args.get("date_to", "", type=str)
test_mode = request.args.get("test_mode", "", type=str) test_mode = request.args.get("test_mode", "", type=str)
data_source = request.args.get("data_source", "", type=str) data_source = request.args.get("data_source", "", 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, data_source) test_mode, data_source, detector_serial)
return jsonify({ return jsonify({
"records": records, "records": records,
"total": total, "total": total,
@@ -42,29 +43,33 @@ def api_test_data():
@bp.route("/api/test-data/chart") @bp.route("/api/test-data/chart")
@login_required @login_required
def api_chart_data(): def api_chart_data():
"""返回图表所需全部数据(不分页)""" """返回图表所需全部数据(不分页,最多 6000 条"""
serial = request.args.get("serial", "", type=str) serial = request.args.get("serial", "", type=str)
detector_serial = request.args.get("detector_serial", "", type=str)
date_from = request.args.get("date_from", "", type=str) date_from = request.args.get("date_from", "", type=str)
date_to = request.args.get("date_to", "", type=str) date_to = request.args.get("date_to", "", type=str)
test_mode = request.args.get("test_mode", "", type=str) test_mode = request.args.get("test_mode", "", type=str)
data_source = request.args.get("data_source", "", type=str) data_source = request.args.get("data_source", "", 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, data_source) test_mode, data_source,
detector_serial)
return jsonify({"records": records, "total": len(records)}) return jsonify({"records": records, "total": len(records)})
@bp.route("/api/test-data/export") @bp.route("/api/test-data/export")
@login_required @login_required
def api_export(): def api_export():
"""导出测试数据为 CSV""" """导出测试数据为 CSV(最多 6000 条)"""
serial = request.args.get("serial", "", type=str) serial = request.args.get("serial", "", type=str)
detector_serial = request.args.get("detector_serial", "", type=str)
date_from = request.args.get("date_from", "", type=str) date_from = request.args.get("date_from", "", type=str)
date_to = request.args.get("date_to", "", type=str) date_to = request.args.get("date_to", "", type=str)
test_mode = request.args.get("test_mode", "", type=str) test_mode = request.args.get("test_mode", "", type=str)
data_source = request.args.get("data_source", "", type=str) data_source = request.args.get("data_source", "", 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, data_source) test_mode, data_source,
detector_serial)
output = io.StringIO() output = io.StringIO()
writer = csv.writer(output) writer = csv.writer(output)

View File

@@ -28,12 +28,11 @@ const VIEWS = {
label: '全部数据', label: '全部数据',
data_source: '', // '' = 不过滤 data_source: '', // '' = 不过滤
cols: [ cols: [
{ key: 'id', title: 'ID' }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) },
{ key: 'serial', title: '设备编码' }, { key: 'serial', title: '设备编码', render: r => (r.serial || '').slice(-6) },
{ key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' }, { key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' },
{ key: 'model', title: '型号', render: r => getDevTypeName(r.sub_type) }, { key: 'model', title: '型号', render: r => getDevTypeName(r.sub_type) },
{ key: 'data_source', title: '来源' }, { key: 'data_source', title: '来源' },
{ key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
{ key: 'iffinish', title: '完成', render: r => r.data_source === 'B4' ? '-' : (r.iffinish === '1' ? '是' : '否') }, { key: 'iffinish', title: '完成', render: r => r.data_source === 'B4' ? '-' : (r.iffinish === '1' ? '是' : '否') },
{ key: 'fault_info', title: '故障信息', render: r => r.data_source === 'B4' ? '-' : `<span style="display:inline-block;max-width:12em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.fault_info || '')}">${escHtml(r.fault_info || '-')}</span>` }, { key: 'fault_info', title: '故障信息', render: r => r.data_source === 'B4' ? '-' : `<span style="display:inline-block;max-width:12em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.fault_info || '')}">${escHtml(r.fault_info || '-')}</span>` },
{ key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) }, { key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) },
@@ -58,18 +57,17 @@ const VIEWS = {
{ key: 'near_dist', title: '最近距离(mm)', render: r => r.data_source === 'B2' ? '-' : (r.near_dist != null ? r.near_dist + ' ' : '-') }, { key: 'near_dist', title: '最近距离(mm)', render: r => r.data_source === 'B2' ? '-' : (r.near_dist != null ? r.near_dist + ' ' : '-') },
{ key: 'far_dist', title: '最远距离(mm)', render: r => r.data_source === 'B2' ? '-' : (r.far_dist != null ? r.far_dist + ' ' : '-') }, { key: 'far_dist', title: '最远距离(mm)', render: r => r.data_source === 'B2' ? '-' : (r.far_dist != null ? r.far_dist + ' ' : '-') },
{ key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'env', title: '测试环境', render: r => envLabel(r) },
{ key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, { key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
], ],
}, },
b2: { b2: {
label: '灵敏度测试', label: '灵敏度测试',
data_source: 'B2', data_source: 'B2',
cols: [ cols: [
{ key: 'id', title: 'ID' }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) },
{ key: 'serial', title: '设备编码' }, { key: 'serial', title: '设备编码', render: r => (r.serial || '').slice(-6) },
{ key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' }, { key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' },
{ key: 'model', title: '型号', render: r => getDevTypeName(r.sub_type) }, { key: 'model', title: '型号', render: r => getDevTypeName(r.sub_type) },
{ key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
{ key: 'iffinish', title: '完成', render: r => r.iffinish === '1' ? '是' : '否' }, { key: 'iffinish', title: '完成', render: r => r.iffinish === '1' ? '是' : '否' },
{ key: 'fault_info', title: '故障信息', render: r => `<span style="display:inline-block;max-width:12em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.fault_info || '')}">${escHtml(r.fault_info || '-')}</span>` }, { key: 'fault_info', title: '故障信息', render: r => `<span style="display:inline-block;max-width:12em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.fault_info || '')}">${escHtml(r.fault_info || '-')}</span>` },
{ key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) }, { key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) },
@@ -82,15 +80,15 @@ const VIEWS = {
{ key: 'enter_speed', title: '触发速度', render: r => toSpeed(r.enter_speed) }, { key: 'enter_speed', title: '触发速度', render: r => toSpeed(r.enter_speed) },
{ key: 'exit_speed', title: '释放速度', render: r => toSpeed(r.exit_speed) }, { key: 'exit_speed', title: '释放速度', render: r => toSpeed(r.exit_speed) },
{ key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'env', title: '测试环境', render: r => envLabel(r) },
{ key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, { key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
], ],
}, },
b4: { b4: {
label: '波动测试', label: '波动测试',
data_source: 'B4', data_source: 'B4',
cols: [ cols: [
{ key: 'id', title: 'ID' }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) },
{ key: 'serial', title: '设备编码' }, { key: 'serial', title: '设备编码', render: r => (r.serial || '').slice(-6) },
{ key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' }, { key: 'detector_serial', title: '车检器序列号', render: r => r.detector_serial || '-' },
{ key: 'remain_count', title: '剩余次数' }, { key: 'remain_count', title: '剩余次数' },
{ key: 'work_freq', title: '工作频率(Hz)' }, { key: 'work_freq', title: '工作频率(Hz)' },
@@ -102,7 +100,7 @@ const VIEWS = {
{ key: 'b4_leave_dist', title: '释放高度(mm)' }, { key: 'b4_leave_dist', title: '释放高度(mm)' },
{ key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) }, { key: 'relay_out', title: '继电器', render: r => fmtRelay(r.relay_out) },
{ key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'env', title: '测试环境', render: r => envLabel(r) },
{ key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, { key: 'test_mode', title: '测试模式', render: r => r.test_mode === 1 ? '波动' : '灵敏度' },
], ],
}, },
}; };
@@ -193,14 +191,16 @@ function getDatetime(dateId, timeId) {
async function searchData(page = 1) { async function searchData(page = 1) {
currentPage = page; currentPage = page;
const serial = document.getElementById("search-serial").value; const serial = document.getElementById("search-serial").value;
const detectorSerial = document.getElementById("search-detector-serial").value;
const dateFrom = getDatetime("search-date-from", "search-time-from"); const dateFrom = getDatetime("search-date-from", "search-time-from");
const dateTo = getDatetime("search-date-to", "search-time-to"); const dateTo = getDatetime("search-date-to", "search-time-to");
const v = VIEWS[currentView]; const v = VIEWS[currentView];
const perPage = parseInt(document.getElementById("per-page").value) || 20; const perPage = parseInt(document.getElementById("per-page").value) || 100;
const params = new URLSearchParams({ page, per_page: perPage }); const params = new URLSearchParams({ page, per_page: perPage });
if (serial) params.set("serial", serial); if (serial) params.set("serial", serial);
if (detectorSerial) params.set("detector_serial", detectorSerial);
if (dateFrom) params.set("date_from", dateFrom); if (dateFrom) params.set("date_from", dateFrom);
if (dateTo) params.set("date_to", dateTo); if (dateTo) params.set("date_to", dateTo);
// 按 data_source 过滤(全部不过滤) // 按 data_source 过滤(全部不过滤)
@@ -271,12 +271,14 @@ function renderPagination() {
function exportCSV() { function exportCSV() {
const serial = document.getElementById("search-serial").value; const serial = document.getElementById("search-serial").value;
const detectorSerial = document.getElementById("search-detector-serial").value;
const dateFrom = getDatetime("search-date-from", "search-time-from"); const dateFrom = getDatetime("search-date-from", "search-time-from");
const dateTo = getDatetime("search-date-to", "search-time-to"); const dateTo = getDatetime("search-date-to", "search-time-to");
const v = VIEWS[currentView]; const v = VIEWS[currentView];
const params = new URLSearchParams(); const params = new URLSearchParams();
if (serial) params.set("serial", serial); if (serial) params.set("serial", serial);
if (detectorSerial) params.set("detector_serial", detectorSerial);
if (dateFrom) params.set("date_from", dateFrom); if (dateFrom) params.set("date_from", dateFrom);
if (dateTo) params.set("date_to", dateTo); if (dateTo) params.set("date_to", dateTo);
if (v.data_source) params.set("data_source", v.data_source); if (v.data_source) params.set("data_source", v.data_source);
@@ -364,6 +366,7 @@ async function loadChart() {
if (!container || container.style.display === 'none') return; if (!container || container.style.display === 'none') return;
const serial = document.getElementById('search-serial').value; const serial = document.getElementById('search-serial').value;
const detectorSerial = document.getElementById('search-detector-serial').value;
const dateFrom = getDatetime('search-date-from', 'search-time-from'); const dateFrom = getDatetime('search-date-from', 'search-time-from');
const dateTo = getDatetime('search-date-to', 'search-time-to'); const dateTo = getDatetime('search-date-to', 'search-time-to');
const v = VIEWS[currentView]; const v = VIEWS[currentView];
@@ -373,6 +376,7 @@ async function loadChart() {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (serial) params.set('serial', serial); if (serial) params.set('serial', serial);
if (detectorSerial) params.set('detector_serial', detectorSerial);
if (dateFrom) params.set('date_from', dateFrom); if (dateFrom) params.set('date_from', dateFrom);
if (dateTo) params.set('date_to', dateTo); if (dateTo) params.set('date_to', dateTo);
if (ds) params.set('data_source', ds); if (ds) params.set('data_source', ds);

View File

@@ -21,7 +21,11 @@
<div class="search-bar"> <div class="search-bar">
<label> <label>
设备编码: 设备编码:
<input type="text" id="search-serial" placeholder="输入设备编码搜索..."> <input type="text" id="search-serial" placeholder="设备编码...">
</label>
<label>
车检器序列号:
<input type="text" id="search-detector-serial" placeholder="车检器序列号...">
</label> </label>
<label> <label>
时间范围: 时间范围:
@@ -38,7 +42,7 @@
<select id="per-page" onchange="searchData(1)" style="width:70px;"> <select id="per-page" onchange="searchData(1)" style="width:70px;">
<option value="20">20</option> <option value="20">20</option>
<option value="50">50</option> <option value="50">50</option>
<option value="100">100</option> <option value="100" selected>100</option>
</select> </select>
</label> </label>
<button id="btn-chart" class="btn-chart" onclick="toggleChart()">📈 图表</button> <button id="btn-chart" class="btn-chart" onclick="toggleChart()">📈 图表</button>