This commit is contained in:
XOF
2025-11-20 12:24:05 +08:00
commit f28bdc751f
164 changed files with 64248 additions and 0 deletions

213
web/static/js/main.js Normal file
View File

@@ -0,0 +1,213 @@
import {
CustomSelect,
modalManager,
taskCenterManager,
toastManager,
uiPatterns
} from "./chunk-EZAP7GR4.js";
import {
apiFetch,
apiFetchJson
} from "./chunk-PLQL6WIO.js";
// frontend/js/components/slidingTabs.js
var SlidingTabs = class {
/**
* @param {HTMLElement} containerElement - The main container element with the `data-sliding-tabs-container` attribute.
*/
constructor(containerElement) {
this.container = containerElement;
this.indicator = this.container.querySelector("[data-tab-indicator]");
this.tabs = this.container.querySelectorAll("[data-tab-item]");
this.activeTab = this.container.querySelector(".tab-active");
if (!this.indicator || this.tabs.length === 0) {
console.error("SlidingTabs component is missing required elements (indicator or items).", this.container);
return;
}
this.init();
}
init() {
if (this.activeTab) {
setTimeout(() => this.updateIndicator(this.activeTab), 50);
}
this.bindEvents();
}
updateIndicator(targetTab) {
if (!targetTab) return;
const containerRect = this.container.getBoundingClientRect();
const targetRect = targetTab.getBoundingClientRect();
const left = targetRect.left - containerRect.left;
const width = targetRect.width;
this.indicator.style.left = `${left}px`;
this.indicator.style.width = `${width}px`;
}
bindEvents() {
this.tabs.forEach((tab) => {
tab.addEventListener("click", (e) => {
if (this.activeTab) {
this.activeTab.classList.remove("tab-active");
}
tab.classList.add("tab-active");
this.activeTab = tab;
this.updateIndicator(this.activeTab);
});
tab.addEventListener("mouseenter", () => {
this.updateIndicator(tab);
});
});
this.container.addEventListener("mouseleave", () => {
this.updateIndicator(this.activeTab);
});
}
};
document.addEventListener("DOMContentLoaded", () => {
const allTabContainers = document.querySelectorAll("[data-sliding-tabs-container]");
allTabContainers.forEach((container) => {
new SlidingTabs(container);
});
});
// frontend/js/components/themeManager.js
var themeManager = {
// 用于存储图标的 SVG HTML
icons: {},
init: function() {
this.html = document.documentElement;
this.buttons = document.querySelectorAll(".theme-btn");
this.cyclerBtn = document.getElementById("theme-cycler-btn");
this.cyclerIconContainer = document.getElementById("theme-cycler-icon");
if (!this.html || this.buttons.length === 0 || !this.cyclerBtn || !this.cyclerIconContainer) {
console.warn("ThemeManager init failed: one or more required elements not found.");
return;
}
this.mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
this.storeIcons();
this.buttons.forEach((btn) => {
btn.addEventListener("click", () => this.setTheme(btn.dataset.theme));
});
this.cyclerBtn.addEventListener("click", () => this.cycleTheme());
this.mediaQuery.addEventListener("change", () => this.applyTheme());
this.applyTheme();
},
// 从现有按钮中提取并存储 SVG 图标
storeIcons: function() {
this.buttons.forEach((btn) => {
const theme = btn.dataset.theme;
const svg = btn.querySelector("svg");
if (theme && svg) {
this.icons[theme] = svg.outerHTML;
}
});
},
// 循环切换主题的核心逻辑
cycleTheme: function() {
const themes = ["system", "light", "dark"];
const currentTheme = this.getTheme();
const currentIndex = themes.indexOf(currentTheme);
const nextIndex = (currentIndex + 1) % themes.length;
this.setTheme(themes[nextIndex]);
},
applyTheme: function() {
let theme = this.getTheme();
if (theme === "system") {
theme = this.mediaQuery.matches ? "dark" : "light";
}
if (theme === "dark") {
this.html.classList.add("dark");
} else {
this.html.classList.remove("dark");
}
this.updateButtons();
this.updateCyclerIcon();
},
setTheme: function(theme) {
localStorage.setItem("theme", theme);
this.applyTheme();
},
getTheme: function() {
return localStorage.getItem("theme") || "system";
},
updateButtons: function() {
const currentTheme = this.getTheme();
this.buttons.forEach((btn) => {
if (btn.dataset.theme === currentTheme) {
btn.classList.add("bg-white", "dark:bg-zinc-700");
} else {
btn.classList.remove("bg-white", "dark:bg-zinc-700");
}
});
},
// 更新移动端循环按钮的图标
updateCyclerIcon: function() {
if (this.cyclerIconContainer) {
const currentTheme = this.getTheme();
if (this.icons[currentTheme]) {
this.cyclerIconContainer.innerHTML = this.icons[currentTheme];
}
}
}
};
// frontend/js/layout/base.js
function initActiveNav() {
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll(".nav-link");
navLinks.forEach((link) => {
const linkPath = link.getAttribute("href");
if (linkPath && linkPath !== "/" && currentPath.startsWith(linkPath)) {
const wrapper = link.closest(".nav-item-wrapper");
if (wrapper) {
wrapper.dataset.active = "true";
}
}
});
}
function bridgeApiToGlobal() {
window.apiFetch = apiFetch;
window.apiFetchJson = apiFetchJson;
console.log("[Bridge] apiFetch and apiFetchJson are now globally available.");
}
function initLayout() {
console.log("[Init] Executing global layout JavaScript...");
initActiveNav();
themeManager.init();
bridgeApiToGlobal();
}
var base_default = initLayout;
// frontend/js/main.js
var pageModules = {
// 键 'dashboard' 对应一个函数,该函数调用 import() 返回一个 Promise
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
"dashboard": () => import("./dashboard-CJJWKYPR.js"),
"keys": () => import("./keys-A2UAJYOX.js"),
"logs": () => import("./logs-FGZ2SMPN.js")
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
// 未来新增的页面只需在这里添加一行映射esbuild会自动处理
};
document.addEventListener("DOMContentLoaded", async () => {
base_default();
const allTabContainers = document.querySelectorAll("[data-sliding-tabs-container]");
allTabContainers.forEach((container) => new SlidingTabs(container));
const allSelectContainers = document.querySelectorAll("[data-custom-select-container]");
allSelectContainers.forEach((container) => new CustomSelect(container));
taskCenterManager.init();
const pageContainer = document.querySelector("[data-page-id]");
if (pageContainer) {
const pageId = pageContainer.dataset.pageId;
if (pageId && pageModules[pageId]) {
try {
const pageModule = await pageModules[pageId]();
if (pageModule.default && typeof pageModule.default === "function") {
pageModule.default();
}
} catch (error) {
console.error(`Failed to load module for page: ${pageId}`, error);
}
}
}
});
window.modalManager = modalManager;
window.taskCenterManager = taskCenterManager;
window.toastManager = toastManager;
window.uiPatterns = uiPatterns;