uni-app 全端动态换肤方案 (Vue2 + uView 1.0)

uni-app 全端动态换肤方案 (Vue2 + uView 1.0)

1. 方案简介

本方案实现了 H5、App、微信小程序 的全端主题切换功能。

  • 页面元素 :使用 CSS 变量 (var(--xxx)) 实现秒级无感切换。
  • 原生控件 :通过 JS API 动态修改 NavigationBar(导航栏)、TabBar(底部菜单样式)及 TabBar Icon(图标)。
  • 兼容性:解决了 H5 背景色不同步、App 端 API 不兼容报错、Webview 重置等问题。

2. 目录结构

Plaintext

复制代码
project-root
├── common
│   └── theme.js          // 核心配置:定义颜色变量、原生配置、图标映射
├── store
│   └── index.js          // 核心逻辑:Vuex 状态管理、调用原生 API
├── mixins
│   └── themeMixin.js     // 注入逻辑:CSS 变量注入、onShow 补救措施
├── static
│   └── tabbar            // 图标资源:需按命名规范存放
├── App.vue               // H5 body 背景处理、初始化
├── main.js               // 全局 Mixin 注册
└── pages/index/index.vue // 使用示例

3. 核心代码实现

3.1 图片资源准备 (static/tabbar/)

文件命名规范{iconKey}_{themeName}.png{iconKey}_{themeName}_sel.png

示例:

  • home_default.png / home_default_sel.png

  • home_red.png / home_red_sel.png

  • user_default.png / ...

    static/
    └── tabbar/
    ├── home_default.png // 默认主题-未选中
    ├── home_default_sel.png // 默认主题-选中
    ├── user_default.png
    ├── user_default_sel.png

    ├── home_red.png // 红色主题-未选中
    ├── home_red_sel.png // 红色主题-选中
    ├── user_red.png
    ├── user_red_sel.png

    ├── home_dark.png // 暗黑主题-未选中
    └── ...

3.2 主题配置文件 (common/theme.js)

JavaScript

复制代码
// Tabbar 结构配置 (需与 pages.json 保持一致)
export const tabbarConfig = [
    { index: 0, text: '首页', iconKey: 'home' },
    { index: 1, text: '我的', iconKey: 'user' }
]

// 主题色值定义
export default {
    // === 默认主题 (蓝色) ===
    default: {
        // CSS 变量 (用于页面)
        '--bg-color': '#f3f4f6',
        '--box-bg': '#ffffff',
        '--text-color': '#333333',
        '--primary-color': '#2979ff',

        // 原生控件配置
        navBg: '#2979ff',
        navTxt: '#ffffff', // 仅支持 #ffffff 或 #000000
        
        tabBg: '#ffffff',
        tabTxtColor: '#999999',
        tabSelColor: '#2979ff',
        tabBorder: 'black'
    },
    
    // === 红色主题 ===
    red: {
        '--bg-color': '#fff1f1',
        '--box-bg': '#ffffff',
        '--text-color': '#4a0a0a',
        '--primary-color': '#fa3534',

        navBg: '#fa3534',
        navTxt: '#ffffff',
        
        tabBg: '#fff0f0',
        tabTxtColor: '#ffadad',
        tabSelColor: '#fa3534',
        tabBorder: 'white'
    }
}

3.3 Vuex 状态管理 (store/index.js)

