diff --git a/packages/@core/preferences/src/index.ts b/packages/@core/preferences/src/index.ts index 33444e13..15ec8963 100644 --- a/packages/@core/preferences/src/index.ts +++ b/packages/@core/preferences/src/index.ts @@ -2,33 +2,17 @@ import type { Preferences } from './types'; import { preferencesManager } from './preferences'; -// 偏好设置(带有层级关系) -const preferences: Preferences = - preferencesManager.getPreferences.apply(preferencesManager); - -// 更新偏好设置 -const updatePreferences = - preferencesManager.updatePreferences.bind(preferencesManager); - -// 重置偏好设置 -const resetPreferences = - preferencesManager.resetPreferences.bind(preferencesManager); - -const clearPreferencesCache = - preferencesManager.clearCache.bind(preferencesManager); - -// 初始化偏好设置 -const initPreferences = - preferencesManager.initPreferences.bind(preferencesManager); - -export { - clearPreferencesCache, - initPreferences, - preferences, - preferencesManager, - resetPreferences, +export const { + getPreferences, updatePreferences, -}; + resetPreferences, + clearCache, + initPreferences, +} = preferencesManager; + +export const preferences: Preferences = getPreferences(); + +export { preferencesManager }; export * from './constants'; export type * from './types'; diff --git a/packages/@core/preferences/src/preferences.ts b/packages/@core/preferences/src/preferences.ts index 7fb245e3..93487d2c 100644 --- a/packages/@core/preferences/src/preferences.ts +++ b/packages/@core/preferences/src/preferences.ts @@ -16,168 +16,167 @@ import { import { defaultPreferences } from './config'; import { updateCSSVariables } from './update-css-variables'; -const STORAGE_KEY = 'preferences'; -const STORAGE_KEY_LOCALE = `${STORAGE_KEY}-locale`; -const STORAGE_KEY_THEME = `${STORAGE_KEY}-theme`; +const STORAGE_KEYS = { + MAIN: 'preferences', + LOCALE: 'preferences-locale', + THEME: 'preferences-theme', +} as const; class PreferenceManager { - private cache: null | StorageManager = null; - // private flattenedState: Flatten; + private cache: StorageManager; + private debouncedSave: (preference: Preferences) => void; private initialPreferences: Preferences = defaultPreferences; - private isInitialized: boolean = false; - private savePreferences: (preference: Preferences) => void; - private state: Preferences = reactive({ - ...this.loadPreferences(), - }); + private isInitialized = false; + private state: Preferences; + constructor() { this.cache = new StorageManager(); - - // 避免频繁的操作缓存 - this.savePreferences = useDebounceFn( - (preference: Preferences) => this._savePreferences(preference), + this.state = reactive( + this.loadFromCache() || { ...defaultPreferences }, + ); + this.debouncedSave = useDebounceFn( + (preference) => this.saveToCache(preference), 150, ); } - clearCache() { - [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => { - this.cache?.removeItem(key); - }); - } - - public getInitialPreferences() { - return this.initialPreferences; - } - - public getPreferences() { - return readonly(this.state); - } + /** + * 清除所有缓存的偏好设置 + */ + clearCache = () => { + Object.values(STORAGE_KEYS).forEach((key) => this.cache.removeItem(key)); + }; /** - * 覆盖偏好设置 - * overrides 要覆盖的偏好设置 - * namespace 命名空间 + * 获取初始化偏好设置 */ - public async initPreferences({ namespace, overrides }: InitialOptions) { - // 是否初始化过 + getInitialPreferences = () => { + return this.initialPreferences; + }; + + /** + * 获取当前偏好设置(只读) + */ + getPreferences = () => { + return readonly(this.state); + }; + + /** + * 初始化偏好设置 + * @param namespace - 命名空间,用于隔离不同应用的配置 + * @param overrides - 要覆盖的偏好设置 + */ + initPreferences = async ({ namespace, overrides }: InitialOptions) => { + // 防止重复初始化 if (this.isInitialized) { return; } - // 初始化存储管理器 + + // 使用命名空间初始化存储管理器 this.cache = new StorageManager({ prefix: namespace }); + // 合并初始偏好设置 this.initialPreferences = merge({}, overrides, defaultPreferences); - // 加载并合并当前存储的偏好设置 + // 加载缓存的偏好设置并与初始配置合并 + const cachedPreferences = this.loadFromCache() || {}; const mergedPreference = merge( {}, - // overrides, - this.loadCachedPreferences() || {}, + cachedPreferences, this.initialPreferences, ); // 更新偏好设置 this.updatePreferences(mergedPreference); + // 设置监听器 this.setupWatcher(); + // 初始化平台标识 this.initPlatform(); - // 标记为已初始化 + this.isInitialized = true; - } + }; /** - * 重置偏好设置 - * 偏好设置将被重置为初始值,并从 localStorage 中移除。 - * - * @example - * 假设 initialPreferences 为 { theme: 'light', language: 'en' } - * 当前 state 为 { theme: 'dark', language: 'fr' } - * this.resetPreferences(); - * 调用后,state 将被重置为 { theme: 'light', language: 'en' } - * 并且 localStorage 中的对应项将被移除 + * 重置偏好设置到初始状态 */ - resetPreferences() { + resetPreferences = () => { // 将状态重置为初始偏好设置 Object.assign(this.state, this.initialPreferences); - // 保存重置后的偏好设置 - this.savePreferences(this.state); - // 从存储中移除偏好设置项 - [STORAGE_KEY, STORAGE_KEY_THEME, STORAGE_KEY_LOCALE].forEach((key) => { - this.cache?.removeItem(key); - }); - this.updatePreferences(this.state); - } + + // 保存偏好设置至缓存 + this.saveToCache(this.state); + + // 直接触发 UI 更新 + this.handleUpdates(this.state); + }; /** * 更新偏好设置 * @param updates - 要更新的偏好设置 */ - public updatePreferences(updates: DeepPartial) { + updatePreferences = (updates: DeepPartial) => { + // 深度合并更新内容和当前状态 const mergedState = merge({}, updates, markRaw(this.state)); - Object.assign(this.state, mergedState); - // 根据更新的键值执行相应的操作 + // 根据更新的值执行更新 this.handleUpdates(updates); - this.savePreferences(this.state); - } + + // 保存到缓存 + this.debouncedSave(this.state); + }; /** - * 保存偏好设置 - * @param {Preferences} preference - 需要保存的偏好设置 - */ - private _savePreferences(preference: Preferences) { - this.cache?.setItem(STORAGE_KEY, preference); - this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale); - this.cache?.setItem(STORAGE_KEY_THEME, preference.theme.mode); - } - - /** - * 处理更新的键值 - * 根据更新的键值执行相应的操作。 - * @param {DeepPartial} updates - 部分更新的偏好设置 + * 处理更新 + * @param updates - 更新的偏好设置 */ private handleUpdates(updates: DeepPartial) { - const themeUpdates = updates.theme || {}; - const appUpdates = updates.app || {}; + const { theme, app } = updates; + if ( - (themeUpdates && Object.keys(themeUpdates).length > 0) || - Reflect.has(themeUpdates, 'fontSize') + theme && + (Object.keys(theme).length > 0 || Reflect.has(theme, 'fontSize')) ) { updateCSSVariables(this.state); } if ( - Reflect.has(appUpdates, 'colorGrayMode') || - Reflect.has(appUpdates, 'colorWeakMode') + app && + (Reflect.has(app, 'colorGrayMode') || Reflect.has(app, 'colorWeakMode')) ) { this.updateColorMode(this.state); } } + /** + * 初始化平台标识 + */ private initPlatform() { - const dom = document.documentElement; - dom.dataset.platform = isMacOs() ? 'macOs' : 'window'; + document.documentElement.dataset.platform = isMacOs() ? 'macOs' : 'window'; } /** - * 从缓存中加载偏好设置。如果缓存中没有找到对应的偏好设置,则返回默认偏好设置。 + * 从缓存加载偏好设置 + * @returns 缓存的偏好设置,如果不存在则返回 null */ - private loadCachedPreferences() { - return this.cache?.getItem(STORAGE_KEY); + private loadFromCache(): null | Preferences { + return this.cache.getItem(STORAGE_KEYS.MAIN); } /** - * 加载偏好设置 - * @returns {Preferences} 加载的偏好设置 + * 保存偏好设置到缓存 + * @param preference - 要保存的偏好设置 */ - private loadPreferences(): Preferences { - return this.loadCachedPreferences() || { ...defaultPreferences }; + private saveToCache(preference: Preferences) { + this.cache.setItem(STORAGE_KEYS.MAIN, preference); + this.cache.setItem(STORAGE_KEYS.LOCALE, preference.app.locale); + this.cache.setItem(STORAGE_KEYS.THEME, preference.theme.mode); } /** - * 监听状态和系统偏好设置的变化。 + * 监听状态和系统偏好设置的变化 */ private setupWatcher() { if (this.isInitialized) { @@ -187,6 +186,7 @@ class PreferenceManager { // 监听断点,判断是否移动端 const breakpoints = useBreakpoints(breakpointsTailwind); const isMobile = breakpoints.smaller('md'); + watch( () => isMobile.value, (val) => { @@ -201,12 +201,13 @@ class PreferenceManager { window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', ({ matches: isDark }) => { - // 如果偏好设置中主题模式为auto,则跟随系统更新 + // 仅在自动模式下跟随系统主题 if (this.state.theme.mode === 'auto') { + // 先应用实际的主题 this.updatePreferences({ theme: { mode: isDark ? 'dark' : 'light' }, }); - // 恢复为auto模式 + // 再恢复为 auto 模式,保持跟随系统的状态 this.updatePreferences({ theme: { mode: 'auto' }, }); @@ -216,19 +217,17 @@ class PreferenceManager { /** * 更新页面颜色模式(灰色、色弱) - * @param preference + * @param preference - 偏好设置 */ private updateColorMode(preference: Preferences) { - if (preference.app) { - const { colorGrayMode, colorWeakMode } = preference.app; - const dom = document.documentElement; - const COLOR_WEAK = 'invert-mode'; - const COLOR_GRAY = 'grayscale-mode'; - dom.classList.toggle(COLOR_WEAK, colorWeakMode); - dom.classList.toggle(COLOR_GRAY, colorGrayMode); - } + const { colorGrayMode, colorWeakMode } = preference.app; + const dom = document.documentElement; + + dom.classList.toggle('invert-mode', colorWeakMode); + dom.classList.toggle('grayscale-mode', colorGrayMode); } } const preferencesManager = new PreferenceManager(); + export { PreferenceManager, preferencesManager }; diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue index 605f3cf5..6263fbdf 100644 --- a/packages/effects/layouts/src/basic/layout.vue +++ b/packages/effects/layouts/src/basic/layout.vue @@ -351,8 +351,6 @@ const headerSlots = computed(() => { diff --git a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue index d4f6b096..df25dcfc 100644 --- a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue +++ b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue @@ -19,7 +19,7 @@ import { computed, ref } from 'vue'; import { Copy, Pin, PinOff, RotateCw } from '@vben/icons'; import { $t, loadLocaleMessages } from '@vben/locales'; import { - clearPreferencesCache, + clearCache, preferences, resetPreferences, usePreferences, @@ -228,7 +228,7 @@ async function handleCopy() { async function handleClearCache() { resetPreferences(); - clearPreferencesCache(); + clearCache(); emit('clearPreferencesAndLogout'); }