New
This commit is contained in:
105
frontend/js/components/themeManager.js
Normal file
105
frontend/js/components/themeManager.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// Filename: frontend/js/components/themeManager.js
|
||||
|
||||
/**
|
||||
* 负责管理应用程序的三态主题(系统、亮色、暗色)。
|
||||
* 封装了所有与主题切换相关的 DOM 操作、事件监听和 localStorage 交互。
|
||||
*/
|
||||
export const 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)');
|
||||
|
||||
// 初始化时,从三按钮组中提取 SVG 并存储起来
|
||||
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; // brilliantly simple cycling logic
|
||||
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();
|
||||
// 从我们存储的 icons 对象中找到对应的 SVG 并注入
|
||||
if (this.icons[currentTheme]) {
|
||||
this.cyclerIconContainer.innerHTML = this.icons[currentTheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user