elementui主题, 如何动态设置scss变量的值?目的还是动态修改elementui框架的主题颜色

如图官方的修改方法

image

我的项目中使用了elementui作为框架,同时使用了scss。
有没有办法动态更改这个变量的值来设置不同的主题?

之前的思路是动态引入不同的主题

// 不同的主题文件,他们的内容只有一个就是设置
// $--color-primary: theme1;
// $--color-primary: theme2;
// $--color-primary: theme3;
// 如果使用var变量来会导致构建失败
// $--color-primary: var(--primary, red)


// 通过接口获取到主题
let theme = getTheme()
import(`./${theme}.scss`)

这样存在一个问题,就是实际构建的时候,所有定义的主题都会被编译。就算我没有动态引入也会被编译,导致主题之间互相覆盖。
所以现在想只用一个scss文件来定义变量,但是这个变量的值不是固定的,有没有办法处理呢?

阅读 12.7k
4 个回答

经过不断的尝试和搜索,我发现所有实现方式最终还是和elementUI切换主题源码的实现方式基本一致,即手动替换掉框架原来的颜色。

源码如下:

ementui切换主题源码
如果项目配置的webpack在4.0以上的用户也可以查看这篇文章,对框架的处理原理也是类似的
https://segmentfault.com/a/11...

最终我自己的实现也类似,代码如下,供参考:

import color from 'css-color-function'
// 这里是elementui对应的颜色公式,和elementui源码中的formula一致
import elementFormula from './elementFormula'

// element_ui默认的主题色
const ORIGINAL_THEME = '#409EFF'
const version = require('element-ui/package.json').version // 版本号
// 所有element_ui的默认样式
let themeChalk = null

// RGB颜色转16进制
function convertColor (RGBColor) {
  // 转成 xxx, xxx, xxx
 let rgbArray = RGBColor.slice(4, RGBColor.length - 1).replace(/s/g, '').split(',')
  let red = Number(rgbArray[0]).toString(16)
  let green = Number(rgbArray[1]).toString(16)
  let blue = Number(rgbArray[2]).toString(16)
  return red + green + blue
}

// 获取默认element_ui颜色转换后的数组
function getThemeColorArray (theme) {
  let colors = [theme.replace('#', '')]
  Object.keys(elementFormula).forEach(key => {
    const value = elementFormula[key].replace(/primary/g, theme)
    // 根据公式转成对应的rgb颜色,但比对用的是16进制颜色
    let RGBColor = color.convert(value)
    colors.push(convertColor(RGBColor))
  })
  return colors
}

/**
 * 替换掉所有的旧颜色
 * @param {Array} oldStyleArray 老的element_ui颜色数组,使用element_ui默认的主题色生成
 * @param {Array} newStyleArray 新颜色数组,通过 getThemeColorArray 方法转换颜色生成
 * */
function updateStyle (oldStyleArray, newStyleArray) {
  oldStyleArray.forEach((color, index) => {
    themeChalk = themeChalk.replace(new RegExp(color, 'ig'), newStyleArray[index])
  })
  return themeChalk
}

function getCSSString (url, callback) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = () => {
    if (xhr.readyState === 4 && xhr.status === 200) {
      themeChalk = xhr.responseText.replace(/@font-face{[^}]+}/, '')
      callback()
    }
  }
  xhr.open('GET', url)
  xhr.send()
}

