前言
在实际的项目开发需求中,我们经常能遇到动态换肤这个需求,但是到底这个功能是如何实现的呢?那么今天就来让大家学会动态换肤的实现方式。
技术栈
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中的方法就会根据这个值来调用不同的方法进行存储。
最后在进行一次演示:
这样就算最后的功能完成了
总结
掌握了这部分的知识,感觉还是很有收获的,最起码这个学习的过程是很重要的。