v2.6.0 历史分页+筛选+搜索+表头升降排序;一键清空历史(DELETE /api/tests);右下角置顶/置底悬浮按钮

This commit is contained in:
2026-09-06 02:32:35 +08:00
parent 901232c75f
commit edbaedce15
7 changed files with 346 additions and 49 deletions
+143 -30
View File
@@ -422,6 +422,7 @@ function stopTest() {
/* ───────────────────────── 测试历史 ───────────────────────── */
let histSelected = new Set(); // 勾选用于对比的测试 id
const histState = { page: 1, pageSize: 20, q: "", status: "", provider: "", sort: "id", order: "desc" };
function updateHistSelectedLabel() {
const el = $("#hist-selected");
@@ -429,43 +430,74 @@ function updateHistSelectedLabel() {
}
async function loadHistory() {
const list = await api("/api/tests");
const p = new URLSearchParams({
page: histState.page, page_size: histState.pageSize,
q: histState.q, status: histState.status, provider: histState.provider,
sort: histState.sort, order: histState.order,
});
let d;
try {
d = await api("/api/tests?" + p.toString());
} catch (e) { return; }
if (!d || !d.ok) return;
renderHistory(d);
}
function renderHistory(d) {
const list = d.items || [];
const tb = $("#history tbody");
tb.innerHTML = "";
const selAll = $("#hist-check-all");
if (selAll) selAll.checked = false;
const hasFilter = histState.q || histState.status || histState.provider;
$("#hist-total").textContent = d.total ? `(共 ${d.total} 条)` : "";
if (!list.length) {
tb.innerHTML = '<tr><td colspan="13" style="color:var(--muted);text-align:center">暂无测试记录</td></tr>';
tb.innerHTML = `<tr><td colspan="13" style="color:var(--muted);text-align:center">暂无${hasFilter ? "匹配的" : ""}测试记录${hasFilter ? "(试试调整筛选条件)" : ""}</td></tr>`;
updateHistSelectedLabel();
return;
}
for (const t of list) {
const s = t.summary || {};
const g = t.gen || {};
const cls = s.concurrency_levels || g.concurrency_levels || [1];
const tr = document.createElement("tr");
tr.innerHTML = `
<td class="chk-col"><input type="checkbox" class="hist-chk" data-id="${t.id}" ${histSelected.has(t.id) ? "checked" : ""}></td>
<td>#${t.id}</td>
<td>${esc(t.created_at)}</td>
<td title="${esc(t.name || "")}">${esc(t.name || "—")}</td>
<td>${esc(PROVIDER_LABEL[t.provider] || t.provider)}</td>
<td>${esc(t.model)}</td>
<td class="num">${esc(cls.join("/"))}</td>
<td class="num">${fmt(s.samples_ok)}/${fmt(s.samples_total)}</td>
<td class="num">${fmt(s.avg_ttft_ms)}</td>
<td class="num">${fmt(s.avg_prefill_speed)}</td>
<td class="num">${fmt(s.avg_decode_speed)}</td>
<td><span class="status-pill ${esc(t.status)}">${STATUS_LABEL[t.status] || t.status}</span></td>
<td>
<button class="btn small" data-view="${t.id}">查看</button>
<button class="btn small" data-send="${t.id}" title="发送到评测站对应账号下">📤 发送</button>
<button class="btn small" data-xlsx="${t.id}">Excel</button>
<button class="btn small danger" data-del="${t.id}">删除</button>
</td>`;
tb.appendChild(tr);
} else {
for (const t of list) {
const s = t.summary || {};
const g = t.gen || {};
const cls = s.concurrency_levels || g.concurrency_levels || [1];
const tr = document.createElement("tr");
tr.innerHTML = `
<td class="chk-col"><input type="checkbox" class="hist-chk" data-id="${t.id}" ${histSelected.has(t.id) ? "checked" : ""}></td>
<td>#${t.id}</td>
<td>${esc(t.created_at)}</td>
<td title="${esc(t.name || "")}">${esc(t.name || "—")}</td>
<td>${esc(PROVIDER_LABEL[t.provider] || t.provider)}</td>
<td>${esc(t.model)}</td>
<td class="num">${esc(cls.join("/"))}</td>
<td class="num">${fmt(s.samples_ok)}/${fmt(s.samples_total)}</td>
<td class="num">${fmt(s.avg_ttft_ms)}</td>
<td class="num">${fmt(s.avg_prefill_speed)}</td>
<td class="num">${fmt(s.avg_decode_speed)}</td>
<td><span class="status-pill ${esc(t.status)}">${STATUS_LABEL[t.status] || t.status}</span></td>
<td>
<button class="btn small" data-view="${t.id}">查看</button>
<button class="btn small" data-send="${t.id}" title="发送到评测站对应账号下">📤 发送</button>
<button class="btn small" data-xlsx="${t.id}">Excel</button>
<button class="btn small danger" data-del="${t.id}">删除</button>
</td>`;
tb.appendChild(tr);
}
}
updateHistSelectedLabel();
// 分页信息
const totalPages = Math.max(1, d.total_pages || 1);
$("#hist-page-info").textContent = `${d.page} / ${totalPages} 页 · 共 ${d.total}`;
$("#hist-prev").disabled = d.page <= 1;
$("#hist-next").disabled = d.page >= totalPages;
histState.page = d.page;
updateSortIndicators();
}
function updateSortIndicators() {
$$("#history thead th.sortable").forEach((th) => {
th.classList.remove("asc", "desc");
if (th.dataset.sort === histState.sort) th.classList.add(histState.order);
});
}
/* ───────────────────────── 多测试对比 ───────────────────────── */
@@ -1064,8 +1096,57 @@ function bind() {
$("#btn-clear-console").addEventListener("click", clearConsole);
$("#btn-export-log").addEventListener("click", exportCurrentLog);
$("#btn-refresh-history").addEventListener("click", loadHistory);
$("#btn-refresh-history").addEventListener("click", () => { histState.page = 1; loadHistory(); });
$("#btn-compare").addEventListener("click", openCompare);
$("#btn-clear-history").addEventListener("click", clearAllHistory);
// 历史:筛选 / 搜索 / 每页条数 / 分页 / 重置
let histQTimer = null;
$("#hist-q").addEventListener("input", () => {
clearTimeout(histQTimer);
histQTimer = setTimeout(() => {
histState.q = $("#hist-q").value.trim();
histState.page = 1;
loadHistory();
}, 300);
});
$("#hist-status").addEventListener("change", () => {
histState.status = $("#hist-status").value;
histState.page = 1;
loadHistory();
});
$("#hist-provider").addEventListener("change", () => {
histState.provider = $("#hist-provider").value;
histState.page = 1;
loadHistory();
});
$("#hist-size").addEventListener("change", () => {
histState.pageSize = parseInt($("#hist-size").value) || 20;
histState.page = 1;
loadHistory();
});
$("#hist-prev").addEventListener("click", () => {
if (histState.page > 1) { histState.page--; loadHistory(); }
});
$("#hist-next").addEventListener("click", () => {
if (!$("#hist-next").disabled) { histState.page++; loadHistory(); }
});
$("#hist-reset").addEventListener("click", resetHistoryFilters);
// 历史:点击表头升降排序
$("#history thead").addEventListener("click", (e) => {
const th = e.target.closest("th.sortable");
if (!th) return;
const key = th.dataset.sort;
if (histState.sort === key) {
histState.order = histState.order === "asc" ? "desc" : "asc";
} else {
histState.sort = key;
histState.order = (key === "id" || key === "created_at") ? "desc" : "asc";
}
histState.page = 1;
loadHistory();
});
$("#history tbody").addEventListener("click", (e) => {
const v = e.target.closest("[data-view]");
@@ -1108,6 +1189,38 @@ function bind() {
$("#dt-send-eval").addEventListener("click", () => { if (window.__detail) sendToEval(window.__detail.id); });
document.addEventListener("keydown", (e) => { if (e.key === "Escape") { hideModelPick(); closeDetail(); closeCompare(); } });
// 右下角一键置顶 / 置底
$("#fab-top").addEventListener("click", () => window.scrollTo({ top: 0, behavior: "smooth" }));
$("#fab-bottom").addEventListener("click", () =>
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: "smooth" }));
}
/* ───────────────────────── 历史筛选工具 ───────────────────────── */
function resetHistoryFilters() {
histState.q = ""; histState.status = ""; histState.provider = "";
histState.sort = "id"; histState.order = "desc";
histState.page = 1;
$("#hist-q").value = "";
$("#hist-status").value = "";
$("#hist-provider").value = "";
loadHistory();
}
async function clearAllHistory() {
if (!confirm("确定清空全部测试历史?\n所有测试记录、采样数据与日志将被永久删除,无法恢复!")) return;
if (!confirm("再次确认:真的要清空全部历史记录吗?")) return;
const r = await api("/api/tests", "DELETE");
if (r.ok) {
histSelected.clear();
histState.page = 1;
toast("✅ 历史记录已全部清空");
loadHistory();
updateHistSelectedLabel();
} else {
toast("❌ 清空失败:" + (r.error || "未知错误"));
}
}
/* ───────────────────────── 初始化 ───────────────────────── */