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
js
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)
js
const themeFileList: Record<string, string> = import.meta.glob(
['@/generated/theme/*.ts', '!@/generated/theme/theme.enums.ts'],
{
import: 'default',
eager: true
}
)
- 根据文件夹匹配出主题key
js
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)
js
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.
js
watch(theme, (nVal) => {
changeTheme(nVal)
})
// 改变主题
const changeTheme = (theme: ThemeEnum) => {
injectRootStyle(theme)
}
//设置主题
const setupTheme = () => {
changeTheme(useThemeStore().theme)
}
四、总体代码
js
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 来实现 修改主题