feat: 设备日志增加时间范围查询 + CSV 导出

- 查询 API 增加 date_from/date_to 参数(前端日期+时间选择器)
- 新增 /api/device-logs/export CSV 导出端点
- 新增 export_device_logs() 模型函数(全量不分页)
- 删除校验放宽:允许纯时间范围作为删除条件
- 前端增加导出 CSV 按钮,遵循 test_data 页面模式
This commit is contained in:
wangfq
2026-06-10 10:44:19 +08:00
parent 6b35d07025
commit d3b6d79a03
3 changed files with 125 additions and 7 deletions

View File

@@ -17,7 +17,16 @@
<option value="tcp_disconnect">TCP断开</option>
</select>
</label>
<label>
时间范围:
<input type="date" id="search-date-from">
<input type="time" id="search-time-from" step="1" style="width:110px;" title="起始时间(时:分:秒)">
<input type="date" id="search-date-to">
<input type="time" id="search-time-to" step="1" style="width:110px;" title="截止时间(时:分:秒)">
</label>
<button onclick="searchLogs(1)" class="btn-search">查询</button>
<button onclick="exportCSV()" class="btn-export">导出 CSV</button>
{% if current_user.role == 'admin' %}
<button onclick="confirmDeleteLogs()" class="btn-delete">🗑 删除</button>
{% endif %}
@@ -44,13 +53,25 @@
<script>
let currentPage = 1, totalPages = 1;
function getDatetime(dateId, timeId) {
const d = document.getElementById(dateId).value;
const t = document.getElementById(timeId).value;
if (!d) return "";
if (!t) return d; // 纯日期 → 后端自动补时间
return d + " " + t; // 完整 datetime
}
async function searchLogs(page = 1) {
currentPage = page;
const serial = document.getElementById("search-serial").value;
const event_type = document.getElementById("search-event").value;
const date_from = getDatetime("search-date-from", "search-time-from");
const date_to = getDatetime("search-date-to", "search-time-to");
const params = new URLSearchParams({page, per_page: 30});
if (serial) params.set("serial", serial);
if (event_type) params.set("event_type", event_type);
if (date_from) params.set("date_from", date_from);
if (date_to) params.set("date_to", date_to);
const resp = await fetch(`/api/device-logs?${params}`);
const data = await resp.json();
@@ -120,20 +141,41 @@ function escHtml(s) {
return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// ─── 导出 CSV ────────────────────────────────────
function exportCSV() {
const serial = document.getElementById("search-serial").value;
const event_type = document.getElementById("search-event").value;
const date_from = getDatetime("search-date-from", "search-time-from");
const date_to = getDatetime("search-date-to", "search-time-to");
const params = new URLSearchParams();
if (serial) params.set("serial", serial);
if (event_type) params.set("event_type", event_type);
if (date_from) params.set("date_from", date_from);
if (date_to) params.set("date_to", date_to);
// 直接打开下载链接
window.location.href = `/api/device-logs/export?${params}`;
}
// ─── 删除 ────────────────────────────────────────
async function confirmDeleteLogs() {
const serial = document.getElementById("search-serial").value;
const event_type = document.getElementById("search-event").value;
if (!serial && !event_type) {
alert("请至少输入设备序列号或选择事件类型作为删除条件");
const date_from = getDatetime("search-date-from", "search-time-from");
const date_to = getDatetime("search-date-to", "search-time-to");
if (!serial && !event_type && !date_from && !date_to) {
alert("请至少输入设备序列号、选择事件类型或指定时间范围作为删除条件");
return;
}
const msg = `确认删除设备日志?\n条件: serial=${serial || '(无)'} type=${event_type || '(无)'}\n此操作不可撤销!`;
const msg = `确认删除设备日志?\n条件: serial=${serial || '(无)'} type=${event_type || '(无)'} time=${date_from||'(无)'}~${date_to||'(无)'}\n此操作不可撤销!`;
if (!confirm(msg)) return;
const resp = await fetch("/api/device-logs/delete", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({serial, event_type}),
body: JSON.stringify({serial, event_type, date_from, date_to}),
});
const data = await resp.json();
if (data.ok) {