《Vue 3 实现多主题切换和自定义主题》

在现代前端开发中,主题切换(例如"暗黑模式"与"亮色模式")已逐渐成为提升用户体验的重要功能之一。无论是白天黑夜的自动调整,还是用户手动配置个性化配色,一个灵活、可持久化的主题系统正变得不可或缺。

本文将基于 Vue 3Pinia 状态管理,结合 pinia-plugin-persistedstate 插件,手把手实现一个支持主题切换与自定义主题配置的完整方案。

初始化主题仓库:useThemeStore

我们首先使用 Pinia 创建一个主题管理仓库,用于集中管理当前主题、可选主题列表,以及相关的操作逻辑。这个仓库也会配合 pinia-plugin-persistedstate 实现数据的本地持久化。

✅ 安装依赖(如果尚未安装):

javascript 复制代码
npm install pinia
npm install pinia-plugin-persistedstate

✅ 在 main.ts 中注册 Pinia 和持久化插件:

javascript 复制代码
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

app.use(pinia)
app.mount('#app')

✅ 创建主题仓库:stores/theme.ts

javascript 复制代码
import { defineStore } from 'pinia'

const useThemeStore = defineStore('theme', {
  state: () => ({
    theme: 'light',
    themes: {
      light: {
        '--bg-color': '#ffffff',
        '--text-color': '#333333',
        '--primary-color': '#007bff',
      },
      dark: {
        '--bg-color': '#121212',
        '--text-color': '#f0f0f0',
        '--primary-color': '#5e8dff',
      },
      custom: {
        '--bg-color': '#f5f5dc',
        '--text-color': '#111111',
        '--primary-color': '#ff6600',
      }
    }
  }),
  getters: {
    availableThemes: (state) => Object.keys(state.themes)
  },
  actions: {
    initTheme() {
      this.setTheme(this.theme)
    },
    setTheme(name) {
      this.theme = name
      const html = document.documentElement
      html.className = name
      const vars = this.themes[name]
      for (const key in vars) {
        html.style.setProperty(key, vars[key])
      }
    },
    toggleTheme() {
      const keys = Object.keys(this.themes)
      const next = keys[(keys.indexOf(this.theme) + 1) % keys.length]
      this.setTheme(next)
    },
    setCustomVar(key, value) {
      if (this.theme !== 'custom') {
        this.theme = 'custom'
      }
      this.themes.custom[key] = value
      const html = document.documentElement
      html.style.setProperty(key, value)
    }
  },
  persist: true,
})

export default useThemeStore

📌 提示:

建议在应用入口调用 themeStore.initTheme() 来确保页面加载时自动应用上次使用的主题。

CSS 变量作用于 document.documentElement,你可以在全局样式中引用这些变量,灵活控制主题样式

如此已经配置好了主题,以下是个使用案例

javascript 复制代码
<script setup>
import { ref, onMounted } from 'vue'
import useThemeStore from '../store/theme'
const themeStore = useThemeStore()
const bgColor = ref('')
const textColor = ref('')
onMounted(() => {
    bgColor.value =  themeStore.themes[themeStore.theme]["--bg-color"]
    textColor.value =  themeStore.themes[themeStore.theme]["--text-color"]
})
const changeTheme = (e) => {
    themeStore.setTheme(e.target.value)
}
const changeBgColor = () => {
    themeStore.setCustomVar("--bg-color", bgColor.value)
}
const changeTextColor = () => {
    themeStore.setCustomVar("--text-color", textColor.value)
}
</script>
<template>
    <div class="layout">
        <header class="header">
            <button @click="themeStore.toggleTheme">下一个主题</button>
            <span>头部、当前主题{{ themeStore.theme }}</span>
            <select v-model="themeStore.theme" @change="changeTheme"
                style="margin-left: 24px;">
                <option v-for="item in themeStore.availableThemes" :key="item" :value="item">{{ item }}</option>
            </select>
               <div v-if="themeStore.theme =='custom'">
                 背景色:
                <input type="color" v-model="bgColor" @input="changeBgColor" />
                文字色:
                <input type="color" v-model="textColor" @input="changeTextColor" />
               </div>
        </header>
        <aside class="sidebar">
            <ul>
                <li>菜单一</li>
                <li>菜单二</li>
                <li>菜单三</li>
            </ul>
        </aside>
        <main class="content">内容区域</main>
    </div>
</template>
<style scoped>
.layout {
    display: flex;
    flex-direction: column;
    height: 100vh;
}
.header {
    background: var(--bg-color);
    color: var(--text-color);
    padding: 16px;
    font-size: 20px;
    height: 100px;
}
.sidebar {
    background: var(--bg-color);
    color: var(--text-color);
    width: 180px;
    padding: 16px 0;
    flex-shrink: 0;
    height: 100%;
    position: absolute;
    top: 100px;
    bottom: 0;
}
.layout {
    position: relative;
}
.sidebar ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.sidebar li {
    padding: 8px 24px;
    cursor: pointer;
}
.content {
    background: var(--bg-color);
    color: var(--text-color);
    margin-left: 180px;
    padding: 24px;
    flex: 1;
}
</style>

✨ 结语

通过本文,我们基于 Vue 3 + Pinia + pinia-plugin-persistedstate 搭建了一个完整的主题管理系统,支持多主题切换、自定义主题变量以及状态持久化。在实际项目中,这样的设计不仅提升了用户体验,还使主题系统具备了良好的可维护性与扩展性。

无论是构建后台管理系统、博客平台还是移动端应用,一个灵活可控的主题系统,都是提升视觉一致性与用户个性化体验的重要一环。希望这篇文章能为你在项目中实现主题功能提供清晰思路和实用参考。

如果你在实践过程中遇到问题,欢迎留言交流,也欢迎点赞、收藏支持我持续输出优质内容 😊!

相关推荐
AlienZHOU2 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc6 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师7 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen8 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒8 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow8 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8188 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800788 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明8 小时前
第 6 章 组件进阶
前端·vue.js
江华森8 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端