mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-02 22:17:47 +08:00
feat: add glass theme
This commit is contained in:
@@ -1,7 +1,22 @@
|
||||
import { applyDocumentThemeChrome } from '@/utils/themePalette'
|
||||
import {
|
||||
applyDocumentThemeChrome,
|
||||
getThemeColorScheme,
|
||||
resolveThemeName,
|
||||
themeRootPalettes,
|
||||
} from '@/utils/themePalette'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
describe('theme palette', () => {
|
||||
// 玻璃主题在首屏与运行阶段都应保持深色外壳和夜航底色。
|
||||
it('resolves glass as a dark theme with its dedicated launch palette', () => {
|
||||
expect(resolveThemeName('glass')).toBe('glass')
|
||||
expect(getThemeColorScheme('glass')).toBe('dark')
|
||||
expect(themeRootPalettes.glass).toEqual({
|
||||
background: '#0B1322',
|
||||
primary: '#E4B863',
|
||||
})
|
||||
})
|
||||
|
||||
it('notifies the favicon renderer with the applied primary color', () => {
|
||||
const handleFaviconChange = vi.fn()
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据当前 Vuetify 主题同步 ApexCharts 的全局明暗外观。 */
|
||||
export function configureApexChartsTheme(themeName: string) {
|
||||
if (typeof window === 'undefined' || !window.Apex) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const isDark = themeName === 'dark' || themeName === 'transparent'
|
||||
const isDark = ['dark', 'glass', 'transparent'].includes(themeName)
|
||||
|
||||
window.Apex.dataLabels = {
|
||||
formatter: function (_: number, { seriesIndex, w }: { seriesIndex: number; w: any }) {
|
||||
|
||||
@@ -12,7 +12,12 @@ class ThemeManager {
|
||||
private currentTheme: string = 'default'
|
||||
private loadedLinks: Map<string, HTMLLinkElement> = new Map()
|
||||
private themeListeners: Map<(theme: string) => void, EventListener> = new Map()
|
||||
private bundledThemeLoaders: Record<string, () => Promise<unknown>> = {
|
||||
glass: () => import('@/styles/themes/glass.scss'),
|
||||
transparent: () => import('@/styles/themes/transparent.scss'),
|
||||
}
|
||||
|
||||
/** 注册内置主题及其按需加载的样式入口。 */
|
||||
constructor() {
|
||||
// 注册所有可用主题
|
||||
this.registerTheme('default', '')
|
||||
@@ -20,8 +25,9 @@ class ThemeManager {
|
||||
this.registerTheme('dark', '')
|
||||
this.registerTheme('purple', '')
|
||||
this.registerTheme('auto', '')
|
||||
// 只有透明主题有特定的CSS文件
|
||||
// 透明和玻璃主题使用独立的按需样式文件。
|
||||
this.registerTheme('transparent', './src/styles/themes/transparent.css')
|
||||
this.registerTheme('glass', './src/styles/themes/glass.css')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +86,10 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// 动态导入CSS模块
|
||||
if (themeName === 'transparent') {
|
||||
await import('@/styles/themes/transparent.scss')
|
||||
// 动态导入打包内置的主题样式模块。
|
||||
const bundledThemeLoader = this.bundledThemeLoaders[themeName]
|
||||
if (bundledThemeLoader) {
|
||||
await bundledThemeLoader()
|
||||
this.themes.get(themeName)!.isLoaded = true
|
||||
return
|
||||
}
|
||||
@@ -134,8 +141,8 @@ class ThemeManager {
|
||||
const theme = this.themes.get(themeName)
|
||||
if (!theme) return
|
||||
|
||||
// 对于动态导入的CSS,我们无法直接卸载,但可以标记为未加载
|
||||
if (themeName === 'transparent') {
|
||||
// 对于动态导入的 CSS,我们无法直接卸载,但可以标记为未加载。
|
||||
if (this.bundledThemeLoaders[themeName]) {
|
||||
theme.isLoaded = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||
|
||||
export type ThemePreference = 'auto' | 'default' | 'light' | 'dark' | 'purple' | 'transparent'
|
||||
export type ResolvedThemeName = 'light' | 'dark' | 'purple' | 'transparent'
|
||||
export type ThemePreference = 'auto' | 'dark' | 'default' | 'glass' | 'light' | 'purple' | 'transparent'
|
||||
export type ResolvedThemeName = 'dark' | 'glass' | 'light' | 'purple' | 'transparent'
|
||||
export type ThemeColorScheme = 'light' | 'dark'
|
||||
|
||||
interface ThemeRootPalette {
|
||||
@@ -33,14 +33,20 @@ export const themeRootPalettes: Record<ResolvedThemeName, ThemeRootPalette> = {
|
||||
background: '#1C1C1C',
|
||||
primary: '#A370F7',
|
||||
},
|
||||
glass: {
|
||||
background: '#0B1322',
|
||||
primary: '#E4B863',
|
||||
},
|
||||
}
|
||||
|
||||
const validResolvedThemes = new Set<string>(Object.keys(themeRootPalettes))
|
||||
|
||||
/** 将任意主题名称收敛为应用支持的实际主题。 */
|
||||
function normalizeResolvedThemeName(themeName: string | null | undefined): ResolvedThemeName {
|
||||
return validResolvedThemes.has(themeName || '') ? (themeName as ResolvedThemeName) : 'light'
|
||||
}
|
||||
|
||||
/** 将用户主题偏好解析为当前实际主题。 */
|
||||
export function resolveThemeName(themePreference: string | null | undefined): ResolvedThemeName {
|
||||
if (themePreference === 'auto') {
|
||||
return checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||
@@ -53,16 +59,19 @@ export function resolveThemeName(themePreference: string | null | undefined): Re
|
||||
return normalizeResolvedThemeName(themePreference)
|
||||
}
|
||||
|
||||
/** 返回浏览器原生控件应使用的明暗配色。 */
|
||||
export function getThemeColorScheme(themeName: string | null | undefined): ThemeColorScheme {
|
||||
return ['dark', 'purple', 'transparent'].includes(themeName || '') ? 'dark' : 'light'
|
||||
return ['dark', 'glass', 'purple', 'transparent'].includes(themeName || '') ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
/** 批量同步匹配选择器的 meta 内容。 */
|
||||
function setMetaContent(selector: string, content: string) {
|
||||
document.querySelectorAll<HTMLMetaElement>(selector).forEach(meta => {
|
||||
meta.content = content
|
||||
})
|
||||
}
|
||||
|
||||
/** 更新或创建浏览器主题色 meta 标签。 */
|
||||
function ensureThemeColorMeta(themeColor: string) {
|
||||
const metas = document.querySelectorAll<HTMLMetaElement>('meta[name="theme-color"]')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user