如何在 vue3+vite 中使用 Element-plus 实现 自定义主题 多主题切换

Element Plus 多主题切换:源码级新玩法

在做 Vue 3 + Element Plus 项目时,自定义主题 几乎是必修课。

大家都知道的几种方案:

  • 覆盖 CSS 变量
  • 修改 SCSS 变量重新编译
  • 官方提供的暗黑模式

这些方式足够应付单主题或暗黑/明亮切换,但要实现 多主题自由切换(品牌 A、品牌 B、高对比度...) 时,体验就差强人意了。

翻源码的过程中,我发现了一个关键点 ------ Element Plus 内部生成颜色变量的函数 set-css-color-type。默认它写死了 "base" 作为主色入口。

前置条件

  • 使用了sass 因此我们需要安装插件 sass-embedded
sh 复制代码
pnpm add sass-embedded -D

原版源码(节选)

源码地址:https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/mixins/_var.scss

scss 复制代码
// https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/mixins/_var.scss
@mixin set-css-color-type($colors, $type) {
  @include set-css-var-value(('color', $type), map.get($colors, $type, 'base'));

  @each $i in (3, 5, 7, 8, 9) {
    @include set-css-var-value(
      ('color', $type, 'light', $i),
      map.get($colors, $type, 'light-#{$i}')
    );
  }

  @include set-css-var-value(
    ('color', $type, 'dark-2'),
    map.get($colors, $type, 'dark-2')
  );
}

可以看到,map.get(..., 'base') 被写死了。

这意味着所有主题都只能以 base 为入口。


改造方案

我们把 base 抽象成一个 $theme 参数,这样就能为每个主题(如 default / simple / grand / noble)指定不同的基色。

scss 复制代码
@mixin set-css-color-type($colors, $type, $theme: "base") {
  @include set-css-var-value(('color', $type), map.get($colors, $type, $theme));

  @each $i in (3, 5, 7, 8, 9) {
    @include set-css-var-value(
      ('color', $type, 'light', $i),
      map.get($colors, $type, 'light-#{$i}')
    );
  }

  @include set-css-var-value(
    ('color', $type, 'dark-2'),
    map.get($colors, $type, 'dark-2')
  );
}

然后批量生成主题 class:

scss 复制代码
@each $class, $color in ('base', 'default', 'simple', 'grand', 'noble') {
  .#{$class} {
    @each $type in (primary, success, warning, danger, info) {
      @include set-css-color-type($colors, $type, $class);
    }
  }
}

定义多主题颜色

scss 复制代码
$colors: (
  'primary': (
    default: #0069d9,
    simple: #4a90e2,
    grand: #2c6b97,
    noble: #003b61
  ),
  'success': (
    default: #28a745,
    simple: #5cb85c,
    grand: #2d8c4f,
    noble: #1f7b35
  ),
  'info': (
    default: #17a2b8,
    simple: #32c8d5,
    grand: #2292b7,
    noble: #166f80
  ),
  'warning': (
    default: #ffc107,
    simple: #f39c12,
    grand: #e67e22,
    noble: #e04e01
  ),
  'danger': (
    default: #dc3545,
    simple: #e57373,
    grand: #c0392b,
    noble: #9b2d20
  )
);

Vue 中动态切换

ts 复制代码
// useTheme.ts
import { ref } from 'vue'

const theme = ref('default')

export function useTheme() {
  const setTheme = (name: string) => {
    theme.value = name
    document.documentElement.className = name
  }
  return { theme, setTheme }
}

只要调用 setTheme('grand'),页面就会切换成深蓝主题,Element Plus 的暗色模式与亮色层级依然会自动生效。不过推荐使用vueuse的 useColorMode,好不好用我就不用多说了

最后 完整代码

目录结构:

cpp 复制代码
src/
└─ index.scss
├─ main.ts
├─ app.vue
vite.ts
scss 复制代码
// src/index.scss
@use 'sass:map';
@use 'sass:color';

$primary-themes: (
  default: #0069d9,
  simple: #4a90e2,
  grand: #2c6b97,
  noble: #003b61
);

$success-themes: (
  default: #28a745,
  simple: #5cb85c,
  grand: #2d8c4f,
  noble: #1f7b35
);

