190 lines
5.7 KiB
Vue
190 lines
5.7 KiB
Vue
|
|
<template>
|
|||
|
|
<view :class="['u-config-provider', `u-theme-${darkMode}`, customClass]" :style="mergedThemeStyle">
|
|||
|
|
<slot />
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts">
|
|||
|
|
/**
|
|||
|
|
* u-config-provider
|
|||
|
|
*
|
|||
|
|
* 说明(简要):
|
|||
|
|
* - 初始化场景(推荐在应用入口执行):使用 `useTheme().initTheme(themes)` 进行一次性初始化与全局设置(例如在 `main.ts` 或 `App.vue` 中)。
|
|||
|
|
* - 组件/页面场景(推荐):使用 `useTheme()` 组合式函数在组件内部读取响应式 `currentTheme`、`themes`、`darkMode` 并通过 `setTheme()` 和 `setDarkMode()` 切换主题/模式。
|
|||
|
|
*
|
|||
|
|
* 该组件的行为:
|
|||
|
|
* - 如果在挂载时传入 `themes`,会调用 `configProvider.initTheme(themes)`
|
|||
|
|
* - 如果传入 `currentTheme`,会优先设置当前主题
|
|||
|
|
* - 如果传入 `darkMode`,会设置当前的暗黑模式状态,同时在 document 上添加 `u-theme-dark` 或 `u-theme-light` 类名
|
|||
|
|
*
|
|||
|
|
* 详尽说明请参考:`docs/config-provider-usage.md`
|
|||
|
|
*/
|
|||
|
|
export default {
|
|||
|
|
name: 'u-config-provider',
|
|||
|
|
options: {
|
|||
|
|
addGlobalClass: true,
|
|||
|
|
// #ifndef MP-TOUTIAO
|
|||
|
|
virtualHost: true,
|
|||
|
|
// #endif
|
|||
|
|
styleIsolation: 'shared'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script lang="ts" setup>
|
|||
|
|
import { computed, watch, onMounted } from 'vue';
|
|||
|
|
import { ConfigProviderProps } from './types';
|
|||
|
|
import { $u, configProvider } from '../../libs';
|
|||
|
|
import { useTheme } from '../../libs/hooks/useTheme';
|
|||
|
|
import { useLocale } from '../../libs/hooks/useLocale';
|
|||
|
|
|
|||
|
|
const props = defineProps(ConfigProviderProps);
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
'theme-change': [themeName: string];
|
|||
|
|
'mode-change': [mode: 'light' | 'dark'];
|
|||
|
|
}>();
|
|||
|
|
|
|||
|
|
// 计算当前的主题模式(亮色/暗黑)
|
|||
|
|
const darkMode = computed(() => (configProvider.isInDarkMode() ? 'dark' : 'light'));
|
|||
|
|
|
|||
|
|
const bootstrapTheme = () => {
|
|||
|
|
// 如果已经初始化过主题,不再重复初始化,只更新 props 相关配置
|
|||
|
|
const existingThemes = configProvider.getThemes();
|
|||
|
|
if (existingThemes.length > 0) {
|
|||
|
|
// 已初始化,只更新当前主题和暗黑模式
|
|||
|
|
if (props.currentTheme) {
|
|||
|
|
configProvider.setTheme(props.currentTheme as string);
|
|||
|
|
}
|
|||
|
|
if (props.darkMode) {
|
|||
|
|
configProvider.setDarkMode(props.darkMode);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 未初始化,进行初始化
|
|||
|
|
if (props.themes && props.themes.length) {
|
|||
|
|
configProvider.initTheme(props.themes, props.currentTheme as any);
|
|||
|
|
} else {
|
|||
|
|
// 使用 useTheme 的 initTheme,它会处理默认主题
|
|||
|
|
const { initTheme } = useTheme();
|
|||
|
|
initTheme(undefined, props.currentTheme as any);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (props.currentTheme) {
|
|||
|
|
configProvider.setTheme(props.currentTheme as string);
|
|||
|
|
}
|
|||
|
|
if (props.darkMode) {
|
|||
|
|
configProvider.setDarkMode(props.darkMode);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const bootstrapLocale = () => {
|
|||
|
|
// 初始化国际化
|
|||
|
|
try {
|
|||
|
|
const { initLocales, setLocale, getLocales } = useLocale();
|
|||
|
|
const existingLocales = getLocales();
|
|||
|
|
if (existingLocales.length > 0) {
|
|||
|
|
if (props.currentLocale) {
|
|||
|
|
setLocale(props.currentLocale as string);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (props.locales && props.locales.length) {
|
|||
|
|
initLocales(props.locales, props.currentLocale as any);
|
|||
|
|
} else {
|
|||
|
|
initLocales(undefined, props.currentLocale as any);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn('[u-config-provider] init locales failed', e);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 当传入自定义 themes 时,初始化全局 configProvider(覆盖已有)
|
|||
|
|
onMounted(() => {
|
|||
|
|
bootstrapTheme();
|
|||
|
|
bootstrapLocale();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听外部 props 变化(如果上层修改 prop)
|
|||
|
|
watch(
|
|||
|
|
() => props.themes,
|
|||
|
|
val => {
|
|||
|
|
// 如果传入的 themes 来自于 configProvider 自身(常见于模板中使用 useTheme() 直接透传),
|
|||
|
|
// 那么对其做深度监听会在我们内部更新主题对象时触发该回调,进而再次调用 init 导致循环更新。
|
|||
|
|
// 为避免该情况,先做简单保护:当传入对象正是 configProvider.themesRef.value 时不重复初始化。
|
|||
|
|
if (val && val.length && val !== configProvider.themesRef.value) {
|
|||
|
|
configProvider.initTheme(val, props.currentTheme as any);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{ deep: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.currentTheme,
|
|||
|
|
val => {
|
|||
|
|
if (val) {
|
|||
|
|
configProvider.setTheme(val);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.darkMode,
|
|||
|
|
val => {
|
|||
|
|
if (val && val !== configProvider.getDarkMode()) {
|
|||
|
|
configProvider.setDarkMode(val);
|
|||
|
|
emit('mode-change', darkMode.value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 监听 locales prop 变化
|
|||
|
|
watch(
|
|||
|
|
() => props.locales,
|
|||
|
|
val => {
|
|||
|
|
if (val && val.length) {
|
|||
|
|
const { initLocales } = useLocale();
|
|||
|
|
initLocales(val, props.currentLocale as any);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{ deep: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.currentLocale,
|
|||
|
|
val => {
|
|||
|
|
if (val) {
|
|||
|
|
const { setLocale } = useLocale();
|
|||
|
|
setLocale(val);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 监听全局主题变更并触发事件
|
|||
|
|
watch(
|
|||
|
|
() => configProvider.currentThemeRef.value,
|
|||
|
|
(val, oldVal) => {
|
|||
|
|
if (val && val.name !== (oldVal as any)?.name) {
|
|||
|
|
emit('theme-change', val.name);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{ immediate: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 监听暗黑模式变更并触发事件
|
|||
|
|
watch(
|
|||
|
|
() => configProvider.darkModeRef.value,
|
|||
|
|
() => {
|
|||
|
|
emit('mode-change', darkMode.value);
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 计算合并样式(作为局部 fallback),configProvider 已经会把变量注入到 document 上
|
|||
|
|
const mergedThemeStyle = computed(() => {
|
|||
|
|
return $u.toStyle(configProvider.cssVarsRef.value, props.customStyle);
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped></style>
|