复制代码
import Vue from 'vue'
import Vuex from 'vuex'
import themes, { tabbarConfig } from '@/common/theme.js'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        themeName: 'default',
    },
    getters: {
        // 生成 CSS 变量字符串
        themeStyle(state) {
            const theme = themes[state.themeName] || themes['default'];
            let styleStr = '';
            for (let key in theme) {
                if (key.startsWith('--')) {
                    styleStr += `${key}: ${theme[key]};`;
                }
            }
            return styleStr;
        },
        // 获取当前主题对象 (供 JS 使用)
        currentThemeColor(state) {
            return themes[state.themeName] || themes['default'];
        }
    },
    mutations: {
        SET_THEME_STATE(state, themeName) {
            state.themeName = themeName;
            uni.setStorageSync('uview_theme', themeName);
        }
    },
    actions: {
        // 对外调用的切换方法
        setTheme({ commit, dispatch }, themeName) {
            if (!themes[themeName]) return;
            commit('SET_THEME_STATE', themeName);
            dispatch('updateNativeUI');
        },

        // 核心:更新原生 UI (Nav, Tabbar)
        updateNativeUI({ state }) {
            const themeName = state.themeName;
            const theme = themes[themeName] || themes['default'];

            // 1. 设置导航栏
            try {
                uni.setNavigationBarColor({
                    frontColor: theme.navTxt,
                    backgroundColor: theme.navBg,
                    animation: { duration: 0 },
                    fail: () => {}
                });
            } catch (e) {}

            // 2. 设置 Tabbar 样式
            try {
                uni.setTabBarStyle({
                    backgroundColor: theme.tabBg,
                    color: theme.tabTxtColor,
                    selectedColor: theme.tabSelColor,
                    borderStyle: theme.tabBorder,
                    fail: () => {}
                });
            } catch (e) {}

            // 3. 设置 Tabbar 图标
            if (typeof tabbarConfig !== 'undefined' && tabbarConfig.length) {
                tabbarConfig.forEach(item => {
                    try {
                        uni.setTabBarItem({
                            index: item.index,
                            text: item.text,
                            iconPath: `static/tabbar/${item.iconKey}_${themeName}.png`,
                            selectedIconPath: `static/tabbar/${item.iconKey}_${themeName}_sel.png`,
                            fail: () => {} // 非 Tabbar 页面调用会失败,必须捕获
                        });
                    } catch (e) {}
                });
            }

            // 4. 设置窗口背景 (仅小程序) - 防止 App/H5 报错
            // #ifdef MP
            if (theme['--bg-color']) {
                uni.setBackgroundColor({
                    backgroundColor: theme['--bg-color'],
                    backgroundColorTop: theme['--bg-color'],
                    backgroundColorBottom: theme['--bg-color'],
                    fail: () => {}
                });
            }
            // #endif
        }
    }
})

3.4 全局 Mixin (mixins/themeMixin.js)

复制代码
import { mapGetters } from 'vuex';

export default {
    computed: {
        ...mapGetters(['themeStyle', 'currentThemeColor']),
        // 供页面根节点绑定的 style
        themeVars() {
            return this.themeStyle || '';
        }
    },
    // 每次页面显示时,强制刷新一次原生 UI
    // 解决 WebView 重置、返回页面导航栏颜色丢失等问题
    onShow() {
        this.$store.dispatch('updateNativeUI');
        // 延迟 50ms 确保页面容器已准备好
		// 如果在某些极端的 支付宝小程序 或 老旧 Android 机型上, onShow 执行过快, DOM 还没准备好, 导致设置失败。 可以使用 this.$nextTick 或 setTimeout 包裹一下:
		// setTimeout(() => {
		// 	this.$store.dispatch('updateNativeUI');
		// }, 50);
    },
    methods: {
        setTheme(name) {
            this.$store.dispatch('setTheme', name);
        }
    }
}

3.5 入口配置 (main.js)

注意Vue.mixin 必须在 new Vue 之前。

复制代码
import Vue from 'vue'
import App from './App'
import store from './store'
import uView from 'uview-ui'
import themeMixin from './mixins/themeMixin.js'

Vue.use(uView)

// 【关键】注册全局 Mixin
Vue.mixin(themeMixin)

const app = new Vue({
    store,
    ...App
})
app.$mount()

3.6 App.vue 配置 (解决 H5 Body 背景)

复制代码
<script>
    export default {
        onLaunch: function() {
            const savedTheme = uni.getStorageSync('uview_theme') || 'default';
            this.$store.dispatch('setTheme', savedTheme);
        },
        onShow: function() {
            // #ifdef H5
            this.updateH5BodyBg();
            // #endif
        },
        methods: {
            updateH5BodyBg() {
                if (!this.$store || !this.$store.getters.currentThemeColor) return;
                const themeConfig = this.$store.getters.currentThemeColor;
                if (themeConfig['--bg-color']) {
                    document.body.style.backgroundColor = themeConfig['--bg-color'];
                }
            }
        },
        watch: {
            // 监听主题变化
            '$store.state.themeName': {
                handler(val) {
                    // #ifdef H5
                    this.updateH5BodyBg();
                    // #endif
                }
            }
        }
    }
