uniapp+vue3+pinia+pinia-plugin-persistedstate主题管理

10 阅读2分钟

注:当前为我自己项目经验的方案,如果大家有更好的希望可以在评论区告诉我                

安装pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate

配置pinia-plugin-persistedstate
//main.js文件
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
    ...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
    createSSRApp
} from 'vue'
import {
    createPinia
} from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export function createApp() {
    const app = createSSRApp(App)
    const pinia = createPinia()
    pinia.use(piniaPluginPersistedstate)
    app.use(pinia)
    return {
        app,
        pinia
    }
}
// #endif

配置主题管理pinia全局缓存文件
// stores/theme.js
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
export const useThemeStore = defineStore('theme', () => {
    // 当前主题名称
    const themeName = ref('KFC')
    // 添加一个标记,表示主题是否已初始化
    const isThemeInitialized = ref(false)

// 主题配置映射
// --dominant-color:如主背景色、按钮颜色、提示性文字色,金额
// --text-color:标题、正文信息颜色
// --text-secondary:标题、正文信息颜色
// --text-auxiliary:用于辅助、次要级信息颜色
// --bg-color:背景色
// --border-color:边框颜色
// --dividing-line:分割线颜色
// --card-bg:卡片背景色
// --mask-bg:遮罩背景色
// --shadow-color:阴影颜色
const themeConfig = ref({
    KFC: {
        name: 'KFC',
        '--primary-color': '#007aff',
        '--secondary-color': '#5856d6',
        '--success-color': '#4cd964',
        '--warning-color': '#ff9500',
        '--danger-color': '#ff3b30',
        '--dominant-color': '#E40031',
        '--bg-color': '#E40031',
        '--text-color': '#222222',
        '--text-secondary': '#666666',
        '--text-auxiliary': '#999999',
        '--border-color': '#e0e0e0',
        '--card-bg': '#ffffff',
        '--mask-bg': 'rgba(0, 0, 0, 0.4)',
        '--shadow-color': 'rgba(0, 0, 0, 0.1)'
    },
    MCD: {
        name: 'MCD',
        '--dominant-color': '#E40031',
        '--primary-color': '#0a84ff',
        '--secondary-color': '#5e5ce6',
        '--success-color': '#30d158',
        '--warning-color': '#ff9f0a',
        '--danger-color': '#ff453a',
        '--bg-color': '#000000',
        '--text-color': '#ffffff',
        '--text-secondary': '#8e8e93',
        '--border-color': '#38383a',
        '--card-bg': '#1c1c1e',
        '--mask-bg': 'rgba(255, 255, 255, 0.1)',
        '--shadow-color': 'rgba(255, 255, 255, 0.1)'
    },
    LC: {
        name: 'LC',
        '--dominant-color': '#E40031',
        '--primary-color': '#007aff',
        '--secondary-color': '#5856d6',
        '--success-color': '#4cd964',
        '--warning-color': '#ff9500',
        '--danger-color': '#ff3b30',
        '--bg-color': '#E40031',
        '--text-color': '#333333',
        '--text-secondary': '#666666',
        '--border-color': '#e0e0e0',
        '--card-bg': '#ffffff',
        '--mask-bg': 'rgba(0, 0, 0, 0.4)',
        '--shadow-color': 'rgba(0, 0, 0, 0.1)'
    },
    custom: {
        name: 'custom',
        '--primary-color': '#ff6600',
        '--secondary-color': '#ff9966',
        '--success-color': '#67c23a',
        '--warning-color': '#e6a23c',
        '--danger-color': '#f56c6c',
        '--bg-color': '#f9f9f9',
        '--text-color': '#333333',
        '--text-secondary': '#606266',
        '--border-color': '#dcdfe6',
        '--card-bg': '#ffffff',
        '--mask-bg': 'rgba(0, 0, 0, 0.4)',
        '--shadow-color': 'rgba(0, 0, 0, 0.1)'
    }
} as any)

// 当前主题变量
const currentTheme = computed(() => themeConfig.value[themeName.value])

// 主题样式字符串(用于行内样式)
const themeStyleString = computed(() => {
    const vars = currentTheme.value
    let style = ''
    for (const key in vars) {
        if (key.startsWith('--')) {
            style += `${key}: ${vars[key]}; `
        }
    }
    return style
})

// 可用主题列表
const availableThemes = computed(() => Object.keys(themeConfig.value))

// 初始化主题
const initTheme = () => {
    console.log('开始初始化主题...')

    // 方法1:直接从本地存储读取(绕过Pinia持久化)
    const saved = uni.getStorageSync('uni-theme')
    console.log('从uni存储读取的主题:', saved)

    // 方法2:从Pinia持久化读取
    const piniaSaved = uni.getStorageSync('pinia-theme-store')
    console.log('从Pinia存储读取的数据:', piniaSaved)

    if (saved && themeConfig.value[saved]) {
        console.log('使用uni存储的主题:', saved)
        themeName.value = saved
    } else if (piniaSaved) {
        try {
            const parsed = JSON.parse(piniaSaved)
            if (parsed.themeName && themeConfig.value[parsed.themeName]) {
                console.log('使用Pinia存储的主题:', parsed.themeName)
                themeName.value = parsed.themeName
            }
        } catch (e) {
            console.error('解析Pinia存储失败:', e)
        }
    } else {
        console.log('使用默认主题: KFC')
        themeName.value = 'KFC'
    }

    // 应用主题
    applyTheme()
    isThemeInitialized.value = true

    // 监听系统主题变化
    watchSystemTheme()
}

// 应用主题到页面
const applyTheme = () => {
    console.log('应用主题:', themeName.value)
    const theme = currentTheme.value

    if (!theme) {
        console.error('主题配置不存在:', themeName.value)
        return
    }

    // 设置CSS变量(H5端)
    if (typeof document !== 'undefined') {
        const root = document.documentElement
        for (const key in theme) {
            if (key.startsWith('--')) {
                root.style.setProperty(key, theme[key])
            }
        }
        root.setAttribute('data-theme', theme.name)
        console.log('CSS变量已设置')
    }

    // 发送主题变化事件
    uni.$emit('theme:change', theme)
    console.log('主题变化事件已发送:', theme.name)

    // 保存到存储
    uni.setStorageSync('uni-theme', themeName.value)
    console.log('主题已保存到存储:', themeName.value)
}

// 监听系统主题
const watchSystemTheme = () => {
    // H5端监听系统主题
    if (typeof window !== 'undefined' && window.matchMedia) {
        const darkModeMedia = window.matchMedia('(prefers-color-scheme: MCD)')

        const handleThemeChange = (e : any) => {
            if (uni.getStorageSync('uni-theme-follow-system') === true) {
                setTheme(e.matches ? 'dark' : 'KFC')
            }
        }

        darkModeMedia.addEventListener('change', handleThemeChange)
    }

    // App端监听系统主题
    if (uni.getSystemSetting) {
        const systemSetting = uni.getSystemSetting()
        if (systemSetting) {
            // App端逻辑
        }
    }
}

// 设置主题
const setTheme = (name : any) => {
    if (!themeConfig.value[name]) {
        console.warn(`主题 "${name}" 不存在`)
        return false
    }

    themeName.value = name
    applyTheme()
    return true
}

// 切换主题
const toggleTheme = () => {
    const themes = availableThemes.value
    const currentIndex = themes.indexOf(themeName.value)
    const nextIndex = (currentIndex + 1) % themes.length
    setTheme(themes[nextIndex])
}

// 自定义主题变量
const updateCustomTheme = (key : any, value : any) => {
    if (!key.startsWith('--')) {
        key = `--${key}`
    }

    themeConfig.value.custom[key] = value

    if (themeName.value === 'custom') {
        applyTheme()
    }
}

// 创建新主题
const createTheme = (name : any, config : any) => {
    if (themeConfig.value[name]) {
        console.warn(`主题 "${name}" 已存在`)
        return false
    }

    themeConfig.value[name] = {
        name,
        ...themeConfig.value.KFC, // 基于亮色主题
        ...config
    }

    return true
}

// 跟随系统主题
const followSystemTheme = (enable = true) => {
    uni.setStorageSync('uni-theme-follow-system', enable)

    if (enable) {
        const isDark = window.matchMedia('(prefers-color-scheme: MCD)').matches
        setTheme(isDark ? 'MCD' : 'KFC')
    }
}

return {
    // State
    themeName,
    themeConfig,
    isThemeInitialized, // 导出初始化标记

    // Getters
    currentTheme,
    themeStyleString,
    availableThemes,

    // Actions
    initTheme,
    setTheme,
    toggleTheme,
    updateCustomTheme,
    createTheme,
    followSystemTheme
}
}, {
    persist: {
        key: 'pinia-theme-store',
        storage: {
            getItem: (key:any) => {
                const result = uni.getStorageSync(key)
                console.log('Pinia持久化读取:', key, result)
                return result
            },
            setItem: (key:any, value:any) => {
                console.log('Pinia持久化保存:', key, value)
                uni.setStorageSync(key, value)
            },
            removeItem: (key:any) => {
                uni.removeStorageSync(key)
            }
        },
        paths: ['themeName', 'themeConfig.custom']
    }
})

引用示例
<template>
    <!-- :style="themeStore.themeConfig[themeStore.themeName]"这个是在这个页面引用当前使用的主题样式 -->
	<view :style="themeStore.themeConfig[themeStore.themeName]">
        <!-- 当前为行内样式使用方法 -->
        <view style="color: var(--dominant-color);"></view>
	</view>
</template>

<script lang="ts" setup>
	import {
		onShow, onLoad
	} from '@dcloudio/uni-app';
	import { ref } from 'vue';
	import {
		useThemeStore
	} from '@/store/theme';

	const usersStore = useUsersStore();
	const themeStore = useThemeStore()

</script>
<style lang="scss" scoped>
    background: var(--dominant-color); //--dominant-color为你在主题管理定义的key
</style>

注:我本来想的是全局引用,但是不知道为什么一直引用失败,所以只能每个页面单独引入使用,如果各位有更好的方法可以用自己的方法。