// 转换主题颜色
export function changeThemeColor (primary) {
  if (typeof primary !== 'string') return
  // 生成新的对应颜色数组
  const themeStyleArray = getThemeColorArray(primary)
  const getHandler = (id) => {
    return () => {
      const originalStyleArray = getThemeColorArray(ORIGINAL_THEME)
      const newStyle = updateStyle(
        originalStyleArray,
        themeStyleArray
      )

      let styleTag = document.getElementById(id)
      if (!styleTag) {
        styleTag = document.createElement('style')
        styleTag.setAttribute('id', id)
        document.head.appendChild(styleTag)
      }
      styleTag.innerText = newStyle
    }
  }
  const themeHandler = getHandler('redcat-theme-style')

  // 第一次从cdn获取对应的样式文件
  if (!themeChalk) {
    const url = `//unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
    getCSSString(url, themeHandler)
  } else {
    themeHandler()
  }
}

关键问题在于编译后的 css 无法动态切换样式,最终还是要通过切换 class 的方式切换主题

看看这篇文章 https://segmentfault.com/a/11...

以下是我实际工作中利用scss实现主题色动态切换

1、首先需要在vue项目中安装sass相关的依赖

npm install sass-loader --save-dev
npm install node-sass --sava-dev
npm install sass-resources-loader --save-dev

2、在src->assets文件下下创建_theme.scss,里面主要存放所有公共变量、混合样式等

3、全局引入_theme.scss:修改根目录->build文件夹->utils.js里面的scss: generateLoaders('sass'),具体修改内容如下

scss: generateLoaders('sass').concat({
  loader: 'sass-resources-loader',
  options: {
    // 
    resources: path.resolve(__dirname, '../src/assets/_theme.scss')
  }
})

4、demo页面效果如下图:
主题换色.gif
5、具体功能实现代码如下:

main.vue代码如下:

<template>
  <div class="theme-demo">
    <!-- 主题色切换按钮 -->
    <div>
      <el-radio-group v-model="theme" @change="switchTheme">
        <el-radio-button label="blue">蓝色主题</el-radio-button>
        <el-radio-button label="green">绿色主题</el-radio-button>
        <el-radio-button label="orange">橙色主题</el-radio-button>
      </el-radio-group>
    </div>

    <!-- 主题色demo -->
    <div class="content">
      <el-tabs v-model="activeName">
        <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
        <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
        <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
      </el-tabs>
    </div>
    
  </div>
</template>

<script>
  export default {
    data () {
      return {
        // 主题颜色
        theme: 'blue',
        // 当前激活tab
        activeName: 'first'
      }
    },
    methods: {
      /**
       * description: 切换主题
       *
       */
      switchTheme () {
        // 缓存主题颜色
        localStorage.setItem('theme', this.theme)
        // 切换主题色(前提需要给html标签设置data-theme自定义属性)
        window.document.documentElement.setAttribute('data-theme', this.theme)
      }
    },
    created () {
      // 获取缓存中的主题颜色
      this.theme = this.getItem('theme', this.theme)
      // 设置主题色(前提需要给html标签设置data-theme自定义属性)
      window.document.documentElement.setAttribute('data-theme', this.theme)
    },
  }
</script>

<style lang="scss" scoped>
  .theme-demo {
    .content {
      margin-top: 30px;
      padding: 30px;
      border-radius: 4px;
      border: 1px solid;
      /* 混入边框颜色样式示例 */
      @include border_color();

      /deep/ .el-tabs__item:hover {
        /* 混入边框颜色样式示例 */
        @include text_color();
      }

      /deep/ .el-tabs__item.is-active {
        /* 混入文字颜色样式示例 */
        @include text_color();
      }

      /deep/ .el-tabs__active-bar {
        /* 混入背景颜色样式示例 */
        @include background_color();
      }

      /deep/ .el-tabs__content {
        /* 使用公共变量 */
        font-size: $font-size-base;
        min-height: 400px;
        text-align: left;
      }
    }
    
  }
</style>
_theme.scss代码如下:

// ====== 公共变量定义 start ======

// 常用字体大小
$font-size-small: 12px;
$font-size-base: 14px;
$font-size-large: 16px;

// ====== 公共变量定义 end ======


// ====== 混合样式定义 start ======

// 文字颜色
@mixin text_color{
  // 蓝色主题
  [data-theme="blue"] & {
    color: #378EF0;
  }

  // 绿色主题
  [data-theme="green"] & {
    color:#37BC64;
  }

  // 橙色主题
  [data-theme="orange"] & {
    color:#FF9D14;
  }
}

// 边框颜色
@mixin border_color{
  // 蓝色主题
  [data-theme="blue"] & {
    border-color: #378EF0;
  }

  // 绿色主题
  [data-theme="green"] & {
    border-color:#37BC64;
  }

  // 橙色主题
  [data-theme="orange"] & {
    border-color:#FF9D14;
  }
}

// 背景色
@mixin background_color{
  // 蓝色主题
  [data-theme="blue"] & {
    background-color: #378EF0;
  }

  // 绿色主题
  [data-theme="green"] & {
    background-color:#37BC64;
  }

  // 橙色主题
  [data-theme="orange"] & {
    background-color:#FF9D14;
  }
}

// ====== 混合样式定义 end ======

如果对你有帮助,感谢采纳~

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