mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-12 19:40:41 +08:00
213 lines
5.1 KiB
TypeScript
213 lines
5.1 KiB
TypeScript
// 主题管理器 - 动态加载主题CSS
|
||
export interface ThemeConfig {
|
||
name: string
|
||
cssPath: string
|
||
isLoaded: boolean
|
||
}
|
||
|
||
class ThemeManager {
|
||
private themes: Map<string, ThemeConfig> = new Map()
|
||
private currentTheme: string = 'default'
|
||
private loadedLinks: Map<string, HTMLLinkElement> = new Map()
|
||
|
||
constructor() {
|
||
// 注册所有可用主题
|
||
this.registerTheme('default', '')
|
||
this.registerTheme('light', '')
|
||
this.registerTheme('dark', '')
|
||
this.registerTheme('purple', '')
|
||
this.registerTheme('auto', '')
|
||
// 只有透明主题有特定的CSS文件
|
||
this.registerTheme('transparent', './src/styles/themes/transparent.css')
|
||
}
|
||
|
||
/**
|
||
* 注册主题
|
||
*/
|
||
registerTheme(name: string, cssPath: string): void {
|
||
this.themes.set(name, {
|
||
name,
|
||
cssPath,
|
||
isLoaded: false,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取当前主题
|
||
*/
|
||
getCurrentTheme(): string {
|
||
return this.currentTheme
|
||
}
|
||
|
||
/**
|
||
* 设置主题
|
||
*/
|
||
async setTheme(themeName: string): Promise<void> {
|
||
if (!this.themes.has(themeName)) {
|
||
console.warn(`Theme "${themeName}" not found`)
|
||
return
|
||
}
|
||
|
||
const theme = this.themes.get(themeName)!
|
||
|
||
// 清理其他主题的CSS(除了当前要设置的主题)
|
||
this.unloadOtherThemes()
|
||
|
||
// 如果主题有CSS文件,则加载CSS
|
||
if (theme.cssPath) {
|
||
try {
|
||
await this.loadThemeCSS(themeName, theme.cssPath)
|
||
} catch (error) {
|
||
console.error(`Failed to load CSS for theme "${themeName}":`, error)
|
||
// 即使CSS加载失败,也继续应用主题(使用默认样式)
|
||
}
|
||
}
|
||
|
||
// 应用主题(无论是否有CSS文件)
|
||
this.applyTheme(themeName)
|
||
}
|
||
|
||
/**
|
||
* 加载主题CSS文件
|
||
*/
|
||
private async loadThemeCSS(themeName: string, cssPath: string): Promise<void> {
|
||
// 如果已经加载过,直接返回
|
||
if (this.loadedLinks.has(themeName)) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 动态导入CSS模块
|
||
if (themeName === 'transparent') {
|
||
await import('@/styles/themes/transparent.scss')
|
||
this.themes.get(themeName)!.isLoaded = true
|
||
return
|
||
}
|
||
|
||
// 对于其他主题,使用传统的link方式
|
||
const link = document.createElement('link')
|
||
link.rel = 'stylesheet'
|
||
link.type = 'text/css'
|
||
link.href = cssPath
|
||
link.id = `theme-${themeName}`
|
||
|
||
// 等待CSS加载完成
|
||
await new Promise<void>((resolve, reject) => {
|
||
link.onload = () => {
|
||
this.loadedLinks.set(themeName, link)
|
||
this.themes.get(themeName)!.isLoaded = true
|
||
resolve()
|
||
}
|
||
link.onerror = () => {
|
||
reject(new Error(`Failed to load theme CSS: ${cssPath}`))
|
||
}
|
||
})
|
||
|
||
// 添加到head
|
||
document.head.appendChild(link)
|
||
} catch (error) {
|
||
console.error(`Error loading theme "${themeName}":`, error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用主题到DOM
|
||
*/
|
||
private applyTheme(themeName: string): void {
|
||
// 移除之前的主题属性
|
||
document.documentElement.removeAttribute('data-theme')
|
||
|
||
// 设置新主题(除了default主题)
|
||
if (themeName !== 'default') {
|
||
document.documentElement.setAttribute('data-theme', themeName)
|
||
}
|
||
|
||
this.currentTheme = themeName
|
||
|
||
// 触发主题变更事件
|
||
this.dispatchThemeChangeEvent(themeName)
|
||
}
|
||
|
||
/**
|
||
* 卸载主题CSS
|
||
*/
|
||
unloadTheme(themeName: string): void {
|
||
const theme = this.themes.get(themeName)
|
||
if (!theme) return
|
||
|
||
// 对于动态导入的CSS,我们无法直接卸载,但可以标记为未加载
|
||
if (themeName === 'transparent') {
|
||
theme.isLoaded = false
|
||
return
|
||
}
|
||
|
||
// 对于传统link方式加载的CSS
|
||
const link = this.loadedLinks.get(themeName)
|
||
if (link) {
|
||
link.remove()
|
||
this.loadedLinks.delete(themeName)
|
||
theme.isLoaded = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 卸载所有主题CSS(除了当前主题)
|
||
*/
|
||
unloadOtherThemes(): void {
|
||
for (const [themeName] of this.themes) {
|
||
if (themeName !== this.currentTheme && this.themes.get(themeName)?.isLoaded) {
|
||
this.unloadTheme(themeName)
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取已注册的主题列表
|
||
*/
|
||
getAvailableThemes(): string[] {
|
||
return Array.from(this.themes.keys())
|
||
}
|
||
|
||
/**
|
||
* 检查主题是否已加载
|
||
*/
|
||
isThemeLoaded(themeName: string): boolean {
|
||
return this.themes.get(themeName)?.isLoaded || false
|
||
}
|
||
|
||
/**
|
||
* 触发主题变更事件
|
||
*/
|
||
private dispatchThemeChangeEvent(themeName: string): void {
|
||
const event = new CustomEvent('themechange', {
|
||
detail: { theme: themeName },
|
||
})
|
||
document.dispatchEvent(event)
|
||
}
|
||
|
||
/**
|
||
* 监听主题变更事件
|
||
*/
|
||
onThemeChange(callback: (theme: string) => void): void {
|
||
document.addEventListener('themechange', (event: any) => {
|
||
callback(event.detail.theme)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 移除主题变更监听器
|
||
*/
|
||
offThemeChange(callback: (theme: string) => void): void {
|
||
document.removeEventListener('themechange', (event: any) => {
|
||
callback(event.detail.theme)
|
||
})
|
||
}
|
||
}
|
||
|
||
// 创建单例实例
|
||
export const themeManager = new ThemeManager()
|
||
|
||
// 导出类型
|
||
export type { ThemeManager }
|