preferences.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import type { DeepPartial } from '@vben-core/typings';
  2. import type { InitialOptions, Preferences } from './types';
  3. import { markRaw, reactive, readonly, watch } from 'vue';
  4. import { StorageManager } from '@vben-core/shared/cache';
  5. import { isMacOs, merge } from '@vben-core/shared/utils';
  6. import {
  7. breakpointsTailwind,
  8. useBreakpoints,
  9. useDebounceFn,
  10. } from '@vueuse/core';
  11. import { defaultPreferences } from './config';
  12. import { updateCSSVariables } from './update-css-variables';
  13. const STORAGE_KEY = 'preferences';
  14. const STORAGE_KEY_LOCALE = `${STORAGE_KEY}-locale`;
  15. const STORAGE_KEY_THEME = `${STORAGE_KEY}-theme`;
  16. class PreferenceManager {
  17. private cache: null | StorageManager = null;
  18. // private flattenedState: Flatten<Preferences>;
  19. private initialPreferences: Preferences = defaultPreferences;
  20. private isInitialized: boolean = false;
  21. private savePreferences: (preference: Preferences) => void;
  22. private state: Preferences = reactive<Preferences>({
  23. ...this.loadPreferences(),
  24. });
  25. constructor() {
  26. this.cache = new StorageManager();
  27. // 避免频繁的操作缓存
  28. this.savePreferences = useDebounceFn(
  29. (preference: Preferences) => this._savePreferences(preference),
  30. 150,
  31. );
  32. }
  33. /**
  34. * 保存偏好设置
  35. * @param {Preferences} preference - 需要保存的偏好设置
  36. */
  37. private _savePreferences(preference: Preferences) {
  38. this.cache?.setItem(STORAGE_KEY, preference);
  39. this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale);
  40. this.cache?.setItem(STORAGE_KEY_THEME, preference.theme.mode);
  41. }
  42. /**
  43. * 处理更新的键值
  44. * 根据更新的键值执行相应的操作。
  45. * @param {DeepPartial<Preferences>} updates - 部分更新的偏好设置
  46. */
  47. private handleUpdates(updates: DeepPartial<Preferences>) {
  48. const themeUpdates = updates.theme || {};
  49. const appUpdates = updates.app || {};
  50. if (themeUpdates && Object.keys(themeUpdates).length > 0) {
  51. updateCSSVariables(this.state);
  52. }
  53. if (
  54. Reflect.has(appUpdates, 'colorGrayMode') ||
  55. Reflect.has(appUpdates, 'colorWeakMode')
  56. ) {
  57. this.updateColorMode(this.state);
  58. }
  59. }
  60. private initPlatform() {
  61. const dom = document.documentElement;
  62. dom.dataset.platform = isMacOs() ? 'macOs' : 'window';
  63. }
  64. /**
  65. * 从缓存中加载偏好设置。如果缓存中没有找到对应的偏好设置,则返回默认偏好设置。
  66. */
  67. private loadCachedPreferences() {
  68. return this.cache?.getItem<Preferences>(STORAGE_KEY);
  69. }
  70. /**
  71. * 加载偏好设置
  72. * @returns {Preferences} 加载的偏好设置
  73. */
  74. private loadPreferences(): Preferences {
  75. return this.loadCachedPreferences() || { ...defaultPreferences };
  76. }
  77. /**
  78. * 监听状态和系统偏好设置的变化。
  79. */
  80. private setupWatcher() {
  81. if (this.isInitialized) {
  82. return;
  83. }
  84. // 监听断点,判断是否移动端
  85. const breakpoints = useBreakpoints(breakpointsTailwind);
  86. const isMobile = breakpoints.smaller('md');
  87. watch(
  88. () => isMobile.value,
  89. (val) => {
  90. this.updatePreferences({
  91. app: { isMobile: val },
  92. });
  93. },
  94. { immediate: true },
  95. );
  96. // 监听系统主题偏好设置变化
  97. window
  98. .matchMedia('(prefers-color-scheme: dark)')
  99. .addEventListener('change', ({ matches: isDark }) => {
  100. this.updatePreferences({
  101. theme: { mode: isDark ? 'dark' : 'light' },
  102. });
  103. });
  104. }
  105. /**
  106. * 更新页面颜色模式(灰色、色弱)
  107. * @param preference
  108. */
  109. private updateColorMode(preference: Preferences) {
  110. if (preference.app) {
  111. const { colorGrayMode, colorWeakMode } = preference.app;
  112. const dom = document.documentElement;
  113. const COLOR_WEAK = 'invert-mode';
  114. const COLOR_GRAY = 'grayscale-mode';
  115. colorWeakMode
  116. ? dom.classList.add(COLOR_WEAK)
  117. : dom.classList.remove(COLOR_WEAK);
  118. colorGrayMode
  119. ? dom.classList.add(COLOR_GRAY)
  120. : dom.classList.remove(COLOR_GRAY);
  121. }
  122. }
  123. clearCache() {
  124. [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => {
  125. this.cache?.removeItem(key);
  126. });
  127. }
  128. public getInitialPreferences() {
  129. return this.initialPreferences;
  130. }
  131. public getPreferences() {
  132. return readonly(this.state);
  133. }
  134. /**
  135. * 覆盖偏好设置
  136. * overrides 要覆盖的偏好设置
  137. * namespace 命名空间
  138. */
  139. public async initPreferences({ namespace, overrides }: InitialOptions) {
  140. // 是否初始化过
  141. if (this.isInitialized) {
  142. return;
  143. }
  144. // 初始化存储管理器
  145. this.cache = new StorageManager({ prefix: namespace });
  146. // 合并初始偏好设置
  147. this.initialPreferences = merge({}, overrides, defaultPreferences);
  148. // 加载并合并当前存储的偏好设置
  149. const mergedPreference = merge(
  150. {},
  151. // overrides,
  152. this.loadCachedPreferences() || {},
  153. this.initialPreferences,
  154. );
  155. // 更新偏好设置
  156. this.updatePreferences(mergedPreference);
  157. this.setupWatcher();
  158. this.initPlatform();
  159. // 标记为已初始化
  160. this.isInitialized = true;
  161. }
  162. /**
  163. * 重置偏好设置
  164. * 偏好设置将被重置为初始值,并从 localStorage 中移除。
  165. *
  166. * @example
  167. * 假设 initialPreferences 为 { theme: 'light', language: 'en' }
  168. * 当前 state 为 { theme: 'dark', language: 'fr' }
  169. * this.resetPreferences();
  170. * 调用后,state 将被重置为 { theme: 'light', language: 'en' }
  171. * 并且 localStorage 中的对应项将被移除
  172. */
  173. resetPreferences() {
  174. // 将状态重置为初始偏好设置
  175. Object.assign(this.state, this.initialPreferences);
  176. // 保存重置后的偏好设置
  177. this.savePreferences(this.state);
  178. // 从存储中移除偏好设置项
  179. [STORAGE_KEY, STORAGE_KEY_THEME, STORAGE_KEY_LOCALE].forEach((key) => {
  180. this.cache?.removeItem(key);
  181. });
  182. this.updatePreferences(this.state);
  183. }
  184. /**
  185. * 更新偏好设置
  186. * @param updates - 要更新的偏好设置
  187. */
  188. public updatePreferences(updates: DeepPartial<Preferences>) {
  189. const mergedState = merge({}, updates, markRaw(this.state));
  190. Object.assign(this.state, mergedState);
  191. // 根据更新的键值执行相应的操作
  192. this.handleUpdates(updates);
  193. this.savePreferences(this.state);
  194. }
  195. }
  196. const preferencesManager = new PreferenceManager();
  197. export { PreferenceManager, preferencesManager };