256 lines
5.3 KiB
TypeScript
256 lines
5.3 KiB
TypeScript
import { computed, ref } from "vue";
|
||
import uniTheme from "@/theme.json";
|
||
import { router } from "../router";
|
||
import { ctx } from "../ctx";
|
||
import { isNull } from "../utils";
|
||
|
||
// 主题类型定义,仅支持 light 和 dark
|
||
type Theme = "light" | "dark";
|
||
|
||
// 是否为自动主题模式(跟随系统)
|
||
export const isAuto = ref(true);
|
||
|
||
/**
|
||
* 获取页面样式
|
||
* @param key 样式 key
|
||
* @returns 样式值
|
||
*/
|
||
export function getStyle(key: string): string | null {
|
||
// 页面配置
|
||
const style = router.route()?.style;
|
||
|
||
// 页面配置 key 映射
|
||
const names = {
|
||
bgColor: "backgroundColor",
|
||
bgContentColor: "backgroundColorContent",
|
||
navBgColor: "navigationBarBackgroundColor",
|
||
navTextStyle: "navigationBarTextStyle"
|
||
};
|
||
|
||
// 如果页面配置存在,则使用页面配置
|
||
if (style != null) {
|
||
if (names[key] != null) {
|
||
const val = style[names[key]!] as string | null;
|
||
|
||
if (val != null) {
|
||
return val;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取颜色
|
||
* @param name 颜色名称
|
||
* @returns 颜色值
|
||
*/
|
||
export const getColor = (name: string) => {
|
||
if (isNull(ctx.color)) {
|
||
return "";
|
||
}
|
||
|
||
return ctx.color[name] as string;
|
||
};
|
||
|
||
/**
|
||
* 获取 uniapp 主题配置
|
||
*/
|
||
export function getConfig(key: string): string {
|
||
// 主题配置
|
||
const themeVal = ((isDark.value ? uniTheme.dark : uniTheme.light) as UTSJSONObject)[key] as
|
||
| string
|
||
| null;
|
||
|
||
// 页面样式
|
||
const styleVal = getStyle(key);
|
||
|
||
return styleVal ?? themeVal ?? "";
|
||
}
|
||
|
||
/**
|
||
* 获取当前主题
|
||
* APP 下优先获取 appTheme,若为 auto 则跟随系统 osTheme
|
||
* H5/小程序下优先获取 hostTheme,否则默认为 light
|
||
*/
|
||
const getTheme = () => {
|
||
let value: string | null;
|
||
|
||
// #ifdef APP
|
||
const appInfo = uni.getAppBaseInfo();
|
||
// @ts-ignore
|
||
const appTheme = appInfo.appTheme as string;
|
||
// Use fallback or specific API for osTheme if possible, otherwise getSystemInfoSync is the only way for osTheme currently
|
||
const sysInfo = uni.getSystemInfoSync();
|
||
const osTheme = sysInfo.osTheme!;
|
||
|
||
// 如果 appTheme 为 auto,则跟随系统主题,否则使用 appTheme
|
||
value = appTheme == "auto" ? osTheme : appTheme;
|
||
isAuto.value = appTheme == "auto";
|
||
// #endif
|
||
|
||
// #ifdef H5 || MP
|
||
const hostTheme = uni.getAppBaseInfo().hostTheme;
|
||
if (hostTheme) {
|
||
// 如果有 hostTheme,则使用 hostTheme
|
||
value = hostTheme;
|
||
} else {
|
||
// 默认使用 light 主题
|
||
value = "light";
|
||
}
|
||
// #endif
|
||
|
||
return value as Theme;
|
||
};
|
||
|
||
// 当前主题响应式变量
|
||
export const theme = ref<Theme>(getTheme());
|
||
|
||
/**
|
||
* 是否为暗色模式
|
||
*/
|
||
export const isDark = computed(() => {
|
||
return theme.value == "dark";
|
||
});
|
||
|
||
/**
|
||
* 切换自动主题模式(仅 APP 有效)
|
||
*/
|
||
export const setIsAuto = () => {
|
||
// #ifdef APP
|
||
isAuto.value = !isAuto.value;
|
||
|
||
if (isAuto.value) {
|
||
// 设置为自动主题,跟随系统
|
||
uni.setAppTheme({
|
||
theme: "auto"
|
||
});
|
||
} else {
|
||
// 关闭自动,使用当前 theme
|
||
setTheme(theme.value);
|
||
}
|
||
// #endif
|
||
};
|
||
|
||
/**
|
||
* 设置主题
|
||
* @param value 主题值("light" | "dark")
|
||
*/
|
||
export const setTheme = (value: Theme) => {
|
||
// 如果当前主题与目标主题一致,则不做处理
|
||
if (theme.value == value) return;
|
||
|
||
// 关闭自动主题
|
||
isAuto.value = false;
|
||
|
||
// #ifdef APP
|
||
uni.setAppTheme({
|
||
theme: value,
|
||
success: () => {
|
||
// 设置成功后更新 theme
|
||
theme.value = value;
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifndef APP
|
||
theme.value = value;
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
setH5();
|
||
// #endif
|
||
};
|
||
|
||
// 设置 H5 下的主题色
|
||
export const setH5 = () => {
|
||
const bgContentColor = getConfig("bgContentColor");
|
||
const tabBgColor = getConfig("tabBgColor");
|
||
const navBgColor = getConfig("navBgColor");
|
||
const navTextStyle = getConfig("navTextStyle");
|
||
|
||
document.body.style.setProperty("--background-color-content", bgContentColor);
|
||
|
||
const tabbar = document.querySelector(".uni-tabbar");
|
||
if (tabbar) {
|
||
(tabbar as HTMLElement).style.backgroundColor = tabBgColor;
|
||
}
|
||
|
||
const pageHead = document.querySelector(".uni-page-head");
|
||
if (pageHead) {
|
||
(pageHead as HTMLElement).style.backgroundColor = navBgColor;
|
||
(pageHead as HTMLElement).style.color = navTextStyle;
|
||
}
|
||
|
||
const pageHeadBtnPath = document.querySelector(".uni-page-head-btn path");
|
||
if (pageHeadBtnPath) {
|
||
(pageHeadBtnPath as HTMLElement).style.fill = navTextStyle;
|
||
}
|
||
|
||
window.parent.postMessage(
|
||
{
|
||
type: "theme-change",
|
||
isDark: isDark.value
|
||
},
|
||
"*"
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 切换主题
|
||
*/
|
||
export const toggleTheme = () => {
|
||
if (isDark.value) {
|
||
setTheme("light");
|
||
} else {
|
||
setTheme("dark");
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 初始化主题监听
|
||
* APP 下监听系统主题和 App 主题变化
|
||
* H5/小程序下监听 hostTheme 变化
|
||
*/
|
||
export const initTheme = () => {
|
||
// #ifdef APP-ANDROID || APP-IOS
|
||
uni.onOsThemeChange((res) => {
|
||
if (isAuto.value) {
|
||
setTimeout(() => {
|
||
uni.setAppTheme({
|
||
theme: res.osTheme,
|
||
success: () => {
|
||
theme.value = res.osTheme;
|
||
}
|
||
});
|
||
}, 100);
|
||
}
|
||
});
|
||
|
||
// 监听 App 主题变化
|
||
uni.onAppThemeChange((res) => {
|
||
theme.value = res.appTheme;
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef MP
|
||
uni.onHostThemeChange((res) => {
|
||
setTheme(res.hostTheme);
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
// 监听父窗口发送的主题变化消息
|
||
// [BUG] uni.onHostThemeChange 打包会丢失
|
||
// uni.onHostThemeChange((res) => {
|
||
// setTheme(res.hostTheme);
|
||
// });
|
||
window.addEventListener("message", (e) => {
|
||
if (e.data?.type == "theme-change") {
|
||
setTheme(e.data.isDark ? "dark" : "light");
|
||
}
|
||
});
|
||
// #endif
|
||
};
|