feat: add glass theme

This commit is contained in:
jxxghp
2026-07-22 15:00:11 +08:00
parent 5cf0dc116a
commit 17cdfe6885
14 changed files with 706 additions and 64 deletions
+16 -1
View File
@@ -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()
+2 -1
View File
@@ -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 }) {
+13 -6
View File
@@ -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
}
+12 -3
View File
@@ -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"]')