UniApp高级实战:跨平台动态主题切换引擎开发指南
一、架构设计
基于Vuex+CSS Variables的主题系统,支持10+主题实时切换,兼容小程序/H5/APP三端
二、核心实现
1. 主题管理器
// theme-manager.js
const themes = {
light: {
'--primary-color': '#1890ff',
'--bg-color': '#ffffff',
'--text-color': '#333333'
},
dark: {
'--primary-color': '#177ddc',
'--bg-color': '#1a1a1a',
'--text-color': '#e6e6e6'
},
// 更多主题...
}
export default {
getThemes() {
return Object.keys(themes)
},
applyTheme(name) {
const theme = themes[name]
if (!theme) return
// 动态更新CSS变量
const root = document.documentElement || document.body
Object.keys(theme).forEach(key => {
root.style.setProperty(key, theme[key])
})
// 持久化存储
uni.setStorageSync('currentTheme', name)
},
initTheme() {
const savedTheme = uni.getStorageSync('currentTheme') || 'light'
this.applyTheme(savedTheme)
return savedTheme
}
}
2. Vuex状态集成
// store/theme.js
import themeManager from '@/utils/theme-manager'
export default {
state: {
currentTheme: themeManager.initTheme(),
availableThemes: themeManager.getThemes()
},
mutations: {
SET_THEME(state, name) {
state.currentTheme = name
themeManager.applyTheme(name)
}
},
actions: {
changeTheme({ commit }, name) {
commit('SET_THEME', name)
}
}
}
三、主题适配方案
1. 全局CSS变量定义
/* uni.scss */
:root {
/* 默认值用于不支持JS的情况 */
--primary-color: #1890ff;
--bg-color: #ffffff;
--text-color: #333333;
}
page {
background-color: var(--bg-color);
color: var(--text-color);
transition: all 0.3s ease;
}
.button-primary {
background-color: var(--primary-color);
color: white;
}
/* 小程序兼容方案 */
/* #ifdef MP-WEIXIN */
page {
--primary-color: #1890ff;
--bg-color: #ffffff;
--text-color: #333333;
}
/* #endif */
2. 组件级主题适配
<template>
<view class="card" :style="cardStyle">
<slot></slot>
</view>
</template>
<script>
export default {
computed: {
cardStyle() {
return {
'--card-bg': this.$store.state.theme.currentTheme === 'dark'
? '#2a2a2a' : '#f5f5f5',
'--card-shadow': this.$store.state.theme.currentTheme === 'dark'
? '0 2px 8px rgba(0,0,0,0.3)' : '0 2px 8px rgba(0,0,0,0.1)'
}
}
}
}
</script>
<style scoped>
.card {
background: var(--card-bg);
box-shadow: var(--card-shadow);
border-radius: 8px;
padding: 16px;
margin: 10px;
}
</style>
四、完整案例
<template>
<view>
<view class="theme-picker">
<button
v-for="theme in themes"
:key="theme"
@click="changeTheme(theme)"
:class="{ active: currentTheme === theme }">
{{ theme }}
</button>
</view>
<theme-card>
<text>当前主题: {{ currentTheme }}</text>
</theme-card>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('theme', ['currentTheme', 'availableThemes'])
},
methods: {
...mapActions('theme', ['changeTheme'])
}
}
</script>
<style>
.theme-picker {
display: flex;
padding: 20px;
}
.theme-picker button {
margin-right: 10px;
padding: 5px 10px;
}
.theme-picker .active {
border: 2px solid var(--primary-color);
}
</style>