import type { DeepPartial, Preference, PreferenceKeys, } from '@vben-core/typings'; import { convertToHslCssVar, merge } from '@vben-core/toolkit'; import type { Ref } from 'vue'; import { breakpointsTailwind, useBreakpoints, useCssVar } from '@vueuse/core'; import { markRaw, reactive, ref, watch } from 'vue'; import { defaultPreference } from './config'; import type { PreferenceCacheType } from './cache'; /** * 当前偏好设置 */ const currentPreference: Preference = reactive(defaultPreference); /** * 当前偏好设置原始值 */ const initialPreference: Ref = ref(defaultPreference); let preferenceCache: PreferenceCacheType; /** * 是否监听过系统设置变化 */ let isRegisterListen = false; function updatePreference(key: keyof Preference, value: boolean | string): void; function updatePreference( preference: DeepPartial, value?: undefined, ): void; /** * 更新偏好设置 * @param preference - 一个部分偏好设置对象,它将被合并到当前偏好设置中。 */ function updatePreference(preference: any, value: any) { if (typeof preference === 'string') { updatePreference({ [preference]: value }, value); } else { const updateKeys = Object.keys(preference) as PreferenceKeys[]; const mergePreference = merge(preference, markRaw(currentPreference)); Object.assign(currentPreference, mergePreference); // 当修改到颜色变量时,更新 css 变量 if (updateKeys.includes('colorPrimary')) { updateCssVar(currentPreference); } // 更新主题 if (updateKeys.includes('theme')) { updateTheme(currentPreference); } // 更新页面颜色模式(灰色、色弱) if ( updateKeys.includes('colorGrayMode') || updateKeys.includes('colorWeakMode') ) { updateColorMode(currentPreference); } preferenceCache.set(currentPreference); } } /** * 更新 CSS 变量 * @param preference - 当前偏好设置对象,它的颜色值将被转换成 HSL 格式并设置为 CSS 变量。 */ function updateCssVar(preference: Preference) { for (const [key, value] of Object.entries(preference)) { if (['colorPrimary'].includes(key)) { const cssVarKey = key.replaceAll(/([A-Z])/g, '-$1').toLowerCase(); const cssVarValue = useCssVar(`--${cssVarKey}`); cssVarValue.value = convertToHslCssVar(value); } } } /** * 更新主题 * @param preference - 当前偏好设置对象,它的主题值将被用来设置文档的主题。 */ function updateTheme(preference: Preference) { // 当修改到颜色变量时,更新 css 变量 const root = document.documentElement; if (root) { const dark = isDarkTheme(preference.theme); root.classList.toggle('dark', dark); } // 只需要监听一次即可 listenOnce(preference); } /** * 更新页面颜色模式(灰色、色弱) * @param preference */ function updateColorMode(preference: Preference) { const { colorGrayMode, colorWeakMode } = preference; const body = document.body; const COLOR_WEAK = 'invert'; const COLOR_GRAY = 'grayscale'; colorWeakMode ? body.classList.add(COLOR_WEAK) : body.classList.remove(COLOR_WEAK); colorGrayMode ? body.classList.add(COLOR_GRAY) : body.classList.remove(COLOR_GRAY); } /** * 1. 监听系统主题偏好设置变化 * 2. 监听断点,判断是否移动端 * @param preference - 当前偏好设置对象,当系统主题偏好变化时,它的主题值会被更新。 */ function listenOnce(preference: Preference) { if (isRegisterListen) { return; } isRegisterListen = true; // 监听系统主题偏好设置变化 window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', ({ matches: isDark }) => { preference.theme = isDark ? 'dark' : 'light'; updateTheme(preference); }); // 监听断点,判断是否移动端 const breakpoints = useBreakpoints(breakpointsTailwind); const isMobile = breakpoints.smaller('md'); watch( () => isMobile.value, (val) => { currentPreference.isMobile = val; }, { immediate: true }, ); } /** * 重置偏好设置 * 偏好设置将被重置为初始值,并从 localStorage 中移除。 */ function resetPreference() { Object.assign(currentPreference, initialPreference.value); updatePreference(currentPreference); preferenceCache.remove(); } /** * 配置当前app默认的偏好配置 * @param overrides */ function overridesPreference( overrides: DeepPartial, cache: PreferenceCacheType, ) { preferenceCache = cache; /** * 重置状态时用到的原始值 */ initialPreference.value = merge(overrides, defaultPreference); const mergedPreference = merge( overrides, preferenceCache.get(defaultPreference), ); updatePreference(mergedPreference); } function isDarkTheme(theme: string) { let dark = theme === 'dark'; if (theme === 'auto') { dark = window.matchMedia('(prefers-color-scheme: dark)').matches; } return dark; } export { currentPreference, initialPreference, isDarkTheme, overridesPreference, resetPreference, updatePreference, };