快速掌握 Vue3 + Element Plus 一键换肤功能,轻松切换主题

4,902 阅读6分钟

一、简介

在这篇文章中,我会带你快速了解如何使用 Vue3 + Element Plus 实现一键换肤和暗黑模式切换,让你的项目瞬间变得更加个性化和用户友好。而且,这些主题设置都会被本地存储,刷新页面也不会丢失!让我们一起为你的项目加点“色彩”吧!

二、先看看效果是不是您需要的

  1. 支持暗黑模式(适配水印)
  2. 支持多种主题换色
  3. 支持颜色本地存储
  4. 提供ElementPlus颜色修改工具类
  5. 可统一定制所有用户默认配色

QQ录屏20240906114846.gif

三、先介绍一下本地存储和默认配色吧

这两个功能为后续开发的基础,在具体实现换肤的时候就不过多描述这些代码了,可以选择性跳过也不影响阅读。

1. 默认配色

在项目中创建了一个 settings.ts 用于项目的默认配置,其他类需要使用可以直接引入即可,我们可以在这里直接定义用户的默认行为,其中主要包括:

  1. 项目配色 themeColor
  2. 项目主题 theme
  3. 项目布局 layout
  4. 项目动态动画 animateCss
  5. 其他的配置项,请自行查看
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进行存储,使用的技术为 piniapinia-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 中实现暗黑模式非常简单,来看看官方怎么说

image.png

意思非常明确,我们只需要在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>

结果在测试切换为暗黑模式的时候发现,咦?我水印怎么不见了?

QQ录屏20240906134329.gif

最终发现原来是官网是将color字段写死的,如果切换为暗黑模式,字体颜色没有改变,那么我们就还需要自己再编写一下,当切换为暗黑模式的时候,切换水印颜色。

image.png

我们新增一个 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 颜色 ↓↓↓

image.png

意思也非常明确,我们修改配色对应属性变量值即可修改主题颜色

了解 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

在线演示 | 源码-基础版 | 源码-后台管理版