index.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import { computed, ref } from "vue";
  2. import uniTheme from "@/theme.json";
  3. import { router } from "../router";
  4. import { ctx } from "../ctx";
  5. import { isNull } from "../utils";
  6. // 主题类型定义,仅支持 light 和 dark
  7. type Theme = "light" | "dark";
  8. // 是否为自动主题模式(跟随系统)
  9. export const isAuto = ref(true);
  10. /**
  11. * 获取页面样式
  12. * @param key 样式 key
  13. * @returns 样式值
  14. */
  15. export function getStyle(key: string): string | null {
  16. // 页面配置
  17. const style = router.route()?.style;
  18. // 页面配置 key 映射
  19. const names = {
  20. bgColor: "backgroundColor",
  21. bgContentColor: "backgroundColorContent",
  22. navBgColor: "navigationBarBackgroundColor",
  23. navTextStyle: "navigationBarTextStyle"
  24. };
  25. // 如果页面配置存在,则使用页面配置
  26. if (style != null) {
  27. if (names[key] != null) {
  28. const val = style[names[key]!] as string | null;
  29. if (val != null) {
  30. return val;
  31. }
  32. }
  33. }
  34. return null;
  35. }
  36. /**
  37. * 获取颜色
  38. * @param name 颜色名称
  39. * @returns 颜色值
  40. */
  41. export const getColor = (name: string) => {
  42. if (isNull(ctx.color)) {
  43. return "";
  44. }
  45. return ctx.color[name] as string;
  46. };
  47. /**
  48. * 获取 uniapp 主题配置
  49. */
  50. export function getConfig(key: string): string {
  51. // 主题配置
  52. const themeVal = ((isDark.value ? uniTheme.dark : uniTheme.light) as UTSJSONObject)[key] as
  53. | string
  54. | null;
  55. // 页面样式
  56. const styleVal = getStyle(key);
  57. return styleVal ?? themeVal ?? "";
  58. }
  59. /**
  60. * 获取当前主题
  61. * APP 下优先获取 appTheme,若为 auto 则跟随系统 osTheme
  62. * H5/小程序下优先获取 hostTheme,否则默认为 light
  63. */
  64. const getTheme = () => {
  65. let value: string | null;
  66. value = "light";
  67. return value as Theme;
  68. };
  69. // 当前主题响应式变量
  70. export const theme = ref<Theme>(getTheme());
  71. /**
  72. * 是否为暗色模式
  73. */
  74. export const isDark = computed(() => {
  75. return theme.value == "dark";
  76. });
  77. /**
  78. * 切换自动主题模式(仅 APP 有效)
  79. */
  80. export const setIsAuto = () => {
  81. // #ifdef APP
  82. isAuto.value = !isAuto.value;
  83. if (isAuto.value) {
  84. // 设置为自动主题,跟随系统
  85. uni.setAppTheme({
  86. theme: "auto"
  87. });
  88. } else {
  89. // 关闭自动,使用当前 theme
  90. setTheme(theme.value);
  91. }
  92. // #endif
  93. };
  94. /**
  95. * 设置主题
  96. * @param value 主题值("light" | "dark")
  97. */
  98. export const setTheme = (value: Theme) => {
  99. // 如果当前主题与目标主题一致,则不做处理
  100. if (theme.value == value) return;
  101. // 关闭自动主题
  102. isAuto.value = false;
  103. // #ifdef APP
  104. uni.setAppTheme({
  105. theme: value,
  106. success: () => {
  107. // 设置成功后更新 theme
  108. theme.value = value;
  109. }
  110. });
  111. // #endif
  112. // #ifndef APP
  113. theme.value = value;
  114. // #endif
  115. // #ifdef H5
  116. setH5();
  117. // #endif
  118. };
  119. // 设置 H5 下的主题色
  120. export const setH5 = () => {
  121. const bgContentColor = getConfig("bgContentColor");
  122. const tabBgColor = getConfig("tabBgColor");
  123. const navBgColor = getConfig("navBgColor");
  124. const navTextStyle = getConfig("navTextStyle");
  125. // 设置主题类
  126. document.documentElement.classList.toggle("dark", isDark.value);
  127. // 设置背景色
  128. document.body.style.setProperty("--background-color-content", bgContentColor);
  129. // 设置 tabbar 背景色
  130. const tabbar = document.querySelector(".uni-tabbar");
  131. if (tabbar) {
  132. (tabbar as HTMLElement).style.backgroundColor = tabBgColor;
  133. }
  134. // 设置页面头部背景色和文字颜色
  135. const pageHead = document.querySelector(".uni-page-head");
  136. if (pageHead) {
  137. (pageHead as HTMLElement).style.backgroundColor = navBgColor;
  138. (pageHead as HTMLElement).style.color = navTextStyle;
  139. }
  140. // 设置页面头部按钮路径颜色
  141. const pageHeadBtnPath = document.querySelector(".uni-page-head-btn path");
  142. if (pageHeadBtnPath) {
  143. (pageHeadBtnPath as HTMLElement).style.fill = navTextStyle;
  144. }
  145. // 发送主题变化消息
  146. window.parent.postMessage(
  147. {
  148. type: "theme-change",
  149. isDark: isDark.value
  150. },
  151. "*"
  152. );
  153. };
  154. /**
  155. * 切换主题
  156. */
  157. export const toggleTheme = () => {
  158. if (isDark.value) {
  159. setTheme("light");
  160. } else {
  161. setTheme("dark");
  162. }
  163. };
  164. /**
  165. * 初始化主题监听
  166. * APP 下监听系统主题和 App 主题变化
  167. * H5/小程序下监听 hostTheme 变化
  168. */
  169. export const initTheme = () => {
  170. // #ifdef APP-ANDROID || APP-IOS
  171. uni.onOsThemeChange((res) => {
  172. if (isAuto.value) {
  173. setTimeout(() => {
  174. uni.setAppTheme({
  175. theme: res.osTheme,
  176. success: () => {
  177. theme.value = res.osTheme;
  178. }
  179. });
  180. }, 100);
  181. }
  182. });
  183. // 监听 App 主题变化
  184. uni.onAppThemeChange((res) => {
  185. theme.value = res.appTheme;
  186. });
  187. // #endif
  188. // #ifdef MP
  189. uni.onHostThemeChange((res) => {
  190. setTheme(res.hostTheme);
  191. });
  192. // #endif
  193. // #ifdef H5
  194. // 监听父窗口发送的主题变化消息
  195. // [BUG] uni.onHostThemeChange 打包会丢失
  196. // uni.onHostThemeChange((res) => {
  197. // setTheme(res.hostTheme);
  198. // });
  199. window.addEventListener("message", (e) => {
  200. if (e.data?.type == "theme-change") {
  201. setTheme(e.data.isDark ? "dark" : "light");
  202. }
  203. });
  204. // #endif
  205. };