《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 搭建了一个完整的主题管理系统,支持多主题切换、自定义主题变量以及状态持久化。在实际项目中,这样的设计不仅提升了用户体验,还使主题系统具备了良好的可维护性与扩展性。

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

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

相关推荐
搬砖记录员39 分钟前
用 PyAutoGUI 手搓录屏"老板键":从原理到四套可跑源码,附工程化落地思考
前端
windliang39 分钟前
Claude Code 源码分析(七):Skill 如何进入 Agent
前端·人工智能·面试
缓冲中请稍后42 分钟前
React Router 完全指南:从 HashRouter 到 BrowserRouter
前端·面试
妙码生花42 分钟前
从 PHP 到 AI + Golang,程序员自救转型手记(五十六):附件管理、增加根据文件后缀生成 SVG 文件图标的接口
前端·后端·go
栀鸢ouo43 分钟前
useTableHeight:一个优雅的 Vue 3 表格自适应高度解决方案
前端
橘子星43 分钟前
一篇文章搞懂 useRef:聚焦 DOM、模拟 forceRender、管理 Worker 都靠它
前端·javascript
前端Hardy1 小时前
Vue 终于杀进终端界!这个开源项目让 CLI 开发像写网页一样简单
前端·javascript·后端
一次旅行1 小时前
多智能体编排实战:拆解Plan-and-Execute范式+三层记忆架构,手写无依赖轻量Agent调度引擎
前端·javascript·架构
小玮看世界2 小时前
[Python]从“脏”数据到优雅实现:一个IoT滑动窗口最大值问题的测试驱动优化实录
linux·前端·python