Files
vue-vben-admin/packages/preference/src/preference.ts
2024-05-19 21:20:42 +08:00

199 lines
5.1 KiB
TypeScript

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<Preference> = ref(defaultPreference);
let preferenceCache: PreferenceCacheType;
/**
* 是否监听过系统设置变化
*/
let isRegisterListen = false;
function updatePreference(key: keyof Preference, value: boolean | string): void;
function updatePreference(
preference: DeepPartial<Preference>,
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<Preference>,
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,
};