增加导航栏

This commit is contained in:
dekun
2026-05-25 17:05:12 +08:00
parent 85477f8dac
commit 2fbb3dd45a
3 changed files with 175 additions and 107 deletions
+53 -55
View File
@@ -262,59 +262,62 @@ function renderDailyReport(payload) {
`;
}
const PANEL_FOLD_STORAGE_KEY = "matrix_panel_fold_v1";
const SECTION_NAV_STORAGE = "matrix_section_nav_v1";
function initPanelFolds() {
let saved = {};
try {
const raw = localStorage.getItem(PANEL_FOLD_STORAGE_KEY);
if (raw) saved = JSON.parse(raw);
} catch (_) {
saved = {};
}
function isNavSectionActive(navId) {
const sec = document.querySelector(`.matrix-nav-section[data-nav-id="${navId}"]`);
return !!(sec && sec.classList.contains("is-nav-active"));
}
function applyFold(panel, folded) {
const toggle = panel.querySelector(".matrix-panel-fold-toggle");
if (folded) {
panel.classList.add("is-folded");
if (toggle) toggle.setAttribute("aria-expanded", "false");
} else {
panel.classList.remove("is-folded");
if (toggle) toggle.setAttribute("aria-expanded", "true");
function initSectionNav() {
const nav = document.getElementById("sectionNav");
if (!nav) return;
const buttons = nav.querySelectorAll(".matrix-nav-item[data-nav-id]");
const sections = document.querySelectorAll(".matrix-nav-section[data-nav-id]");
function activate(navId, opts = {}) {
const scrollTop = opts.scrollTop !== false;
buttons.forEach((b) => b.classList.toggle("is-active", b.dataset.navId === navId));
sections.forEach((s) => s.classList.toggle("is-nav-active", s.dataset.navId === navId));
try {
sessionStorage.setItem(SECTION_NAV_STORAGE, navId);
} catch (_) {
/* ignore */
}
if (scrollTop) {
nav.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
try {
const url = `#${navId}`;
if (location.hash !== url) history.replaceState(null, "", url);
} catch (_) {
/* ignore */
}
}
document.querySelectorAll(".matrix-panel-fold[data-fold-id]").forEach((panel) => {
const id = panel.dataset.foldId;
const defaultOpen = panel.dataset.foldDefault === "open";
let folded;
if (Object.prototype.hasOwnProperty.call(saved, id)) {
folded = saved[id] === true;
} else {
folded = !defaultOpen;
}
applyFold(panel, folded);
const toggle = panel.querySelector(".matrix-panel-fold-toggle");
if (!toggle) return;
const onToggle = (e) => {
if (e.target.closest("[data-fold-ignore]")) return;
const willFold = !panel.classList.contains("is-folded");
applyFold(panel, willFold);
saved[id] = willFold;
try {
localStorage.setItem(PANEL_FOLD_STORAGE_KEY, JSON.stringify(saved));
} catch (_) {
/* ignore quota */
let initial = "gemma-funnel";
const hash = (location.hash || "").replace(/^#/, "");
if (hash && document.querySelector(`.matrix-nav-section[data-nav-id="${hash}"]`)) {
initial = hash;
} else {
try {
const saved = sessionStorage.getItem(SECTION_NAV_STORAGE);
if (saved && document.querySelector(`.matrix-nav-section[data-nav-id="${saved}"]`)) {
initial = saved;
}
};
} catch (_) {
/* ignore */
}
}
activate(initial, { scrollTop: false });
toggle.addEventListener("click", onToggle);
toggle.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onToggle(e);
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const id = btn.dataset.navId;
if (!id) return;
activate(id);
if (id === "scan-layers" || id === "runtime-logs" || id === "telemetry") {
refresh();
}
});
});
@@ -430,11 +433,6 @@ async function saveDailyReportSettings() {
}
}
function isPanelFolded(foldId) {
const panel = document.querySelector(`.matrix-panel-fold[data-fold-id="${foldId}"]`);
return !!(panel && panel.classList.contains("is-folded"));
}
async function refresh() {
if (document.visibilityState !== "visible") return;
const mobileLite = isMobileLite();
@@ -452,7 +450,7 @@ async function refresh() {
const statusPre = document.getElementById("status");
const cf = document.getElementById("config");
if (!mobileLite) {
if (!mobileLite || isNavSectionActive("telemetry")) {
if (statusPre) {
const st = pretty(status);
if (statusPre.textContent !== st) statusPre.textContent = st;
@@ -512,7 +510,7 @@ async function refresh() {
const watchRows = allAlerts.filter((a) => (a.details && a.details.signal_level) === "WATCH");
const triggerRows = allAlerts.filter((a) => (a.details && a.details.signal_level) === "TRIGGER");
if (!mobileLite || !isPanelFolded("scan-layers")) {
if (isNavSectionActive("scan-layers")) {
renderItems("watchAlerts", watchRows, (a) => `
<div class="matrix-row-title"><strong>${a.symbol}</strong> <span class="matrix-dim">${escapeHtml(a.chain || "")}</span></div>
<div>级别: ${(a.details && a.details.signal_level) || "N/A"}</div>
@@ -541,7 +539,7 @@ async function refresh() {
}
}
if (!mobileLite || !isPanelFolded("runtime-logs")) {
if (isNavSectionActive("runtime-logs")) {
renderItems("logs", logs.items || [], (l) => `
<div><strong class="matrix-log-lvl-${(l.level || "").toLowerCase()}">[${l.level}]</strong> ${escapeHtml(l.message)}</div>
<div class="time">${formatIsoToBeijing(l.created_at)}</div>
@@ -877,7 +875,7 @@ tickClock();
setInterval(tickClock, 1000);
initMatrixRain();
initFunnelWindowControls();
initPanelFolds();
initSectionNav();
refresh();
const REFRESH_MS = isMobileLite() ? 10000 : 4000;
setInterval(refresh, REFRESH_MS);