feat: admin用户支持删除测试数据(按条件/日期范围),含确认框+日志
This commit is contained in:
@@ -582,3 +582,48 @@ def delete_vehicle_base_test(test_id: int):
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ─── 测试数据删除 ──────────────────────────────────────────────
|
||||
|
||||
def delete_test_data(serial: str = "", date_from: str = "",
|
||||
date_to: str = "", data_source: str = "") -> int:
|
||||
"""删除符合条件的测试数据,返回删除行数
|
||||
|
||||
必须至少提供一个条件(serial / date范围 / data_source),不允许无条件全删。
|
||||
"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
where = []
|
||||
params = []
|
||||
if serial:
|
||||
where.append("t.dnt_id IN (SELECT id FROM dnt_info WHERE serial LIKE %s)")
|
||||
params.append(f"%{serial}%")
|
||||
if date_from:
|
||||
where.append("t.create_time >= %s")
|
||||
params.append(date_from)
|
||||
if date_to:
|
||||
where.append("t.create_time <= %s")
|
||||
params.append(date_to + " 23:59:59")
|
||||
if data_source:
|
||||
where.append("t.data_source = %s")
|
||||
params.append(data_source)
|
||||
|
||||
if not where:
|
||||
return 0 # 拒绝无条件全删
|
||||
|
||||
where_clause = " AND ".join(where)
|
||||
cur.execute(
|
||||
f"SELECT COUNT(*) as cnt FROM tb_state_tst t WHERE {where_clause}",
|
||||
params,
|
||||
)
|
||||
cnt = cur.fetchone()["cnt"]
|
||||
|
||||
cur.execute(
|
||||
f"DELETE t FROM tb_state_tst t WHERE {where_clause}", params,
|
||||
)
|
||||
conn.commit()
|
||||
return cnt
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import csv
|
||||
import io
|
||||
from flask import Blueprint, jsonify, render_template, request, Response
|
||||
from flask_login import login_required
|
||||
from app.models import get_test_data, get_all_test_data_for_export
|
||||
from flask_login import login_required, current_user
|
||||
from app.models import get_test_data, get_all_test_data_for_export, delete_test_data, insert_log
|
||||
|
||||
bp = Blueprint("test_data", __name__)
|
||||
|
||||
@@ -78,3 +78,39 @@ def api_export():
|
||||
mimetype="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=test_data.csv"},
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/api/test-data/delete", methods=["POST"])
|
||||
@login_required
|
||||
def api_delete():
|
||||
"""删除测试数据(仅 admin)"""
|
||||
if current_user.role != "admin":
|
||||
return jsonify({"ok": False, "error": "无权限"}), 403
|
||||
|
||||
data = request.get_json() or {}
|
||||
serial = data.get("serial", "")
|
||||
date_from = data.get("date_from", "")
|
||||
date_to = data.get("date_to", "")
|
||||
data_source = data.get("data_source", "")
|
||||
|
||||
cnt = delete_test_data(serial, date_from, date_to, data_source)
|
||||
|
||||
detail_parts = [f"删除 {cnt} 条测试数据"]
|
||||
if serial:
|
||||
detail_parts.append(f"设备={serial}")
|
||||
if date_from:
|
||||
detail_parts.append(f"从{date_from}")
|
||||
if date_to:
|
||||
detail_parts.append(f"至{date_to}")
|
||||
if data_source:
|
||||
detail_parts.append(f"来源={data_source}")
|
||||
|
||||
insert_log(
|
||||
current_user.id, current_user.username, "delete",
|
||||
target="test_data",
|
||||
detail=", ".join(detail_parts),
|
||||
result="ok",
|
||||
ip=request.remote_addr or "",
|
||||
)
|
||||
|
||||
return jsonify({"ok": True, "deleted": cnt})
|
||||
|
||||
@@ -364,3 +364,46 @@ async function loadChart() {
|
||||
|
||||
renderHead();
|
||||
searchData(1);
|
||||
|
||||
// ─── 删除(admin)─────────────────────────────────
|
||||
|
||||
function confirmDelete() {
|
||||
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];
|
||||
const ds = v.data_source || '';
|
||||
|
||||
let desc = '';
|
||||
if (serial) desc += `设备: ${serial}\n`;
|
||||
if (dateFrom || dateTo) desc += `日期: ${dateFrom || '不限'} ~ ${dateTo || '不限'}\n`;
|
||||
if (ds) desc += `数据来源: ${ds}\n`;
|
||||
if (!desc) desc = '⚠ 未设置任何筛选条件,不会删除任何数据';
|
||||
|
||||
const msg = `确认删除以下条件的测试数据?\n\n${desc}\n此操作不可撤销!`;
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
doDelete(serial, dateFrom, dateTo, ds);
|
||||
}
|
||||
|
||||
async function doDelete(serial, dateFrom, dateTo, dataSource) {
|
||||
try {
|
||||
const resp = await fetch('/api/test-data/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serial, date_from: dateFrom,
|
||||
date_to: dateTo, data_source: dataSource,
|
||||
}),
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.ok) {
|
||||
alert(`已删除 ${data.deleted} 条记录`);
|
||||
searchData(1);
|
||||
} else {
|
||||
alert('删除失败: ' + (data.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('删除请求失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
</select>
|
||||
</label>
|
||||
<button id="btn-chart" class="btn-chart" onclick="toggleChart()">📈 图表</button>
|
||||
{% if current_user.role == 'admin' %}
|
||||
<button id="btn-delete" class="btn-delete" onclick="confirmDelete()">🗑 删除</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="chart-container" style="display:none; width:100%; height:500px; margin-bottom:16px;"></div>
|
||||
@@ -73,6 +76,17 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn-chart.active { background: #27ae60; color: #fff; }
|
||||
.btn-delete {
|
||||
margin-left: 8px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #e74c3c;
|
||||
background: #fff;
|
||||
color: #e74c3c;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn-delete:hover { background: #e74c3c; color: #fff; }
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user