前言
在实际的项目开发需求中,我们经常能遇到动态换肤这个需求,但是到底这个功能是如何实现的呢?那么今天就来让大家学会动态换肤的实现方式。
技术栈
Vue3 Vuex ElementPlus 持久化插件
ElementPlus的动态换肤👨🎓👨🎓
我在学习这部分的时候,发现网上的做法参差不齐,有些方法过于繁琐,在这里我查看了ElementPlus官方的文档,并且也参考了其他博主的一些文章,在这里写一下自己的认知和理解。
首先是针对ElementPlus
的动态换肤,这一部分我参考了ElementPlus官网的动态换肤
官网讲解了官方使用了css变量来进行颜色的替换,废话不多说,直接上代码进行演示
selectColor.vue中的核心代码:
<el-form> <div class="flex flex-wrap"> <el-form-item v-for="color in Object.keys(colors)" :label="colors[color].label" :key="color" > <el-color-picker v-model="colors[color].value" @change="changeColor($event, colors[color].key)" :predefine="predefineColors" ></el-color-picker> </el-form-item> </div> </el-form>
// element-plus主题颜色设置 const colors = reactive({ primary: { label: "主要颜色", value: "", key: "--el-color-primary", type: "element", }, info: { label: "次要颜色", value: "", key: "--el-color-info", type: "element", }, //...依次类推设置相应的变量 });
onMounted(() => { setColorVar(); }); // 定义全局变量 const currentCss = getComputedStyle(document.documentElement); // 设置主题颜色 const setColorVar = () => { colors.primary.value = currentCss.getPropertyValue("--el-color-primary"); colors.info.value = currentCss.getPropertyValue("--el-color-info"); colors.danger.value = currentCss.getPropertyValue("--el-color-danger"); colors.warning.value = currentCss.getPropertyValue("--el-color-warning"); colors.bg.value = currentCss.getPropertyValue("--el-bg-color"); };
这里面我们使用了一个颜色选择器el-color-picker
对着块不是很熟悉的同=同学可以上官网进行查看
这里面通过getComputedStyle(document.documentElement);
这一段代码实现获取全局的变量,然后定义setColorVar
方法,通过
getPropertyValue
这个方法获取相应变量的颜色,然后设置到colors这个对象中的value值里面,在页面一开始渲染的时候就调用这个方法进行相应的颜色渲染。
更改颜色的方法,在使用ElementPlus颜色选择器中,使用setProperty
方法选择颜色之后来进行相应的颜色设置,然后使用了持久化存储,这个我们接下来仔细说明。
// 更改颜色 const changeColor = (color, key) => { if (color == null) { ElMessage.error("颜色值不能为空"); return; } // 设置颜色 document.documentElement.style.setProperty(key, color); // 持久化存储 store.commit("theme/setCustomized", colors); };
在这个时候我们已经完成了对属于ElementPlus相关的颜色的设置,但是目前还是存在问题,主要就是在页面刷新之后,颜色还是会恢复之前的原始状态,那么这个时候我们就要使用vuex并且进行持久化的存储。
使用vuex并完成持久化的存储👨🔬👨🔬
vuex中的相关代码,这里使用了vuex中的模块化的思想,在store/module/theme.js中进行相关的设置。
export default { namespaced: true, state: { // 主题颜色 mainColor: {}, globalCss, }, mutations: { // 设置主题颜色 setMainColor(state, { property, colorVal }) { // 设置颜色 state.mainColor[property] = colorVal; }, // 设置侧边栏颜色 setSideBarColor(state, { property, colorVal }) { state.globalCss[property] = colorVal; }, // 设置自定义颜色主题 setCustomized(state, Colors) { Object.keys(Colors).forEach((item) => { if (Colors[item].type == "element") { this.commit("theme/setMainColor", { property: Colors[item].key, colorVal: Colors[item].value, }); } else { this.commit("theme/setSideBarColor", { property: Colors[item].key, colorVal: Colors[item].value, }); } }); }, }, };
其中setCustomized
这个方法接收之前定义的colors对象,来进行相关的设置,首先进行判断是否为element-plus的相关设置,然后在调用setMainColor
方法,将相应的颜色值进行存储,但是我们知道,如果只是进行到这一步还是不够的,因为vuex中存储的数据在进行刷新之后还是会消失,因此我们要进行持久化的存储,那么这个时候我选择的是时候相关的插件,
下载相关的持久化存储插件
npm i vuex-persistedstate
然后在store/index.js中进行相关的设置
import { createStore } from "vuex"; import theme from "./module/theme"; import getters from "./getters"; // 引入第三方插件,配置持久化 import createPersistedState from "vuex-persistedstate"; export default createStore({ getters, modules: { theme, }, plugins: [ createPersistedState({ storage: window.localStorage, key: "mainColor", paths: ["theme"], }), ], });
在下载了相关的npm包之后,我们导入createPersistedState
这个方法,然后在vuex中的createStore中的plugins
中进行相关的配置
这个plugins
是一个数组,然后在里面使用createPersistedState
方法传入一个对象,进行键值对的设置,其中paths是一个数组,里面表示要持久化存储的模块。storage表示的是使用什么进行持久化的存储,这里我使用的是localStorage
来及进行持久化的存储。
到现在如果我们进行页面刷新的时候,会发现还是没有达到相应的要求,这是为什么呢?其实我们会发现我们使用的setVarCss方法设置的还是element-plus默认的初始值,这里虽然进行了持久化的存储,但是相关的值没有使用,因此我又定义了相关的方法来完成这最后一步,代码如下
export default (store) => { // 如果存在自定义的颜色 if (JSON.stringify(store.getters.mainColor) != "{}") { Object.keys(store.getters.mainColor).forEach((key) => { document.documentElement.style.setProperty( key, store.getters.mainColor[key] ); }); } };
这部分代码直接以函数的形式导出,然后在main.js中进行引入,传入store来完成相应的方法,这个方法其实就是将之前持久化存储的值在这里通过setProperty
来进行相关的设置,以便我们之前在组件中setVarCss
方法能够正确的完成。
这样我们就算是完成了持久化存储,下面我们实际的演示一下。
到这里我们就实现了对ElementPlus的动态换肤,那么我认为就是一下步骤:
- 首先我们肯定要使用ElementPlus官方的组件:el-color-picker,为此我们还准备了一个对象colors,这个对象里面存储的是每种颜色(例如primary,info等等),包含每种颜色的key和value,便于我们进行设置
- 然后我们还创建了一个changeColor方法,这个方法用了修改选中的一些颜色,并且我们还进行了持久化的存储,使用了一个持久化存储的npm包,并且进行了一些相关的配置。
- 我们进行页面渲染的核心其实就是setVarCss这个方法,这个方法写在OnMounted中,每次页面渲染的时候,就会调用这个方法,本质就是利用全局变量中存储的变量来进行渲染
- 在调用全局的css变量的时候使用
getPropertyValue
这个方法,但是我们注意到我们此时调用的这个全局变量其实还是迷人值,没有利用起来vuex持久化存储的数据,那么为此我又单独创建了一个方法。并且将这个方法导出然后在main.js中进行引用 - 这个方法接收一个store参数,然后就可以获取到持久化存储的相应值。来进行相应的渲染,这样持久化存储的目的也就达到了。
非ElementPlus的动态换肤
这里我还定义了一些自定义的部分进行颜色换肤,比如我自己写的侧边栏,这里也要拥有动态换肤的功能,其实实现的思路和ElementPlus的换肤思想是类似的,我们现在colors中添加这个颜色对象
sideBarColor: { label: "侧边栏颜色", value: "", key: "menuBg", type: "unelement", },
然后在页面初始渲染的时候我们同样也是用vuex中的颜色,注意这里我在本地也定义了css颜色变量,然后我在setVarCss这个方法中新增一个行代码如下:
colors.sideBarColor.value = store.getters.globalCss.menuBg;
这部分代码就是对侧边栏颜色的设置,以便于当颜色修改之后我们可以在选择器中也随着修改。
import globalCss from "@/style/global.module.scss"; export default{ state: { // 主题颜色 mainColor: {}, globalCss, }, }
我们这里导入的是scss文件,这个引入的就是我在本地定义的一些自定义的颜色变量,在scss中使用下面这种形式来到出css变量
// sidebar $menuBg: #304156; // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass // JS 与 scss 共享变量,在 scss 中通过 :export 进行导出,在 js 中可通过 ESM 进行导入 :export { menuBg: $menuBg; }
由于颜色太多,这里就不一一展示,只是举例其中的一个例子,别的也是类似的。
其他的其实是类似的,比如在切换颜色之后也需要进行持久化的存储,我们在之前的vuex的方法中也写了,我们定义了两个方法,其中一个是针对ElementPlus的动态换肤来写的,另一个就是针对非ElementPlus换肤来写的。我们在colors这个对象中对每个属性中也增加了区分的方式,通过type这个方式来区分,然后vuex中的方法就会根据这个值来调用不同的方法进行存储。
最后在进行一次演示:
这样就算最后的功能完成了
总结
掌握了这部分的知识,感觉还是很有收获的,最起码这个学习的过程是很重要的。