一、简介
在这篇文章中,我会带你快速了解如何使用 Vue3 + Element Plus 实现一键换肤和暗黑模式切换,让你的项目瞬间变得更加个性化和用户友好。而且,这些主题设置都会被本地存储,刷新页面也不会丢失!让我们一起为你的项目加点“色彩”吧!
二、先看看效果是不是您需要的
- 支持暗黑模式(适配水印)
- 支持多种主题换色
- 支持颜色本地存储
- 提供ElementPlus颜色修改工具类
- 可统一定制所有用户默认配色
三、先介绍一下本地存储和默认配色吧
这两个功能为后续开发的基础,在具体实现换肤的时候就不过多描述这些代码了,可以选择性跳过也不影响阅读。
1. 默认配色
在项目中创建了一个 settings.ts 用于项目的默认配置,其他类需要使用可以直接引入即可,我们可以在这里直接定义用户的默认行为,其中主要包括:
- 项目配色
themeColor - 项目主题
theme - 项目布局
layout - 项目动态动画
animateCss - 其他的配置项,请自行查看
const {pkg} = __APP_INFO__;
interface DefaultSettings {
title: string,
version: string,
watermarkContent: string,
app: AppState,
settings: AppSettings
}
const defaultSettings: DefaultSettings = {
title: pkg.name,
version: pkg.version,
watermarkContent: pkg.name,
app: {
size: SizeEnum.DEFAULT,
device: DeviceEnum.DESKTOP,
sidebarStatus: SidebarStatusEnum.CLOSED,
tourStatus: true
},
settings: {
settingsVisible: false,
fixedHeader: true,
tagsView: true,
sidebarLogo: true,
breadCrumb: true,
layout: LayoutEnum.LEFT,
theme: ThemeEnum.LIGHT,
themeColor: {
info: "#909399",
primary: "#409EFF",
success: "#67C23A",
warning: "#E6A23C",
danger: "#F56C6C"
},
animateCss: 'animate__fadeIn',
watermarkEnabled: true
}
};
export default defaultSettings;
2. 本地存储
项目采用LocalStorage进行存储,使用的技术为 pinia 和 pinia-plugin-persistedstate,我们通过 persist 属性即可在 state 改变的时候改变 LocalStorage, 配置项如下 :
export const useSystemStore = defineStore(StoreTypeEnum.SYSTEM, {
state: (): {
app: AppState,
settings: AppSettings
} => {
return {
app: {...defaultSettings.app},
// 解决浅拷贝对象指向同一地址问题
settings: _.cloneDeep(defaultSettings.settings)
}
},
persist: true,
// 定义计算属性
getters: {
// 省略
},
// 定义操作
actions: {
// 省略
},
})
三、实现暗黑模式
基础实现
在 Element Plus 中实现暗黑模式非常简单,来看看官方怎么说
意思非常明确,我们只需要在html中加上dark的类即可,那我们只需要编写一个切换按钮,修改类上的class属性即可:
// 切换按钮页面省略,该方法为 pinia 内 actions 中方法
/**
* 设置主题
* @param theme 主题类型
*/
setTheme(theme: ThemeEnum) {
if (theme === ThemeEnum.LIGHT) {
// light 主题
this.settings.theme = ThemeEnum.LIGHT;
document.documentElement.classList.remove("dark");
} else if (theme === ThemeEnum.DARK) {
// dark 主题
this.settings.theme = ThemeEnum.DARK;
document.documentElement.classList.add("dark");
}
}
水印问题
突然有一天,领导突发奇想叫你加个水印,你说不轻轻松松?在 router-view 外面套一个 el-watermark 不就行了吗,官方也有提供 , 于是你添加了以下代码 :
<template>
<el-config-provider :size="size" :locale="zhCn">
<div class="app-container">
<!-- 开启水印 z-index > el-affix(z-index = 100) 即可 :locale="'zh-cn'" -->
<el-watermark
v-if="watermarkEnabled"
:z-index="101"
:content="defaultSettings.title"
>
<router-view/>
</el-watermark>
<!-- 关闭水印 -->
<router-view v-else/>
</div>
</el-config-provider>
</template>
结果在测试切换为暗黑模式的时候发现,咦?我水印怎么不见了?
最终发现原来是官网是将color字段写死的,如果切换为暗黑模式,字体颜色没有改变,那么我们就还需要自己再编写一下,当切换为暗黑模式的时候,切换水印颜色。
我们新增一个 fontColor 使用 computed 来计算颜色改变,当切换为暗黑模式的时候,更改对应颜色
<template>
<el-config-provider :locale="zhCn" :size="size">
<div class="app-container">
<!-- 开启水印 z-index > el-affix(z-index = 100) 即可 :locale="'zh-cn'" -->
<el-watermark
v-if="watermarkEnabled"
:content="defaultSettings.title"
:font="{ color: fontColor }"
:z-index="101"
>
<router-view/>
</el-watermark>
<!-- 关闭水印 -->
<router-view v-else/>
</div>
</el-config-provider>
</template>
<script lang="ts" setup>
const systemStore = useSystemStore();
const fontStyle = computed(() => systemStore.fontStyle);
const size = computed(() => systemStore.app.size as SizeEnum);
const watermarkEnabled = computed(() => systemStore.settings.watermarkEnabled);
// 明亮/暗黑主题水印字体颜色适配
const fontColor = computed(() => {
return systemStore.settings.theme === ThemeEnum.DARK
? "rgba(255, 255, 255, .15)"
: "rgba(0, 0, 0, .15)";
});
})
</script>
就不演示给大家看啦,肯定是成功的,我部署了一个演示地址,大家可以看看
四、主题颜色更改
看看官方怎么说,如何控制 ElmentPlus 颜色 ↓↓↓
意思也非常明确,我们修改配色对应属性变量值即可修改主题颜色
了解 ElementPlus 的颜色搭配
在项目中引入暗黑模式后,简单地修改 --el-color-XX 变量只会影响非暗黑模式下的主题颜色,而在暗黑模式下,这些颜色仍然保持不变,导致体验不一致。为了在不同模式下都拥有协调的配色,我们需要对 Element Plus 提供的全套颜色进行合理搭配。
Element Plus 通过一系列预定义的颜色变量来实现灵活的主题切换,例如:
:root {
--el-color-primary: #409eff; /* 主色 */
--el-color-primary-light-3: #79bbff; /* 主色的 3 度亮色 */
--el-color-primary-light-5: #a0cfff; /* 主色的 5 度亮色 */
--el-color-primary-light-7: #c6e2ff; /* 主色的 7 度亮色 */
--el-color-primary-light-8: #d9ecff; /* 主色的 8 度亮色 */
--el-color-primary-light-9: #ecf5ff; /* 主色的 9 度亮色 */
--el-color-primary-dark-2: #337ecc; /* 主色的 2 度暗色 */
--el-color-success: #67c23a; /* 成功色 */
--el-color-success-light-3: #95d475; /* 成功色的 3 度亮色 */
--el-color-success-light-5: #b3e19d; /* 成功色的 5 度亮色 */
--el-color-success-light-7: #d1edc4; /* 成功色的 7 度亮色 */
--el-color-success-dark-2: #529b2e; /* 成功色的 2 度暗色 */
/* 其他颜色如警告、信息和危险色也有类似的变量 */
}
编写修改颜色工具类
编写工具类之前,我们只需要明确一个点 : 修改 Element Plus 颜色搭配,我们还需要修改其他协调色
工具类大家不必深究直接复制即可
type RGB = {
r: number;
g: number;
b: number;
};
const rgbWhite = {
r: 255,
g: 255,
b: 255,
};
const rgbBlack = {
r: 0,
g: 0,
b: 0,
};
function componentToHex(c: number): string {
const hex = Math.round(c).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
function rgbToHex(rgb: RGB): string {
return `#${componentToHex(rgb.r)}${componentToHex(rgb.g)}${componentToHex(rgb.b)}`;
}
function mix(color: RGB, mixColor: RGB, weight: number): RGB {
return {
r: color.r * (1 - weight) + mixColor.r * weight,
g: color.g * (1 - weight) + mixColor.g * weight,
b: color.b * (1 - weight) + mixColor.b * weight,
};
}
/**
* hex 转换为 rgb
* @param hex 例如 #FF0000
*/
function hexToRGB(hex: string): RGB {
if (!/^[0-9A-Fa-f]{3}$|[0-9A-Fa-f]{6}$/.test(hex)) {
throw new Error("请传入合法的16进制颜色值,eg: #FF0000");
}
// 移除可能存在的 # 符号
hex = hex.replace('#', '');
// 确保十六进制代码是有效的
// 返回 RGB 对象
return {
r: parseInt(hex.slice(0, 2), 16),
g: parseInt(hex.slice(2, 4), 16),
b: parseInt(hex.slice(4, 6), 16)
};
}
/**
* 修改 element-plus的颜色主题
*/
function updateElementPlusTheme(type: string, baseColor: string): void {
// 针对 element-plus 进行修改
const colorArray: Record<string, string>[] = [
{className: `--el-color-${type}`, color: rgbToHex(mix(hexToRGB(baseColor), rgbBlack, 0))},
{className: `--el-color-${type}-dark-2`, color: rgbToHex(mix(hexToRGB(baseColor), rgbBlack, 0.2))},
{className: `--el-color-${type}-light-3`, color: rgbToHex(mix(hexToRGB(baseColor), rgbWhite, 0.3))},
{className: `--el-color-${type}-light-5`, color: rgbToHex(mix(hexToRGB(baseColor), rgbWhite, 0.5))},
{className: `--el-color-${type}-light-7`, color: rgbToHex(mix(hexToRGB(baseColor), rgbWhite, 0.7))},
{className: `--el-color-${type}-light-8`, color: rgbToHex(mix(hexToRGB(baseColor), rgbWhite, 0.78))},
{className: `--el-color-${type}-light-9`, color: rgbToHex(mix(hexToRGB(baseColor), rgbWhite, 0.85))},
]
colorArray.forEach(item => {
document.documentElement.style.setProperty(item.className, item.color);
})
}
export {
RGB,
hexToRGB,
rgbToHex,
updateElementPlusTheme
}
其中最主要的方法就是 updateElementPlusTheme 用于修改对应 type 的 ElementPlus 的颜色,包括一并修改其他协调色。
在页面中修改主题颜色
当我们有工具就非常简单了,直接使用工具类方法即可一行代码修改配色
// 切换按钮页面省略,该方法为 pinia 内 actions 中方法
/**
* 设置主题颜色
* @param type 类型
* @param color 颜色
*/
setThemeColor(type: ElementPlusModifyEnum, color: string) {
// 修改 store 中主题颜色的值
this.settings.themeColor[type] = color
// 更新 Element Plus 主题
updateElementPlusTheme(type, color);
},
在页面初始化的时候加载配色
我们需要在用户进入页面的时候就将本地存储的配色加载好,所以在 App.vue 的时候就需要加载这些配色和暗黑模式
onMounted(() => {
// 初始化暗黑模式
if (systemStore.settings.theme === ThemeEnum.DARK) {
document.documentElement.classList.add('dark')
}
// 初始化主题色
Object.keys(defaultSettings.settings.themeColor).forEach((key) => {
const enumKey = key as ElementPlusModifyEnum; // 类型断言将键转换为 ElementPlusModifyEnum 类型
if (defaultSettings.settings.themeColor[enumKey] !== systemStore.settings.themeColor[enumKey]) {
systemStore.setThemeColor(enumKey, systemStore.settings.themeColor[enumKey])
}
})
})
五、结束语
Element Plus 一键换肤功能比较简单,在在线演示中还有其他小功能,比如动画切换、页签拖拽等其他功能,欢迎大家交流、start