Vue實現(xiàn)項目主題切換功能的兩種方案詳解
引言
在現(xiàn)代前端項目開發(fā)中,主題切換已成為提升用戶體驗的核心功能之一 —— 無論是適配用戶白天 / 夜間的使用場景、滿足企業(yè)級產(chǎn)品的品牌定制需求,還是應(yīng)對不同設(shè)備的顯示環(huán)境,靈活的主題系統(tǒng)都能讓產(chǎn)品更具競爭力。在 Vue 3 項目中,基于 CSS 變量與組合式 API(Composables)的主題切換方案最為常見,以下將詳細拆解兩種實現(xiàn)思路,并結(jié)合實際項目需求分析選型邏輯。
方案一:基于 CSS 類名切換的主題系統(tǒng)
該方案的核心思路是通過預(yù)定義不同主題的 CSS 類(如.theme-dark),在切換時為根元素添加 / 移除對應(yīng)類名,觸發(fā)全局 CSS 變量的替換,適合主題樣式固定、擴展需求較少的中小型項目。
步驟 1:搭建全局樣式文件結(jié)構(gòu)
首先在/src/assets下新建styles文件夾并創(chuàng)建theme.scss文件 —— 選擇 SCSS 而非普通 CSS,是因為 SCSS 支持變量嵌套、混入等特性,后續(xù)若需擴展主題樣式(如添加主題專屬組件樣式),可更高效地維護。

