vue+element 自定义主题

593 阅读3分钟

需求背景:项目需要支持动态换主题,elementui仅需动态替换主题色

大概的思路就是给html根标签设置一个data-theme属性,然后通过js切换data-theme的属性值,scss根据此属性来判断使用对应主题变量。这里可以选择持久化Vux或接口来保存用户选择的主题。 elementui则参考ThemePicker组件 实现定制主题色

1.主题

1.新建一个Scss文件_themes.scss,里面可以配置不同的主题配色方案

$orange-theme: (
  //主题色
  main-color: #e6a23c,
  //文字颜色
  text-color: #999999,
  //顶部背景颜色
  top-background: #ffffff,
  //左菜单栏背景色
  sidebar-bg: #ffffff,
);

//蓝色主题
$blue-theme: (
  main-color: #409EFF,
  text-color: #999999,
  top-background: #ffffff,
  sidebar-bg: #ffffff,
);
//定义映射集合
$themes: (
  orange-theme: $orange-theme,
  blue-theme: $blue-theme,
);

2.定义操作,可另外写个scss或者写在一起,上代码:

@mixin themeify {
  @each $theme-name, $theme-map in $themes {
    $theme-map: $theme-map !global;
    [data-theme="#{$theme-name}"] & {
      @content;
    }
  }
}
//从主题色map中取出对应颜色
@function themed($key) {
  @return map-get($theme-map, $key);
}

//获取背景颜色
@mixin background_color($color) {
  @include themeify {
    background-color: themed($color) !important;
  }
}

//获取字体颜色
@mixin font_color($color) {
  @include themeify {
    color: themed($color) !important;
  }
}

可根据需求自己往里面加方法

3.具体在vue中使用,直接引入对应混入器就好,取哪个颜色,传哪个key

<style lang="scss" scoped>
@import '~@/styles/theme/theme.scss';
.main-container{
    @include background_color("main-color");
}
</style>

根据自己的路径引入

4.使用js动态切换HTML的属性data-theme的值

关键代码

window.document.documentElement.setAttribute( "data-theme", "blue-theme" );

切换后会发现你的css选择器前面多了一些东西[data-theme="blue-theme"]

2.elementui 主题色切换

这里仅需替换主题色所以采用最简单的方式,参考 vue-element-admin 该项目里面的 ThemePicker组件 可以直接实现该功能,但仔细观察代码后,发现它需要发请求获取到Element UI 的默认样式 index.css,所以需要改一下; 由于项目使用的Element UI的版本是唯一的,直接把 这个样式代码拷贝出来就行了,然后再对其进行操作; index.css 文件路径: node_modules\element-ui\lib\theme-chalk\index.css

1.定义eleUiCss

export default=`此处为拷贝elementui样式`

2。主要实现

/**
 * 获取一系列 主题色
 * 入参:67c23a
 * 结果:['67c23a', '103,194,58', '#76c84e', '#85ce61', '#95d475', '#a4da89', '#b3e19d', '#c2e7b0', '#d1edc4', '#e1f3d8', '#f0f9eb', '#5daf34']
 */
function getThemeCluster(theme, type = '') {
    const tintColor = (color, tint) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)

        if (tint === 0) {
            // when primary color is in its rgb space
            return [red, green, blue].join(',')
        } else {
            red += Math.round(tint * (255 - red))
            green += Math.round(tint * (255 - green))
            blue += Math.round(tint * (255 - blue))

            red = red.toString(16)
            green = green.toString(16)
            blue = blue.toString(16)

            return `#${red}${green}${blue}`
        }
    }

    const shadeColor = (color, shade) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)

        red = Math.round((1 - shade) * red)
        green = Math.round((1 - shade) * green)
        blue = Math.round((1 - shade) * blue)

        red = red.toString(16)
        green = green.toString(16)
        blue = blue.toString(16)

        return `#${red}${green}${blue}`
    }

    const clusters = [theme]
    for (let i = 0; i <= 9; i++) {
        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
    }
    clusters.push(shadeColor(theme, 0.1))
    return clusters
}

/**
 * @param style 老的css样式代码
 * @param oldCluster 老的一些列主题色  待替换
 * @param newCluster 新的一系列主题色  替换成
 *
 * @returns newStyle 新的 css样式代码 替换后的
 */
function updateStyle(style, oldCluster, newCluster) {
    let newStyle = style
    oldCluster.forEach((color, index) => {
        // 将老颜色替换成新颜色
        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index].trim()) // 全局替换 不区分大小写 去掉多余空格
    })
    return newStyle
}


import ElementUiCSS from './eleUiCss.js' 
export const updateThemeColor = function (val) {
    if (typeof val !== 'string' || val.length === 0) return
    const ORIGINAL_THEME = `#409EFF`// default color (element ui的默认主题色,所有我们根据这个去改)
    const ThemeCode = ElementUiCSS
        .replace(/@font-face{[^}]+}/g, '') // 去掉字体样式
        .replace(/.el-icon-[a-zA-Z0-9-:^]+before{content:"[^}]+}/g, '') // 去掉图标样式
    // 得到一系列 主题色颜色 (我们需要的颜色 '产出')
    const themeCluster = getThemeCluster(val.replace('#', ''), 'new')
    /**
     * 入参:'chalk'(旧css代码), 'chalk-style'(style的id)
     * 直接 将老的 css 代码里 待改的旧颜色改成 新颜色 然后将新的样式 插入到head标签里
     */
    const getHandler = id => {
        return () => {
            // 得到一系列 主题色颜色 (原始的一些列颜色  待改)
            const originalCluster = getThemeCluster(ORIGINAL_THEME.replace('#', ''))
            const newStyle = updateStyle(ThemeCode, originalCluster, themeCluster)
            let styleTag = document.getElementById(id)
            if (!styleTag) {
                styleTag = document.createElement('style')
                styleTag.setAttribute('id', id)
                document.head.appendChild(styleTag)
            }
            styleTag.innerText = newStyle
        }
    }
    const chalkHandler = getHandler('chalk-style')
    chalkHandler()
}

3.调用方法

updateThemeColor(themeColor)

至此完工!