UNIAPP多主题

Lirioing2024/05/09UNIAPPFEVUEUNIAPP

换肤方案简介

实现方案问题
备注信息
点击切换主题按钮,重启项目,
重新引入对应的主题
H5操作无异常,运行到APP后出现异常:
App V3版本不支持在js中引入css,scss文件
考虑弃用
项目入口文件中App.vue中写一个视图元素
配置一个动态style的样式
uniapp不支持在App.vue里面添加template标签无法实现
在每一个页面中根节点添加一个动态style页面中不能使用page{}修改页面样式可行

App.vueuni-app 的主组件,所有页面都是在 App.vue 下进行切换的,是页面入口文件。但 App.vue 本身不是页面,这里不能编写视图元素,也就是没有 <template>

换肤实现原理

  1. 配置主题 css变量 文件
  2. 使用 vuex 缓存同步样式设置, 使用 stroage 缓存样式
  3. 配置全局 SCSS变量 和全局 class
  4. 添加 mixin 样式混入
  5. 初始化主题和切换主题
  6. 单个页面使用

配置主题 CSS 文件

import light from "./theme-light";
import dark from "./theme-dark";
const themes = {
	light,
	dark
}
export default themes
const light = {
  value: [
    // 黑白色
    { name: '--white', value: '#ffffff'},
    { name: '--black', value: '#000000'},
    // 背景色
    { name: '--bg-color', value: '#f7f9fc'}, // 页面背景色
    // 主题色
    { name: '--primary-color', value: '#286df7'},
    { name: '--primary-color-disabled', value: '#afcafa'},
    { name: '--primary-color-light', value: '#e9f2ff'},
  ],
  tabBar:{
    color: '#808080',
    selectedColor: '#1D61F6',
    backgroundColor:'#ffffff'
  },
  // Android 上的 backgroundColor 参数有限制,黑色大于 rgb(30,30,30), 白色小于 rgb(235,235,235)
  navbar: { 
    frontColor: '#000000', // 前景颜色值,包括按钮、标题、状态栏的颜色,仅支持 #ffffff 和 #000000
    backgroundColor: '#dddddd',
  }
}
export default light
const dark = {
  value: [
    // 黑白色
    { name: '--white', value: '#ffffff'},
    { name: '--black', value: '#000000'},
    // 背景色
    { name: '--bg-color', value: '#f7f9fc'}, // 页面背景色
    // 主题色
    { name: '--primary-color', value: '#286df7'},
    { name: '--primary-color-disabled', value: '#afcafa'},
    { name: '--primary-color-light', value: '#e9f2ff'},
  ],
  tabBar:{
    color: "#ffffff",
    selectedColor: "#1D61F6",
    backgroundColor: "#000000",
  },
  // Android 上的 backgroundColor 参数有限制,黑色大于 rgb(30,30,30), 白色小于 rgb(235,235,235)
  navbar: { 
    frontColor: '#ffffff', // 前景颜色值,包括按钮、标题、状态栏的颜色,仅支持 #ffffff 和 #000000
    backgroundColor: '#333333',
  }
}
export default dark

配置全局 SCSS 变量

.status_bar {
  height: var(--status-bar-height);
  width: 100%;
}

/* 项目颜色配置 */
$black: var(--black);
$white: var(--white);

/* 主色 */
$primary-color: var(--primary-color);
$primary-color-disabled: var(--primary-color-disabled);
$primary-color-light: var(--primary-color-light);
/* 主色 */
/* 文字颜色 */

/* 背景颜色 */
$bg-color: var(--bg-color);
/* 背景颜色 */

同步并缓存样式

// 引入主题
import themes from '@/theme/index'
import storage from '@/utils/storage'
 
const store = {
	state: {
        // 写上默认皮肤的数据
		skin: '',
		skinMap: {},
		theme: themes[storage.getItem('theme') || 'light']
	},
	getters: {
 
	},
	mutations: {
		// 皮肤更换
		skinPeeler(state,skin = []){
			// 将皮肤配置JSON转为以 ; 分割的字符串(style 值)
			let styleMap = {}
			let style = skin.map((item,index)=>{
				styleMap[item.name] = item.value
				return `${item.name}:${item.value}`
			}).join(";");
			state.skin = style;
			state.skinMap = styleMap;
			console.log(skin,styleMap, style, 'mutations')

		},
		updateTheme(state,mode='light'){
			state.theme = themes[mode];
		}
	}
}
 
export default store
const getters = {
  skin: state => state.theme.skin,
  theme: state => state.theme.theme,
  skinMap: state => state.theme.skinMap,
}
export default getters
import Vue from 'vue'
import Vuex from 'vuex'
import user from '@/store/modules/user'
import theme from '@/store/modules/theme'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
  modules: {
    user,
    theme
  },
  getters
})
export default store

Minix 样式混入

import { mapGetters } from 'vuex'
import storage from '@/utils/storage'
import themes from '@/theme/index'
export default {
  install(Vue) {
    Vue.mixin({
      onShow() {
        setTimeout(() => {
          this.initNavbar()
        }, 100)
      },
      computed: {
        ...mapGetters(['skin', 'theme', 'skinMap'])
      },
      methods: {
        // 初始化tabbar和navbar样式
        initNavbar() {
          let theme = storage.getItem('theme') || 'light'
          let themeObj = themes[theme]
          uni.setTabBarStyle(themeObj.tabBar)
          uni.setNavigationBarColor(themeObj.navbar)
          console.log(themeObj, "initNavbar")
        },
        // 获取某个css变量的颜色值
        getStyleProperty(key) {
          return this.skinMap[key]
        }
      }
    })
  }
}
import themeMixin from './mixin/themeMixin';
Vue.use(themeMixin);

切换与使用主题

// 初始化主题
import themes from '@/theme/index'
import store from '@/store'
import storage from '@/utils/storage'

...

methods: {
  // 初始化主题
  initTheme() {
    let theme = storage.getItem('theme') || 'light'
    let themeObj = themes[theme]
    store.commit('skinPeeler', themeObj.value)
    console.log(themeObj, "initTheme")
    storage.setItem('theme', theme)
  },
}

...

import storage from "@/utils/storage.js"
import themes from '@/theme/index'
import { mapMutations } from 'vuex'

...

methods: {
  ...mapMutations({
    setTheme : 'skinPeeler',
	updateTheme: 'updateTheme'
  }),
  // 切换主题
  changeTheme(type) {
    let theme = themes[type]
    this.setTheme(theme.value)
    this.updateTheme(type)
    storage.setItem('theme', type)
    uni.setTabBarStyle(theme.tabBar)
    uni.setNavigationBarColor(theme.navbar)
    console.log(theme, "changeTheme")
  },
}

...

<template>
  <view :style="skin">
    <u-calendar 
      v-model="showCalendar" 
      :active-bg-color="activeBgColor" 
      mode="date">
    </u-calendar>
  	...
  </view>
</template>
<script>

...

computed: {
  activeColor() {
    return this.getStyleProperty('--primary-color')
  },
}
...

</script>
.home-page {
  color: $primary-color;
  background: var(--bg-color);
}
上次更新 2025/12/6 16:52:48