</script>

<style lang="scss">
    @import "uview-ui/index.scss";
    
    // 强制覆盖 page 背景,使 App 端生效
    page {
        background-color: var(--bg-color) !important;
        transition: background-color 0.3s;
    }
</style>

4. 页面使用指南

在任何 .vue 页面中,必须遵守以下规则:

  1. 根节点绑定 :最外层 <view> 必须绑定 :style="themeVars"
  2. 样式使用
    • CSS 中 :使用 var(--primary-color)
    • uView 组件中 :支持 custom-style 的直接传 var(...);不支持的属性用 currentThemeColor['--primary-color']

示例代码 (pages/index/index.vue):

复制代码
<template>
    <view class="content" :style="themeVars">
        
        <view class="box">我是跟随主题的盒子</view>
        
        <u-button 
            :custom-style="{ backgroundColor: 'var(--primary-color)', color: '#fff' }"
            @click="setTheme('red')">
            切换红色主题
        </u-button>

        <u-icon name="star" :color="currentThemeColor['--primary-color']"></u-icon>
        
    </view>
</template>

<style lang="scss" scoped>
    .content {
        // 使用变量
        background-color: var(--bg-color);
        min-height: 100vh;
    }
    .box {
        background-color: var(--box-bg);
        color: var(--text-color);
    }
</style>

5. 常见问题 (FAQ)

  1. Q: 为什么 H5 报错 setBackgroundColor is not yet implemented?
    • A: 可以在 store/index.js 中使用了条件编译 // #ifdef MP 包裹该 API,确保只在小程序环境执行。
  2. Q: 为什么报错 themeVars is not defined?
    • A: 检查 main.jsVue.mixin(themeMixin) 是否写在了 new Vue() 之前。
  3. Q: 导航栏文字颜色为什么没变?
    • A: uni.setNavigationBarColorfrontColor 属性非常严格,只能是 #ffffff#000000。请检查 theme.js 配置。
  4. Q: 为什么切换主题时 Tabbar 图标会闪一下?
    • A: 这是因为原生 API 替换图片需要加载时间。建议压缩图标体积(推荐 PNG, 81x81px 以内)。

如果对您有帮忙感谢一箭三连支持,如果有错误的地方欢迎指正,大家一起学习进步!

相关推荐
郑州光合科技余经理1 小时前
开发实战:海外版同城o2o生活服务平台核心模块设计
开发语言·git·python·架构·uni-app·生活·智慧城市
行走的陀螺仪2 小时前
在UniApp H5中,实现路由栈的持久化
前端·javascript·uni-app·路由持久化·路由缓存策略
影子打怪2 小时前
uniapp通过plus.geolocation.watchPosition获取的坐标格式转换
uni-app
忒可君2 小时前
2026新年第一篇:uni-app + AI = 3分钟实现数据大屏
前端·vue.js·uni-app
行走的陀螺仪2 小时前
UniApp 横向可滚动 Tab 组件开发详解
uni-app·封装组件·tabs·自定义封装组件·可滚动组件tab
2501_915918412 小时前
介绍如何在电脑上查看 iPhone 和 iPad 的完整设备信息
android·ios·小程序·uni-app·电脑·iphone·ipad
2501_916008892 小时前
没有 Mac 如何在 Windows 上创建 iOS 应用描述文件
android·macos·ios·小程序·uni-app·iphone·webview
Rysxt_13 小时前
uni-app路由跳转完全指南:从基础到高级实践
uni-app
一壶纱18 小时前
UniApp + Pinia 数据持久化
前端·数据库·uni-app
酒醉的胡铁1 天前
uniapp解决video组件在ios上全屏页面旋转90度,组件旋转180度
ios·uni-app