theme.scss文件:
// 默認使用淺色主題
:root{
--bg-color:#ffffff;
--text-color: #333333;
}
// 深色主題
.theme-dark{
--bg-color:#333333;
--text-color: #ffffff;
}
// 自定義主題
.theme-custom{
--bg-color: red;
--text-color: #333333;
}
// 全局應(yīng)用背景色
body{
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.5s ease-in-out; // 添加過渡效果,平滑過渡
}步驟 2:全局導入樣式文件
在 Vue 入口文件main.ts中導入theme.scss—— 入口文件導入可確保樣式全局生效,無需在每個組件中重復(fù)引入,避免代碼冗余。同時,該步驟需在createApp之前執(zhí)行,確保組件渲染時主題樣式已加載。
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/styles/theme.scss'; // 導入全局主題樣式:確保主題變量全局可訪問
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')步驟 3:在頭部組件實現(xiàn)主題切換交互
頭部組件是用戶高頻操作區(qū)域,在此添加主題切換按鈕可提升易用性。核心邏輯是通過useTheme組合式 API 封裝主題切換方法,同時結(jié)合 Pinia 存儲用戶信息、定時器更新當前時間,確保功能模塊化。
<script lang="ts" setup>
import router from '@/router';
import { ref, onMounted, watchEffect, computed, onUnmounted } from 'vue'
import type { Ref } from 'vue'
import { getUserInfo } from '@/api/home';
import { useUserStore } from '@/stores/user';
import { useTheme } from '@/composables/useTheme';
const { currentTheme, changeTheme } = useTheme();
const userStore = useUserStore()
interface IUserInfo {
adminid: string,
adminname: string,
checkedKeys: any[],
role: number
}
const userInfo: Ref<IUserInfo> = ref({
adminid: '',
adminname: '',
checkedKeys: [],
role: -1
})
// 1.使用 ref 創(chuàng)建一個響應(yīng)式的當前時間 toLocaleString()是格式化日期和時間的方法
const currentTime = ref(new Date().toLocaleString())
// 2.定義一個函數(shù)來更新時間
function updateTime(){
currentTime.value = new Date().toLocaleString()
}
onMounted(async () => {
// 3.設(shè)置一個定時器,每一秒更新一遍時間
const time = setInterval(updateTime,1000)
onUnmounted(()=>{
// 4.在組件卸載時清除定時器,避免內(nèi)存泄漏
clearInterval(time)
})
const res = await getUserInfo({
adminname: userStore.userInfo.adminname
})
userInfo.value = res.data.data[0]
})
const logout = (() => {
localStorage.removeItem('userInfo')
router.replace('/login')
})
</script>
<template>
<div class="header-manage">
<h1>HEADER</h1>
<div class="admin-manage">
<div class="theme-switch">
<button @click="changeTheme('light')">淺色主題</button>
<button @click="changeTheme('dark')">深色主題</button>
<button @click="changeTheme('custom')">自定義主題</button>
<p class="theme-text">當前主題:{{ currentTheme }}</p>
</div>
<div class="admin-con">
<el-icon>
<i-ep-Stopwatch />
</el-icon>
<span>{{ currentTime }}</span>
</div>
<div class="admin-con">
<el-icon>
<i-ep-User />
</el-icon>
<span>{{ userStore.userInfo.adminname }}</span>
</div>
<div class="admin-con" style="cursor: pointer;">
<el-icon>
<i-ep-SwitchButton />
</el-icon>
<span @click="logout">退出登錄</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.header-manage {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color:var(--text-primary-color) //全局主題顏色
}
.admin-manage {
display: flex;
.theme-switch {
.theme-text{
color: var(--text-secondary-color);
}
}
}
.admin-con {
margin-left: 30px;
display: flex;
align-items: center;
span {
font-size: 14px;
margin-left: 8px;
}
}
</style>方案二:基于 TypeScript 配置的動態(tài)主題系統(tǒng)
該方案的核心思路是通過 TypeScript 定義主題配置接口,將主題變量集中管理,切換時直接通過 JS 動態(tài)修改根元素的 CSS 變量,無需預(yù)定義 CSS 類,靈活性更高,適合主題變量多、需頻繁擴展的中大型項目。
步驟 1:搭建主題配置文件
在/src下新建theme文件夾并創(chuàng)建index.ts—— 用 TypeScript 定義主題接口與配置,既能通過類型約束避免變量名錯誤,又能將所有主題變量集中管理,后續(xù)修改或新增主題時,只需更新配置文件即可。
// 主題配置接口(添加索引簽名)
interface ThemeConfig {
"--bg-color": string;
"--text-primary-color": string;
"--text-secondary-color": string;
"--text-tertiary-color": string;
"--text-disabled-color": string;
"--button-bg-color": string;
"--button-text-color": string;
// 索引簽名:允許用字符串鍵訪問(關(guān)鍵!)
[key: string]: string;
}
// 主題類型定義
export type ThemeType = 'light' | 'dark' | 'custom'
// 主題配置
export const themeConfig: Record<ThemeType, ThemeConfig> = {
light:{
"--bg-color":'#ffffff', // 背景色
"--text-primary-color": '#333333',// 主要文字色
"--text-secondary-color":'#666666', // 次要文字色
"--text-tertiary-color":'#999999', // 三級文字色
"--text-disabled-color":'#cccccc', // 禁用文字色
"--button-bg-color":'#42b983', // 按鈕背景色
"--button-text-color":'#ffffff', // 按鈕文字色
},
dark:{
"--bg-color":'#333333', // 背景色
"--text-primary-color":'#ffffff', // 主要文字色
"--text-secondary-color":'#cccccc', // 次要文字色
"--text-tertiary-color":'#999999', // 三級文字色
"--text-disabled-color":'#666666', // 禁用文字色
"--button-bg-color":'#42b983', // 按鈕背景色
"--button-text-color":'#ffffff', // 按鈕文字色
},
custom:{
"--bg-color":'skyblue', // 背景色
"--text-primary-color":'#333333', // 主要文字色
"--text-secondary-color":'skyblue', // 次要文字色
"--text-tertiary-color":'#999999', // 三級文字色
"--text-disabled-color":'#cccccc', // 禁用文字色
"--button-bg-color":'#42b983', // 按鈕背景色
"--button-text-color":'#ffffff', // 按鈕文字色
}
}
// 默認主題
export const defaultTheme: ThemeType = 'light';步驟 2:封裝主題切換組合式 API
在/src/composables下新建useTheme.ts—— 用 Vue 的組合式 API 封裝主題切換邏輯,將 “主題初始化、主題應(yīng)用、主題切換” 等功能模塊化,后續(xù)任何組件需要使用主題功能,只需導入該 API 即可,避免代碼重復(fù)。
import type { ThemeType } from '@/theme'
import { themeConfig, defaultTheme } from '@/theme'
import { onMounted, ref } from 'vue'
export function useTheme() {
// 當前主題,響應(yīng)式
const currentTheme = ref<ThemeType>(defaultTheme)
// 初始化:從localStorage中獲取保存的主題
onMounted(() => {
const theme = localStorage.getItem('app-theme') as ThemeType
if (theme && Object.keys(themeConfig).includes(theme)) {
currentTheme.value = theme
applyTheme(theme) //應(yīng)用主題
}
})
// 應(yīng)用主題(核心:給html加主題類名,觸發(fā)css變量切換)
const applyTheme = (theme: ThemeType) => {
// 先移除所有主題類
document.documentElement.classList.remove('theme-light','theme-dark','theme-custom')
// 添加當前主題類(如果不是默認主題)
// if(theme !== 'light'){
// document.documentElement.classList.add(`theme-${theme}`)
// }
Object.keys(themeConfig[theme]).forEach(key => {
document.documentElement.style.setProperty(`${key}`, themeConfig[theme][key]);
});
// document.documentElement.style.setProperty('--bg-color', themeConfig[theme].backgroundColor);
// 存儲當前主題
localStorage.setItem('app-theme', theme)
}
// 切換主題
const changeTheme = (theme: ThemeType) => {
if(themeConfig[theme]){
currentTheme.value = theme
applyTheme(theme)
}
}
return { currentTheme, changeTheme }
}步驟 3:復(fù)用組件交互邏輯
與方案一的步驟 3 一致,在頭部組件中導入useTheme API,綁定按鈕點擊事件觸發(fā)changeTheme—— 由于方案二的主題邏輯已通過 API 封裝,組件層面無需修改任何交互代碼,只需復(fù)用現(xiàn)有按鈕即可實現(xiàn)主題切換,體現(xiàn)了模塊化的優(yōu)勢。



