This commit is contained in:
2026-03-25 14:54:15 +08:00
commit ab4646b43a
827 changed files with 123812 additions and 0 deletions
@@ -0,0 +1,45 @@
import type { ExtractPropTypes, PropType } from 'vue';
import { baseProps } from '../common/props';
import { config, configProvider } from '../../libs';
import type { Theme } from '../../types/global';
export const ConfigProviderProps = {
...baseProps,
/**
* 主题风格,可选值:
* - 'light': 强制亮色模式(默认)
* - 'dark': 强制暗黑模式
* - 'auto': 自动跟随系统设置
*/
darkMode: {
type: String as PropType<'light' | 'dark' | 'auto'>,
default: () => config.defaultDarkMode
},
/**
* 当前主题名称(用于多主题切换)
*/
currentTheme: {
type: String,
default: () => configProvider.getCurrentTheme()?.name ?? config.defaultTheme
},
/**
* 自定义主题列表
*/
themes: {
type: Array as PropType<Theme[]>,
default: () => configProvider.getThemes()
},
/**
* 国际化配置:传入 locale 列表(数组)并可指定默认语言 currentLocale
*/
locales: {
type: Array as PropType<any[]>,
default: () => []
},
currentLocale: {
type: String,
default: () => configProvider.getCurrentLocale()?.name ?? ''
}
};
export type ConfigProviderProps = ExtractPropTypes<typeof ConfigProviderProps>;
@@ -0,0 +1,189 @@
<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>