$info-themes: (
  default: #17a2b8,
  simple: #32c8d5,
  grand: #2292b7,
  noble: #166f80
);

$warning-themes: (
  default: #ffc107,
  simple: #f39c12,
  grand: #e67e22,
  noble: #e04e01
);

$danger-themes: (
  default: #dc3545,
  simple: #e57373,
  grand: #c0392b,
  noble: #9b2d20
);

@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': $primary-themes,
    'success': $success-themes,
    'info': $info-themes,
    'warning': $warning-themes,
    'danger': $danger-themes
  )
);

@use 'element-plus/theme-chalk/src/common/var.scss' as *;
@use 'element-plus/theme-chalk/src/mixins/_var.scss' as *;




@mixin set-css-color-type($colors, $type, $theme: "base") {
  @include set-css-var-value(('color', $type), map.get($colors, $type, $theme));
  @each $i in (3, 5, 7, 8, 9) {
    @include set-css-var-value(
      ('color', $type, 'light', $i),
      map.get($colors, $type, 'light-#{$i}')
    );
  }

  @include set-css-var-value(
    ('color', $type, 'dark-2'),
    map.get($colors, $type, 'dark-2')
  );
}


@each $class, $color in $primary-themes {
  .#{$class} {
    @each $type in (primary, success, warning, danger,  info) {
        @include set-css-color-type($colors, $type,$class)
    }
  }
}
ts 复制代码
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
// import 'element-plus/dist/index.css'
import "./index.scss"
createApp(App).use(ElementPlus).mount('#app')
ts 复制代码
// vite.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
})
js 复制代码
// src/app.vue
<script setup lang="ts">

</script>

<template>
  <div>
    element-plus-custom-themes
    <el-button>默认按钮</el-button>
    <el-button type="primary">主要按钮</el-button>
  </div>
</template>

<style scoped>

</style>

按需引入

如果我们使用按需引入,只需要改掉vite.ts 和main.ts

  • 前置条件 安装 按需引入需要的插件
sh 复制代码
pnpm add unplugin-vue-components unplugin-auto-import -D
  • 修改后的文件
ts 复制代码
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
// import ElementPlus from 'element-plus'
// import 'element-plus/dist/index.css'
// import "./assets/styles/index.scss"
// createApp(App).use(ElementPlus).mount('#app')
createApp(App).mount('#app')
ts 复制代码
// vite.ts
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      include: [/\.[jt]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/],
      imports: [
        'vue',
      ],
      resolvers: [ElementPlusResolver()],
      vueTemplate: true,
    }),
    Components({
      extensions: ['vue', 'md'],
      include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
      resolvers: [ElementPlusResolver({
        importStyle: 'sass',
      })],
    }),
  ],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/assets/styles/themes/index.scss" as *;`
      },
    },
  },
})

总结

  • Element Plus 默认 set-css-color-type 写死了 "base",这是多主题受限的根源。
  • 我们改造后,可以让 default / simple / grand / noble 等多套主题并存。
  • 内置的暗黑模式、颜色层级(light/dark)逻辑依旧能正常工作,不需要重复维护。
  • 按需导入,减少构建体积。

这样,你就可以在实际项目里优雅地支持 品牌主题、活动主题、暗黑主题 等多套风格啦 🎉

相关推荐
月亮慢慢圆2 小时前
网络监控状态
前端
_AaronWong2 小时前
实现 Electron 资源下载与更新:实时进度监控
前端·electron
Doris_20232 小时前
Python条件判断语句 if、elif 、else
前端·后端·python
Doris_20232 小时前
Python 模式匹配match case
前端·后端·python
森林的尽头是阳光2 小时前
vue防抖节流,全局定义,使用
前端·javascript·vue.js
YiHanXii2 小时前
React.memo 小练习题 + 参考答案
前端·javascript·react.js
zero13_小葵司2 小时前
Vue 3 前端工程化规范
前端·javascript·vue.js
Yolanda_20222 小时前
vue-sync修饰符解析以及切换iframe页面进行保存提示功能的思路
前端·javascript·vue.js
伍哥的传说3 小时前
Vite Plugin PWA – 零配置构建现代渐进式Web应用
开发语言·前端·javascript·web app·pwa·service worker·workbox