vue3 中 主題定制
背景
做多主題定制,黑/白 ,里面還要再分各種顏色,每次進來都要記住上次的主題設置
效果圖
一、目錄結構
├── generated
│ ├── theme
│ │ └── dark-yellow.ts
│ │ └── dark-orange.ts
│ │ └── light-yellow.ts
│ │ └── light-orange.ts
│ │ └── theme.enums.ts
├── stores
│ ├── theme-store.ts
數據結構
主題
枚舉
二、流程
- Init的時候,根據 store 中的顏色主題,先做一次匹配,changeTheme
- 切換主題的時候,監聽綁定的值,做 changeTheme
- changeTheme 主要就是讀取 文件列表,做匹配,做規則制定,最后使用
document.documentElement.style.setProperty
設置style,根據css 變量做匹配
三、核心實現
- init 時,設置已存儲的theme
import { setupTheme } from '@/stores/theme-store'
const app = createApp(App)
initApp(app)const initApp = async (app: App) => {setupStore(app)setupRouter(app)setupLang(app)setupTheme()app.mount('#app')
}
- 讀取配置主題文件(匹配到的文件默認是懶加載的,通過動態導入實現eager: true)
const themeFileList: Record<string, string> = import.meta.glob(['@/generated/theme/*.ts', '!@/generated/theme/theme.enums.ts'],{import: 'default',eager: true}
)
- 根據文件夾匹配出主題key
const themeFileListObject = {}
for (const key in themeFileList) {const filename = key.split('/').pop()?.replace('.ts', '')if (filename) {themeFileListObject[filename] = themeFileList[key]}
}
- 替換root 樣式(根據存儲key匹配出當前色值表,format css name,then set style)
const injectRootStyle = (theme: ThemeEnum) => {const themeObject = themeFileListObject[theme]for (const key in themeObject) {const cssVariableName = `--${key.replace(/_/g, '-').toLowerCase()}`document.documentElement.style.setProperty(cssVariableName, themeObject[key])}
}
- Pian 中做數據監聽,data change to call changeTheme function.
watch(theme, (nVal) => {changeTheme(nVal)
})
// 改變主題
const changeTheme = (theme: ThemeEnum) => {injectRootStyle(theme)
}
//設置主題
const setupTheme = () => {changeTheme(useThemeStore().theme)
}
四、總體代碼
import ThemeEnum from '@/generated/theme/theme-enum'
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'// 讀取全部主題配置
const themeFileList: Record<string, string> = import.meta.glob(['@/generated/theme/*.ts', '!@/generated/theme/theme.enums.ts'],{import: 'default',eager: true}
)// 根據讀取的文件路徑,生成名稱和地址
const themeFileListObject = {}
for (const key in themeFileList) {const filename = key.split('/').pop()?.replace('.ts', '')if (filename) {themeFileListObject[filename] = themeFileList[key]}
}// 注入根樣式
const injectRootStyle = (theme: ThemeEnum) => {const themeObject = themeFileListObject[theme]for (const key in themeObject) {const cssVariableName = `--${key.replace(/_/g, '-').toLowerCase()}`document.documentElement.style.setProperty(cssVariableName, themeObject[key])}
}// 定義一個函數,用于改變主題
const changeTheme = (theme: ThemeEnum) => {// 注入根樣式injectRootStyle(theme)
}// 定義一個函數,用于設置主題
const setupTheme = () => {// 改變主題changeTheme(useThemeStore().theme)
}const useThemeStore = defineStore('theme',() => {const theme = ref(ThemeEnum['light-red'])watch(theme, (nVal) => {changeTheme(nVal)})return { theme }},{ persist: true }
)export { setupTheme, changeTheme, useThemeStore }
五、總結
- 我們在切換主題的時候,在組件內其實沒有做任何處理,是在pinia 里做的監聽
- 持久化 這里用到了 persist 插件
- 通過向外暴露setupTheme 來實現 修改主題