兩種方案對比與選型分析
| 對比維度 | 方案一(CSS 類名切換) | 方案二(TS 配置動態(tài)修改) |
|---|---|---|
| 實現(xiàn)原理 | 預(yù)定義主題類,通過添加 / 移除類名切換 | 集中管理 TS 配置,動態(tài)修改 CSS 變量 |
| 靈活性 | 新增主題需修改 CSS 文件,擴展成本高 | 新增主題只需添加 TS 配置,擴展成本低 |
| TypeScript 支持 | 無類型約束,易出現(xiàn)變量名錯誤 | 接口 + 類型約束,避免變量錯誤 |
| 維護成本 | 主題樣式分散在 CSS 中,維護難度高 | 主題配置集中在 TS 中,維護更高效 |
| 持久化實現(xiàn) | 需額外編寫邏輯保存類名 | 內(nèi)置 localStorage 保存,邏輯更簡潔 |
| 適用場景 | 主題少、需求穩(wěn)定的中小型項目 | 主題多、需頻繁擴展的中大型項目 |
從實際項目需求出發(fā),方案二的優(yōu)勢更為明顯:一是 TypeScript 的類型約束降低了協(xié)作中的錯誤率;二是集中式配置讓主題維護更高效(如后續(xù)新增 “企業(yè)主題”,只需在themeConfig中添加配置);三是動態(tài)修改 CSS 變量無需預(yù)定義類名,減少了 CSS 代碼冗余。因此,最終選擇方案二作為項目的主題切換實現(xiàn)方案。
以上就是Vue實現(xiàn)項目主題切換功能的兩種方案詳解的詳細內(nèi)容,更多關(guān)于Vue項目主題切換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
echarts.js 動態(tài)生成多個圖表 使用vue封裝組件操作
這篇文章主要介紹了echarts.js 動態(tài)生成多個圖表 使用vue封裝組件操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
vue 3 中watch 和watchEffect 的新用法
本篇文章主要通過 Options API 和 Composition API 對比 watch 的使用方法,讓大家快速掌握 vue3 中 watch 新用法,需要的朋友可以參考一下哦,希望對大家有所幫助2021-11-11
使用vue實現(xiàn)點擊按鈕滑出面板的實現(xiàn)代碼
這篇文章主要介紹了使用vue實現(xiàn)點擊按鈕滑出面板的實現(xiàn)代碼,非常不錯,具有參考借鑒價值,需要的朋友參考下2017-01-01
vue實現(xiàn)微信分享鏈接添加動態(tài)參數(shù)的方法
這篇文章主要介紹了vue微信分享鏈接添加動態(tài)參數(shù)的實現(xiàn)方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-04-04
vue2使用ts?vue-class-component的過程
vue-property-decorator?是一個?Vue.js?的裝飾器庫,它提供了一些裝飾器來讓你在?Vue?組件中定義屬性、計算屬性、方法、事件等,本文給大家介紹vue2使用ts?vue-class-component的相關(guān)知識,感興趣的朋友一起看看吧2023-11-11
Vue3實現(xiàn)通過axios來讀取本地json文件
這篇文章主要介紹了Vue3實現(xiàn)通過axios來讀取本地json文件方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

