refactor: 主题设置重构优化

This commit is contained in:
ray
2025-07-30 00:06:50 +08:00
parent 805078fd1a
commit a95fbd6a54
12 changed files with 234 additions and 209 deletions

1
components.d.ts vendored
View File

@@ -35,6 +35,7 @@ declare module 'vue' {
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
WdRadio: typeof import('wot-design-uni/components/wd-radio/wd-radio.vue')['default']
WdRadioGroup: typeof import('wot-design-uni/components/wd-radio-group/wd-radio-group.vue')['default']
WdStatusTip: typeof import('wot-design-uni/components/wd-status-tip/wd-status-tip.vue')['default']
WdSwiper: typeof import('wot-design-uni/components/wd-swiper/wd-swiper.vue')['default']
WdSwitch: typeof import('wot-design-uni/components/wd-switch/wd-switch.vue')['default']
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']

View File

@@ -1,13 +1,8 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
import { useTheme } from "@/composables/useTheme";
const { initTheme } = useTheme();
onLaunch(() => {
console.log("App Launch");
// 初始化主题
initTheme();
});
onShow(() => {

View File

@@ -0,0 +1,46 @@
import type { ConfigProviderThemeVars } from "wot-design-uni";
/**
* 主题色选项接口
*/
export interface ThemeColorOption {
name: string;
value: string;
primary: string;
}
/**
* 主题类型
*/
export type ThemeMode = "light" | "dark";
/**
* 主题状态接口
*/
export interface ThemeState {
theme: ThemeMode;
followSystem: boolean;
hasUserSet: boolean;
currentThemeColor: ThemeColorOption;
themeVars: ConfigProviderThemeVars;
}
/**
* 系统主题状态接口(简化版)
*/
export interface SystemThemeState {
theme: ThemeMode;
themeVars: ConfigProviderThemeVars;
}
/**
* 预定义的主题色选项
*/
export const themeColorOptions: ThemeColorOption[] = [
{ name: "默认蓝", value: "blue", primary: "#4D7FFF" },
{ name: "活力橙", value: "orange", primary: "#FF7D00" },
{ name: "薄荷绿", value: "green", primary: "#07C160" },
{ name: "樱花粉", value: "pink", primary: "#FF69B4" },
{ name: "紫罗兰", value: "purple", primary: "#8A2BE2" },
{ name: "朱砂红", value: "red", primary: "#FF4757" },
];

View File

@@ -1,178 +1,132 @@
import type { ConfigProviderThemeVars } from "wot-design-uni";
import { defineStore } from "pinia";
import type { ThemeColorOption, ThemeMode, ThemeState } from "@/composables/types/theme";
import { themeColorOptions } from "@/composables/types/theme";
// 定义主题色选项
export interface ThemeColorOption {
name: string;
value: string;
primary: string;
}
/**
* 完整版主题状态管理
* 支持手动切换主题、主题色选择、跟随系统主题等完整功能
*/
export const useThemeStore = defineStore("theme", {
state: (): ThemeState => ({
theme: "light",
followSystem: true, // 是否跟随系统主题
hasUserSet: false, // 用户是否手动设置过主题
currentThemeColor: themeColorOptions[0],
themeVars: {
darkBackground: "#0f0f0f",
darkBackground2: "#1a1a1a",
darkBackground3: "#242424",
darkBackground4: "#2f2f2f",
darkBackground5: "#3d3d3d",
darkBackground6: "#4a4a4a",
darkBackground7: "#606060",
darkColor: "#ffffff",
darkColor2: "#e0e0e0",
darkColor3: "#a0a0a0",
colorTheme: themeColorOptions[0].primary,
},
}),
// 预定义的主题色选项
export const themeColorOptions: ThemeColorOption[] = [
{ name: "默认蓝", value: "blue", primary: "#4D7FFF" },
{ name: "活力橙", value: "orange", primary: "#FF7D00" },
{ name: "薄荷绿", value: "green", primary: "#07C160" },
{ name: "樱花粉", value: "pink", primary: "#FF69B4" },
{ name: "紫罗兰", value: "purple", primary: "#8A2BE2" },
{ name: "朱砂红", value: "red", primary: "#FF4757" },
];
getters: {
isDark: (state) => state.theme === "dark",
},
export function useTheme() {
// 状态定义
const theme = ref<"light" | "dark">("light");
const followSystem = ref(true); // 是否跟随系统主题
const hasUserSet = ref(false); // 用户是否手动设置过主题
const currentThemeColor = ref<ThemeColorOption>(themeColorOptions[0]);
const showThemeColorSheet = ref(false);
actions: {
/**
* 手动切换主题
* @param mode 指定主题模式,不传则自动切换
*/
toggleTheme(mode?: ThemeMode) {
this.theme = mode || (this.theme === "light" ? "dark" : "light");
this.hasUserSet = true; // 标记用户已手动设置
this.followSystem = false; // 不再跟随系统
this.setNavigationBarColor();
},
const themeVars = reactive<ConfigProviderThemeVars>({
darkBackground: "#0f0f0f",
darkBackground2: "#1a1a1a",
darkBackground3: "#242424",
darkBackground4: "#2f2f2f",
darkBackground5: "#3d3d3d",
darkBackground6: "#4a4a4a",
darkBackground7: "#606060",
darkColor: "#ffffff",
darkColor2: "#e0e0e0",
darkColor3: "#a0a0a0",
colorTheme: themeColorOptions[0].primary,
});
// 计算属性
const isDark = computed(() => theme.value === "dark");
/* 手动切换主题 */
function toggleTheme(mode?: "light" | "dark") {
theme.value = mode || (theme.value === "light" ? "dark" : "light");
hasUserSet.value = true; // 标记用户已手动设置
followSystem.value = false; // 不再跟随系统
setNavigationBarColor();
}
/* 设置是否跟随系统主题 */
function setFollowSystem(follow: boolean) {
followSystem.value = follow;
if (follow) {
hasUserSet.value = false;
initTheme(); // 重新获取系统主题
}
}
/* 设置导航栏颜色 */
function setNavigationBarColor() {
uni.setNavigationBarColor({
frontColor: theme.value === "light" ? "#000000" : "#ffffff",
backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
});
}
/* 设置主题色 */
function setCurrentThemeColor(color: ThemeColorOption) {
currentThemeColor.value = color;
themeVars.colorTheme = color.primary;
}
/* 获取系统主题 */
function getSystemTheme(): "light" | "dark" {
try {
// #ifdef MP-WEIXIN
// 微信小程序使用 getAppBaseInfo
const appBaseInfo = uni.getAppBaseInfo();
if (appBaseInfo && appBaseInfo.theme) {
return appBaseInfo.theme as "light" | "dark";
/**
* 设置是否跟随系统主题
* @param follow 是否跟随系统
*/
setFollowSystem(follow: boolean) {
this.followSystem = follow;
if (follow) {
this.hasUserSet = false;
this.initTheme(); // 重新获取系统主题
}
// #endif
},
// #ifndef MP-WEIXIN
// 其他平台使用 getSystemInfoSync
const systemInfo = uni.getSystemInfoSync();
if (systemInfo && systemInfo.theme) {
return systemInfo.theme as "light" | "dark";
}
// #endif
} catch (error) {
console.warn("获取系统主题失败:", error);
}
return "light"; // 默认返回 light
}
/* 初始化主题 */
function initTheme() {
// 如果用户已手动设置且不跟随系统,保持当前主题
if (hasUserSet.value && !followSystem.value) {
console.log("使用用户设置的主题:", theme.value);
setNavigationBarColor();
return;
}
// 获取系统主题
const systemTheme = getSystemTheme();
// 如果是首次启动或跟随系统,使用系统主题
if (!hasUserSet.value || followSystem.value) {
theme.value = systemTheme;
if (!hasUserSet.value) {
followSystem.value = true;
console.log("首次启动,使用系统主题:", theme.value);
} else {
console.log("跟随系统主题:", theme.value);
}
}
setNavigationBarColor();
}
/* 打开主题色选择 */
function openThemeColorPicker() {
showThemeColorSheet.value = true;
}
/* 关闭主题色选择 */
function closeThemeColorPicker() {
showThemeColorSheet.value = false;
}
/* 选择主题色 */
function selectThemeColor(option: ThemeColorOption) {
setCurrentThemeColor(option);
closeThemeColorPicker();
}
// 检查函数是否存在的工具函数
const isFunction = (fn: any): boolean => typeof fn === "function";
onBeforeMount(() => {
initTheme();
if (isFunction(uni.onThemeChange)) {
uni.onThemeChange((res) => {
toggleTheme(res.theme);
/**
* 设置导航栏颜色
*/
setNavigationBarColor() {
uni.setNavigationBarColor({
frontColor: this.theme === "light" ? "#000000" : "#ffffff",
backgroundColor: this.theme === "light" ? "#ffffff" : "#000000",
});
}
});
},
onUnmounted(() => {
if (isFunction(uni.offThemeChange)) {
uni.offThemeChange((res) => {
toggleTheme(res.theme);
});
}
});
/**
* 设置主题色
* @param color 主题色选项
*/
setCurrentThemeColor(color: ThemeColorOption) {
this.currentThemeColor = color;
this.themeVars.colorTheme = color.primary;
},
return {
theme: computed(() => theme.value),
isDark,
followSystem: computed(() => followSystem.value),
hasUserSet: computed(() => hasUserSet.value),
currentThemeColor: computed(() => currentThemeColor.value),
showThemeColorSheet,
themeVars,
themeColorOptions,
initTheme,
toggleTheme,
setFollowSystem,
openThemeColorPicker,
closeThemeColorPicker,
selectThemeColor,
};
}
/**
* 获取系统主题
* @returns 系统主题模式
*/
getSystemTheme(): ThemeMode {
try {
// #ifdef MP-WEIXIN
// 微信小程序使用 getAppBaseInfo
const appBaseInfo = uni.getAppBaseInfo();
if (appBaseInfo && appBaseInfo.theme) {
return appBaseInfo.theme as ThemeMode;
}
// #endif
// #ifndef MP-WEIXIN
// 其他平台使用 getSystemInfoSync
const systemInfo = uni.getSystemInfoSync();
if (systemInfo && systemInfo.theme) {
return systemInfo.theme as ThemeMode;
}
// #endif
} catch (error) {
console.warn("获取系统主题失败:", error);
}
return "light"; // 默认返回 light
},
/**
* 初始化主题
*/
initTheme() {
// 如果用户已手动设置且不跟随系统,保持当前主题
if (this.hasUserSet && !this.followSystem) {
console.log("使用用户设置的主题:", this.theme);
this.setNavigationBarColor();
return;
}
// 获取系统主题
const systemTheme = this.getSystemTheme();
// 如果是首次启动或跟随系统,使用系统主题
if (!this.hasUserSet || this.followSystem) {
this.theme = systemTheme;
if (!this.hasUserSet) {
this.followSystem = true;
console.log("首次启动,使用系统主题:", this.theme);
} else {
console.log("跟随系统主题:", this.theme);
}
}
this.setNavigationBarColor();
},
},
});

View File

@@ -2,5 +2,14 @@
* 常量统一导出
*/
// 存储相关常量
export * from "./storage.constant";
/**
* 存储键常量统一管理
* 包括 localStorage、sessionStorage 等各种存储的键名
*/
// 🔐 用户认证相关
export const ACCESS_TOKEN_KEY = "access_token";
export const REFRESH_TOKEN_KEY = "refresh_token";
// 📊 用户缓存相关
export const USER_INFO_KEY = "user_info";

View File

@@ -1,11 +0,0 @@
/**
* 存储键常量统一管理
* 包括 localStorage、sessionStorage 等各种存储的键名
*/
// 🔐 用户认证相关
export const ACCESS_TOKEN_KEY = "access_token";
export const REFRESH_TOKEN_KEY = "refresh_token";
// 📊 用户缓存相关
export const USER_INFO_KEY = "user_info";

View File

@@ -1,5 +1,9 @@
<script lang="ts" setup>
const { theme, themeVars } = useTheme();
import { useThemeStore } from "@/composables/useTheme";
import { storeToRefs } from "pinia";
const themeStore = useThemeStore();
const { theme, themeVars } = storeToRefs(themeStore);
</script>
<script lang="ts">

View File

@@ -26,13 +26,15 @@
<script setup lang="ts">
import { useRoute } from "vue-router";
import { useTheme } from "@/composables/useTheme";
import { useThemeStore } from "@/composables/useTheme";
import { useTabbar } from "@/composables/useTabbar";
import { useRouter } from "uni-mini-router";
import { storeToRefs } from "pinia";
const router = useRouter();
const route = useRoute();
const { themeVars, theme } = useTheme();
const themeStore = useThemeStore();
const { themeVars, theme } = storeToRefs(themeStore);
const { activeTabbar, getTabbarItemValue, setTabbarItemActive, tabbarList } = useTabbar();
function handleTabbarChange({ value }: { value: string }) {

View File

@@ -126,7 +126,7 @@ import { type LoginData } from "@/api/auth";
import { useUserStore } from "@/store/modules/user.store";
import { useToast } from "wot-design-uni";
import { useWechat } from "@/composables/useWechat";
import { useTheme } from "@/composables/useTheme";
import { useThemeStore } from "@/composables/useTheme";
import { computed, onMounted } from "vue";
const loginFormRef = ref();
@@ -136,10 +136,10 @@ const userStore = useUserStore();
const showPassword = ref(false);
const loginType = ref<"account" | "phone">("account");
const { authState, getLoginCode, getPhoneNumber } = useWechat();
const { theme } = useTheme();
const themeStore = useThemeStore();
// 是否暗黑模式
const isDarkMode = computed(() => theme.value === "dark");
const isDarkMode = computed(() => themeStore.theme === "dark");
// 登录表单数据
const loginFormData = ref<LoginData>({

View File

@@ -162,12 +162,13 @@
import { onShow } from "@dcloudio/uni-app";
import { useToast } from "wot-design-uni";
import { useUserStore } from "@/store/modules/user.store";
import { useTheme } from "@/composables/useTheme";
import { useThemeStore } from "@/composables/useTheme";
import { computed } from "vue";
const toast = useToast();
const userStore = useUserStore();
const { currentThemeColor } = useTheme();
const themeStore = useThemeStore();
const currentThemeColor = computed(() => themeStore.themeVars.colorTheme);
const userInfo = computed(() => userStore.userInfo);
const isLogin = computed(() => !!userInfo.value);
const defaultAvatar = "/static/images/default-avatar.png";

View File

@@ -9,11 +9,11 @@
<!-- 暗黑模式设置 -->
<wd-card class="setting-section">
<view class="section-header">
<wd-icon name="moon" size="20" :color="theme === 'dark' ? '#FFD700' : '#666'" />
<wd-icon name="moon" size="20" :color="isDarkMode ? '#FFD700' : '#666'" />
<text class="section-title">外观模式</text>
</view>
<wd-cell title="暗黑模式" :value="theme === 'dark' ? '已开启' : '已关闭'">
<wd-switch :model-value="theme === 'dark'" @change="handleToggleDarkMode" />
<wd-cell title="暗黑模式" :value="isDarkMode ? '已开启' : '已关闭'">
<wd-switch :model-value="isDarkMode" @change="handleToggleDarkMode" />
</wd-cell>
</wd-card>
@@ -95,6 +95,16 @@
</wd-grid>
</wd-card>
<!-- 跟随系统设置 -->
<wd-card class="setting-section">
<wd-cell title="跟随系统主题" :value="followSystemActive ? '已开启' : '已关闭'">
<wd-switch :model-value="followSystemActive" @change="handleFollowSystemChange" />
</wd-cell>
<view class="follow-system-tip p-3 text-sm text-gray-500">
开启后应用会自动适配系统的深色/浅色模式设置
</view>
</wd-card>
<!-- 操作按钮 -->
<wd-card class="action-section">
<wd-button type="info" size="large" block @click="handleResetTheme">重置为默认主题</wd-button>
@@ -136,9 +146,17 @@
</template>
<script lang="ts" setup>
import { useTheme, themeColorOptions } from "@/composables/useTheme";
import { useThemeStore } from "@/composables/useTheme";
import { themeColorOptions } from "@/composables/types/theme";
import { storeToRefs } from "pinia";
const { theme, themeVars, toggleTheme, selectThemeColor } = useTheme();
const themeStore = useThemeStore();
// 将store中的数据解构为响应式引用
const { themeVars } = storeToRefs(themeStore);
// 创建响应式的计算属性来解决类型问题
const isDarkMode = computed(() => themeStore.theme === "dark");
const followSystemActive = computed(() => themeStore.followSystem);
// 自定义颜色输入
const customColor = ref("");
@@ -146,12 +164,12 @@ const showCustomColorInput = ref(false);
// 当前选中的主题色
const currentThemeColor = computed(() => {
return themeVars.colorTheme || themeColorOptions[0].primary;
return themeVars.value.colorTheme || themeColorOptions[0].primary;
});
// 选择预设颜色
const handleSelectColor = (color: (typeof themeColorOptions)[0]) => {
selectThemeColor(color);
themeStore.setCurrentThemeColor(color);
customColor.value = color.primary;
// 提示
@@ -194,7 +212,7 @@ const applyCustomColor = () => {
primary: color,
};
selectThemeColor(customColorOption);
themeStore.setCurrentThemeColor(customColorOption);
showCustomColorInput.value = false;
// 提示
@@ -212,7 +230,7 @@ const handleResetTheme = () => {
content: "确定要重置为默认主题吗?",
success: (res) => {
if (res.confirm) {
selectThemeColor(themeColorOptions[0]);
themeStore.setCurrentThemeColor(themeColorOptions[0]);
customColor.value = themeColorOptions[0].primary;
uni.showToast({
@@ -227,16 +245,21 @@ const handleResetTheme = () => {
// 切换暗黑模式
const handleToggleDarkMode = () => {
toggleTheme();
themeStore.toggleTheme();
nextTick(() => {
uni.showToast({
title: `已切换到${theme.value === "dark" ? "暗黑" : "浅色"}模式`,
title: `已切换到${isDarkMode.value ? "暗黑" : "浅色"}模式`,
icon: "success",
duration: 1500,
});
});
};
// 处理跟随系统设置的变更
const handleFollowSystemChange = (value: boolean) => {
themeStore.setFollowSystem(value);
};
onLoad(() => {
// 初始化自定义颜色输入框
customColor.value = currentThemeColor.value;
@@ -245,13 +268,15 @@ onLoad(() => {
onMounted(() => {
// 初始化自定义颜色输入框
customColor.value = currentThemeColor.value;
// 初始化主题
themeStore.initTheme();
});
// 页面显示时确保主题色同步
onShow(() => {
customColor.value = currentThemeColor.value;
console.log("主题设置页面显示,当前主题:", {
mode: theme.value,
mode: isDarkMode.value ? "dark" : "light",
color: currentThemeColor.value,
});
});

View File

@@ -143,6 +143,7 @@ declare global {
const useInterceptor: typeof import('@uni-helper/uni-use')['useInterceptor']
const useLink: (typeof import("vue-router"))["useLink"]
const useLoading: typeof import('@uni-helper/uni-use')['useLoading']
const useManualTheme: typeof import('../composables/useTheme')['useManualTheme']
const useMessage: typeof import('wot-design-uni')['useMessage']
const useModal: typeof import('@uni-helper/uni-use')['useModal']
const useModel: typeof import('vue')['useModel']
@@ -296,7 +297,6 @@ declare module 'vue' {
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly store: UnwrapRef<typeof import('../store/index')['store']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly themeColorOptions: UnwrapRef<typeof import('../composables/useTheme')['themeColorOptions']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
@@ -316,7 +316,6 @@ declare module 'vue' {
readonly useStomp: UnwrapRef<typeof import('../composables/useStomp')['useStomp']>
readonly useTabbar: UnwrapRef<typeof import('../composables/useTabbar')['useTabbar']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly useTheme: UnwrapRef<typeof import('../composables/useTheme')['useTheme']>
readonly useThemeStore: UnwrapRef<typeof import('../store/modules/theme.store')['useThemeStore']>
readonly useToast: UnwrapRef<typeof import('wot-design-uni')['useToast']>
readonly useUserStore: UnwrapRef<typeof import('../store/modules/user.store')['useUserStore']>