fix(theme): 修复跟随系统主题解析

This commit is contained in:
Coast
2026-04-21 14:48:07 +08:00
parent 33bde0eaf8
commit 1d6c65d1a7
9 changed files with 99 additions and 35 deletions

View File

@@ -27,6 +27,8 @@ const watermarkContent = appConfig.name;
// 明亮/暗黑主题水印字体颜色适配 // 明亮/暗黑主题水印字体颜色适配
const fontColor = computed(() => { const fontColor = computed(() => {
return settingsStore.theme === ThemeMode.DARK ? "rgba(255, 255, 255, .15)" : "rgba(0, 0, 0, .15)"; return settingsStore.resolvedTheme === ThemeMode.DARK
? "rgba(255, 255, 255, .15)"
: "rgba(0, 0, 0, .15)";
}); });
</script> </script>

View File

@@ -19,7 +19,7 @@ const layout = computed(() => settingsStore.layout);
const hamburgerClass = computed(() => { const hamburgerClass = computed(() => {
// 如果暗黑主题 // 如果暗黑主题
if (settingsStore.theme === ThemeMode.DARK) { if (settingsStore.resolvedTheme === ThemeMode.DARK) {
return "hamburger--white"; return "hamburger--white";
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<el-dropdown trigger="click" @command="handleDarkChange"> <el-dropdown trigger="click" @command="handleDarkChange">
<el-icon :size="20"> <el-icon :size="20">
<component :is="settingsStore.theme === ThemeMode.DARK ? Moon : Sunny" /> <component :is="currentThemeIcon" />
</el-icon> </el-icon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@@ -34,6 +34,14 @@ const theneList = [
{ label: t("login.auto"), value: ThemeMode.AUTO, component: Monitor }, { label: t("login.auto"), value: ThemeMode.AUTO, component: Monitor },
]; ];
const currentThemeIcon = computed(() => {
if (settingsStore.theme === ThemeMode.AUTO) {
return Monitor;
}
return settingsStore.resolvedTheme === ThemeMode.DARK ? Moon : Sunny;
});
const handleDarkChange = (theme: ThemeMode) => { const handleDarkChange = (theme: ThemeMode) => {
settingsStore.theme = theme; settingsStore.theme = theme;
}; };

View File

@@ -77,7 +77,7 @@ import { useLayout } from "./useLayout";
import { useAppStore, usePermissionStore, useSettingsStore } from "@/stores"; import { useAppStore, usePermissionStore, useSettingsStore } from "@/stores";
import { isExternal } from "@/utils/index"; import { isExternal } from "@/utils/index";
import { translateRouteTitle } from "@/lang/utils"; import { translateRouteTitle } from "@/lang/utils";
import { SidebarColor } from "@/enums/settings"; import { SidebarColor, ThemeMode } from "@/enums/settings";
import { ElIcon } from "element-plus"; import { ElIcon } from "element-plus";
import BaseLayout from "./BaseLayout.vue"; import BaseLayout from "./BaseLayout.vue";
import LayoutLogo from "./components/LayoutLogo.vue"; import LayoutLogo from "./components/LayoutLogo.vue";
@@ -127,7 +127,8 @@ const isLogoCollapsed = computed(() => width.value < 768);
// 是否使用深色菜单配色(暗色主题或经典蓝侧边栏) // 是否使用深色菜单配色(暗色主题或经典蓝侧边栏)
const useMenuColors = computed( const useMenuColors = computed(
() => () =>
settingsStore.theme === "dark" || settingsStore.sidebarColorScheme === SidebarColor.CLASSIC_BLUE settingsStore.resolvedTheme === ThemeMode.DARK ||
settingsStore.sidebarColorScheme === SidebarColor.CLASSIC_BLUE
); );
// 顶部菜单项(处理单子菜单显示优化) // 顶部菜单项(处理单子菜单显示优化)

View File

@@ -11,13 +11,17 @@
<el-divider>{{ t("settings.theme") }}</el-divider> <el-divider>{{ t("settings.theme") }}</el-divider>
<div class="flex-center"> <div class="flex-center">
<el-switch <el-radio-group v-model="themeMode" class="theme-mode-group">
v-model="isDark" <el-radio-button :value="ThemeMode.LIGHT">
active-icon="Moon" {{ t("login.light") }}
inactive-icon="Sunny" </el-radio-button>
class="theme-switch" <el-radio-button :value="ThemeMode.DARK">
@change="handleThemeChange" {{ t("login.dark") }}
/> </el-radio-button>
<el-radio-button :value="ThemeMode.AUTO">
{{ t("login.auto") }}
</el-radio-button>
</el-radio-group>
</div> </div>
</section> </section>
@@ -71,7 +75,10 @@
<el-switch v-model="settingsStore.colorWeak" /> <el-switch v-model="settingsStore.colorWeak" />
</div> </div>
<div v-if="!isDark" class="config-item flex-x-between"> <div
v-if="settingsStore.resolvedTheme !== ThemeMode.DARK"
class="config-item flex-x-between"
>
<span class="text-xs">{{ t("settings.sidebarColorScheme") }}</span> <span class="text-xs">{{ t("settings.sidebarColorScheme") }}</span>
<el-radio-group v-model="sidebarColor" @change="changeSidebarColor"> <el-radio-group v-model="sidebarColor" @change="changeSidebarColor">
<el-radio :value="SidebarColor.CLASSIC_BLUE"> <el-radio :value="SidebarColor.CLASSIC_BLUE">
@@ -199,8 +206,13 @@ const colorPresets = [...themeColorPresets];
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const isDark = ref<boolean>(settingsStore.theme === ThemeMode.DARK);
const sidebarColor = ref(settingsStore.sidebarColorScheme); const sidebarColor = ref(settingsStore.sidebarColorScheme);
const themeMode = computed({
get: () => settingsStore.theme,
set: (value: ThemeMode) => {
settingsStore.theme = value;
},
});
const selectedThemeColor = computed({ const selectedThemeColor = computed({
get: () => settingsStore.themeColor, get: () => settingsStore.themeColor,
@@ -214,15 +226,6 @@ const drawerVisible = computed({
set: (value) => (settingsStore.settingsVisible = value), set: (value) => (settingsStore.settingsVisible = value),
}); });
/**
* 处理主题切换
*
* @param isDark 是否启用暗黑模式
*/
const handleThemeChange = (isDark: string | number | boolean) => {
settingsStore.theme = isDark ? ThemeMode.DARK : ThemeMode.LIGHT;
};
/** /**
* 更改侧边栏颜色 * 更改侧边栏颜色
* *
@@ -277,8 +280,6 @@ const handleResetSettings = async () => {
try { try {
settingsStore.resetSettings(); settingsStore.resetSettings();
// 同步更新本地状态"
isDark.value = settingsStore.theme === ThemeMode.DARK;
sidebarColor.value = settingsStore.sidebarColorScheme; sidebarColor.value = settingsStore.sidebarColorScheme;
ElMessage.success(t("settings.resetSuccess")); ElMessage.success(t("settings.resetSuccess"));

View File

@@ -29,7 +29,7 @@ import { useRoute } from "vue-router";
import path from "path-browserify"; import path from "path-browserify";
import type { MenuInstance } from "element-plus"; import type { MenuInstance } from "element-plus";
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import { SidebarColor } from "@/enums/settings"; import { SidebarColor, ThemeMode } from "@/enums/settings";
import { useSettingsStore, useAppStore } from "@/stores"; import { useSettingsStore, useAppStore } from "@/stores";
import { isExternal } from "@/utils/index"; import { isExternal } from "@/utils/index";
import LayoutSidebarItem from "./LayoutSidebarItem.vue"; import LayoutSidebarItem from "./LayoutSidebarItem.vue";
@@ -61,7 +61,7 @@ const currentRoute = useRoute();
const expandedMenuIndexes = ref<string[]>([]); const expandedMenuIndexes = ref<string[]>([]);
// 获取主题 // 获取主题
const theme = computed(() => settingsStore.theme); const theme = computed(() => settingsStore.resolvedTheme);
// 获取浅色主题下的侧边栏配色方案 // 获取浅色主题下的侧边栏配色方案
const sidebarColorScheme = computed(() => settingsStore.sidebarColorScheme); const sidebarColorScheme = computed(() => settingsStore.sidebarColorScheme);
@@ -69,7 +69,7 @@ const sidebarColorScheme = computed(() => settingsStore.sidebarColorScheme);
// 菜单主题属性 // 菜单主题属性
const menuThemeProps = computed(() => { const menuThemeProps = computed(() => {
const isDarkOrClassicBlue = const isDarkOrClassicBlue =
theme.value === "dark" || sidebarColorScheme.value === SidebarColor.CLASSIC_BLUE; theme.value === ThemeMode.DARK || sidebarColorScheme.value === SidebarColor.CLASSIC_BLUE;
return { return {
backgroundColor: isDarkOrClassicBlue ? variables["menu-background"] : undefined, backgroundColor: isDarkOrClassicBlue ? variables["menu-background"] : undefined,

View File

@@ -125,15 +125,15 @@ function handleProfileClick() {
// 根据主题和侧边栏配色方案选择样式类 // 根据主题和侧边栏配色方案选择样式类
const navbarActionsClass = computed(() => { const navbarActionsClass = computed(() => {
const { theme, sidebarColorScheme, layout } = settingStore; const { resolvedTheme, sidebarColorScheme, layout } = settingStore;
// 暗黑主题下,所有布局都使用白色文字 // 暗黑主题下,所有布局都使用白色文字
if (theme === ThemeMode.DARK) { if (resolvedTheme === ThemeMode.DARK) {
return "navbar-actions--white-text"; return "navbar-actions--white-text";
} }
// 明亮主题下 // 明亮主题下
if (theme === ThemeMode.LIGHT) { if (resolvedTheme === ThemeMode.LIGHT) {
// 顶部布局和混合布局的顶部区域: // 顶部布局和混合布局的顶部区域:
// - 如果侧边栏是经典蓝色,使用白色文字 // - 如果侧边栏是经典蓝色,使用白色文字
// - 如果侧边栏是极简白色,使用深色文字 // - 如果侧边栏是极简白色,使用深色文字

View File

@@ -1,6 +1,13 @@
import { SidebarColor, ThemeMode } from "@/enums"; import { SidebarColor, ThemeMode } from "@/enums";
import type { LayoutMode } from "@/enums"; import type { LayoutMode } from "@/enums";
import { applyTheme, generateThemeColors, toggleDarkMode, toggleSidebarColor } from "@/utils/theme"; import {
applyTheme,
generateThemeColors,
resolveThemeMode,
toggleDarkMode,
toggleSidebarColor,
watchSystemTheme,
} from "@/utils/theme";
import { STORAGE_KEYS } from "@/constants"; import { STORAGE_KEYS } from "@/constants";
import { defaults } from "@/settings"; import { defaults } from "@/settings";
@@ -25,14 +32,34 @@ export const useSettingsStore = defineStore("setting", () => {
// 主题 // 主题
const theme = useStorage<ThemeMode>(STORAGE_KEYS.THEME, defaults.theme); const theme = useStorage<ThemeMode>(STORAGE_KEYS.THEME, defaults.theme);
const themeColor = useStorage(STORAGE_KEYS.THEME_COLOR, defaults.themeColor); const themeColor = useStorage(STORAGE_KEYS.THEME_COLOR, defaults.themeColor);
const resolvedTheme = ref<ThemeMode>(resolveThemeMode(theme.value));
// 特殊模式 // 特殊模式
const grayMode = useStorage(STORAGE_KEYS.GRAY_MODE, false); const grayMode = useStorage(STORAGE_KEYS.GRAY_MODE, false);
const colorWeak = useStorage(STORAGE_KEYS.COLOR_WEAK, false); const colorWeak = useStorage(STORAGE_KEYS.COLOR_WEAK, false);
// 主题变化监听 // 主题变化监听
let stopWatchingSystemTheme: (() => void) | undefined;
watch( watch(
[theme, themeColor], theme,
(value) => {
stopWatchingSystemTheme?.();
resolvedTheme.value = resolveThemeMode(value);
if (value === ThemeMode.AUTO) {
stopWatchingSystemTheme = watchSystemTheme((systemTheme) => {
resolvedTheme.value = systemTheme;
});
} else {
stopWatchingSystemTheme = undefined;
}
},
{ immediate: true }
);
watch(
[resolvedTheme, themeColor],
([t, c]: [ThemeMode, string]) => { ([t, c]: [ThemeMode, string]) => {
toggleDarkMode(t === ThemeMode.DARK); toggleDarkMode(t === ThemeMode.DARK);
applyTheme(generateThemeColors(c, t)); applyTheme(generateThemeColors(c, t));
@@ -87,6 +114,7 @@ export const useSettingsStore = defineStore("setting", () => {
layout, layout,
themeColor, themeColor,
theme, theme,
resolvedTheme,
resetSettings, resetSettings,
}; };
}); });

View File

@@ -1,5 +1,7 @@
import { ThemeMode } from "@/enums"; import { ThemeMode } from "@/enums";
const SYSTEM_DARK_MEDIA = "(prefers-color-scheme: dark)";
// 辅助函数:将十六进制颜色转换为 RGB // 辅助函数:将十六进制颜色转换为 RGB
function hexToRgb(hex: string): [number, number, number] { function hexToRgb(hex: string): [number, number, number] {
const bigint = parseInt(hex.slice(1), 16); const bigint = parseInt(hex.slice(1), 16);
@@ -52,6 +54,7 @@ export const getLightColor = (color: string, level: number): string => {
* @param theme 主题类型 * @param theme 主题类型
*/ */
export function generateThemeColors(primary: string, theme: ThemeMode) { export function generateThemeColors(primary: string, theme: ThemeMode) {
const resolvedTheme = resolveThemeMode(theme);
const colors: Record<string, string> = { const colors: Record<string, string> = {
primary, primary,
}; };
@@ -59,18 +62,39 @@ export function generateThemeColors(primary: string, theme: ThemeMode) {
// 生成浅色变体 // 生成浅色变体
for (let i = 1; i <= 9; i++) { for (let i = 1; i <= 9; i++) {
colors[`primary-light-${i}`] = colors[`primary-light-${i}`] =
theme === ThemeMode.LIGHT resolvedTheme === ThemeMode.LIGHT
? `${getLightColor(primary, i / 10)}` ? `${getLightColor(primary, i / 10)}`
: `${getDarkColor(primary, i / 10)}`; : `${getDarkColor(primary, i / 10)}`;
} }
// 生成深色变体 // 生成深色变体
colors["primary-dark-2"] = colors["primary-dark-2"] =
theme === ThemeMode.LIGHT ? `${getLightColor(primary, 0.2)}` : `${getDarkColor(primary, 0.3)}`; resolvedTheme === ThemeMode.LIGHT
? `${getLightColor(primary, 0.2)}`
: `${getDarkColor(primary, 0.3)}`;
return colors; return colors;
} }
export function getSystemTheme() {
return window.matchMedia(SYSTEM_DARK_MEDIA).matches ? ThemeMode.DARK : ThemeMode.LIGHT;
}
export function resolveThemeMode(theme: ThemeMode) {
return theme === ThemeMode.AUTO ? getSystemTheme() : theme;
}
export function watchSystemTheme(callback: (theme: ThemeMode) => void) {
const mediaQuery = window.matchMedia(SYSTEM_DARK_MEDIA);
const handler = () => callback(mediaQuery.matches ? ThemeMode.DARK : ThemeMode.LIGHT);
mediaQuery.addEventListener("change", handler);
return () => {
mediaQuery.removeEventListener("change", handler);
};
}
export function applyTheme(colors: Record<string, string>) { export function applyTheme(colors: Record<string, string>) {
const el = document.documentElement; const el = document.documentElement;