vue-cli3,vue2+elementui实现用户自定义主题色,切换主题,(2023-02-10增加功能:后台可配置主题色以及功能色)

自定义主题色包含以下两种,并且这两种的主题色需要同步

  1. element-ui的主题色
  2. 程序员自己写的样式中的主题色

我参考

  1. 一篇博客:vue,elementUI切换主题,自定义主题 - 小兔额乖乖 - 博客园
  2. 一个开源框架:腾讯的tDesign搭建的开源框架——TDesign Vue Next Starter

输出了此篇文章

说明1:小兔额乖乖文章中的项目示例,在修改主题色后,刷新一下,鼠标hover时会有问题,此问题在本篇文章中已经解决

说明2:我曾经尝试完全模仿腾讯那个框架的思路。但是此思路不适用于element-ui这个框架,会报错

说明3:下面的正文中,项目中修改主题色时,会运行  document.head.appendChild(style)  ,修改一次颜色就会添加一个stylesheet,有兴趣的同学可以优化一下,改成替换掉上一次的style。

 以下是正文

---------------------------------------------------------------------------------------------------------------------------------

1、创建一个vue项目

① vue-cli3创建项目 

vue create vue-custom-theme

② 安装element-ui相关

安装elementUI

npm i element-ui -S

安装sass  

npm install node-sass@4.11.0 sass-loader@7.1.0 -D

 启动项目

npm run serve

2、引入element-ui

①在main,js中

import ElementUI from 'element-ui'
import './styles.scss'
Vue.use(ElementUI)

②给一个默认的主题色【此步骤可省略,不需要。因为可以在Theme.vue中直接从后台获取默认主题(this.$store.state.sysConfig.theme)。this.$store.state.sysConfig.theme是在后台获取的,存储在了vuex里面,获取时机是全局路由拦截,在进入页面之前获取的】

用户先从线上拉去配置主题的css文件,当用户更改颜色后,在把css文件里面所有的颜色值替换掉,然后把这个css文件重新插入到html中达到改变颜色。

在这里都需要修改再方法1的基础上进行扩展:在element-variables.scss(大部分项目都是)添加 默认我们自己设置的颜色。

当然这个颜色也可以在其他公共css修改。我是在style.scss中修改的,如下

style.scss

/* theme color */
$--color-primary: #ff6f4b;

/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

3、主题色修改

①安装两个插件

npm install css-color-function  

npm install object-assign

③ 从 unpkg.com/element-ui/lib/theme-chalk/index.css 把最新css文件复制下来copy到项目静态文件目录中:

④  接下来就是写代码了。在App.vue上引入自定义的修改主题组件,再随便弄些element组件观察变化:

 ⑤写组件:Theme.vue

<!-- 切换主题色  -->
<template>
  <div class="handle-theme">
    <el-color-picker
      @change="colorChange"
      v-model="colors.primary"
    ></el-color-picker>
  </div>
</template>
<script>
import generateColors from "@/utils/color";
import objectAssign from 'object-assign';
export default {
  name: 'App',
  data() {
    return {
      originalStylesheetCount: -1, // 记录当前已引入style数量
      originalStyle: '', // 获取拿到的.css的字符串
      colors: {
   
        primary: '#ff6f4b',// this.$store.state.sysConfig.theme,
        success: '#00c5bd',
        warning: '#f39800',
        danger: '#f56c6c',
        info: '#909399'
      },
      // primaryColor: "", //提交成功后设置默认颜色
      cssUrl: [
        '//unpkg.com/element-ui/lib/theme-chalk/index.css',
        process.env.BASE_URL + 'static/css/index.css'
      ]
    };
  },
  methods: {
    initTheme() {
      // 默认从线上官方拉取最新css,2秒钟后做一个检查没有拉到就从本地在拉下
      // 如果是记住用户的状态就需要,在主题切换的时候记录颜色值,在下次打开的时候从新赋值
      this.colors.primary =  localStorage.getItem('color') || this.colors.primary; // 例如
      // this.getIndexStyle(this.cssUrl[0]);
      // setTimeout(() =>{
      //   if (this.originalStyle) {
      //     return;
      //   } else {
      this.getIndexStyle(this.cssUrl[1]);
      //   }
      // }, 2000);
      this.$nextTick(() => {
        // 获取页面一共引入了多少个style 文件
        this.originalStylesheetCount = document.styleSheets.length;
      });
    },
    colorChange(e) {
      if (!e) return;
      this.primaryColor = e;
      localStorage.setItem('color', e);

      let primarySeries = this.generateColors('p', e)
      let successSeries = this.generateColors('s', this.colors.success)
      let warningSeries = this.generateColors('w', this.colors.warning)
      let dangerSeries = this.generateColors('d', this.colors.danger)
      let infoSeries = this.generateColors('i', this.colors.info)

      this.colors = objectAssign(
        {},
        this.colors,
        primarySeries,
        successSeries,
        warningSeries,
        dangerSeries,
        infoSeries
      );
      this.writeNewStyle();
    },
    writeNewStyle() {
      let cssText = this.originalStyle;
      Object.keys(this.colors).forEach((key) => {
        cssText = cssText.replace(
          new RegExp('(:|\\s+)' + key, 'g'),
          '$1' + this.colors[key]
        );
      });
      const style = document.createElement('style');
      style.innerText = `
                    :root{
                        --primary-color: ${this.colors.primary};
                        --primary-color-light-1:${this.colors['pl-1']};
                        --primary-color-light-2:${this.colors['pl-2']};
                        --primary-color-light-3:${this.colors['pl-3']};
                        --primary-color-light-4:${this.colors['pl-4']};
                        --primary-color-light-5:${this.colors['pl-5']};
                        --primary-color-light-6:${this.colors['pl-6']};
                        --primary-color-light-7:${this.colors['pl-7']};
                        --primary-color-light-8:${this.colors['pl-8']};
                        --primary-color-light-9:${this.colors['pl-9']};
                    }${cssText}`;
      // ":root{--primary-color:" + this.colors.primary + "}" + cssText;
      document.head.appendChild(style);
      // if (this.originalStylesheetCount === document.styleSheets.length) {
      //     // 如果之前没有插入就插入
      //     const style = document.createElement("style");
      //     style.innerText =
      //         ":root{--primary-color:" + this.colors.primary + "}" + cssText;
      //     document.head.appendChild(style);
      // } else {
      //     // 如果之前没有插入就修改
      //     document.head.lastChild.innerText =
      //         ":root{--primary-color:" +
      //         this.colors.primary +
      //         "} " +
      //         cssText;
      // }
    },
    getIndexStyle(url) {
      var request = new XMLHttpRequest();
      request.open('GET', url);
      request.onreadystatechange = () => {
        if (
          request.readyState === 4 &&
          (request.status == 200 || request.status == 304)
        ) {
          // 调用本地的如果拿不到会得到html,html是不行的
          if (request.response && !/DOCTYPE/gi.test(request.response)) {
            this.originalStyle = this.getStyleTemplate(request.response);
            this.colorChange(this.colors.primary);
          } else {
            this.originalStyle = '';
          }
        } else {
          this.originalStyle = '';
        }
      };
      request.send(null);
    },
    getStyleTemplate(data) {
       const colorMap = {
        // "#3a8ee6": "shade-1",
        // "#409eff": "primary",
        // "#53a8ff": "light-1",
        // "#66b1ff": "light-2",
        // "#79bbff": "light-3",
        // "#8cc5ff": "light-4",
        // "#a0cfff": "light-5",
        // "#b3d8ff": "light-6",
        // "#c6e2ff": "light-7",
        // "#d9ecff": "light-8",
        // "#ecf5ff": "light-9",

        '#3a8ee6': 'ps-1',
        '#409eff': 'primary',
        '#53a8ff': 'pl-1',
        '#66b1ff': 'pl-2',
        '#79bbff': 'pl-3',
        '#8cc5ff': 'pl-4',
        '#a0cfff': 'pl-5',
        '#b3d8ff': 'pl-6',
        '#c6e2ff': 'pl-7',
        '#d9ecff': 'pl-8',
        '#ecf5ff': 'pl-9',

        '#5daf34': 'ss-1', // is-plain:active
        '#67c23a': 'success', // success
        '#76c84e': 'sl-1', // 没有
        '#85ce61': 'sl-2', // hover color
        '#95d475': 'sl-3', // 没有
        '#a4da89': 'sl-4', // is-disabled hover color
        '#b3e19d': 'sl-5', // is-disabled color
        '#c2e7b0': 'sl-6', // border-color
        '#d1edc4': 'sl-7', // 没有
        '#e1f3d8': 'sl-8', // light
        '#f0f9eb': 'sl-9', // lighter

        '#cf9236': 'ws-1', // is-plain:active
        '#E6A23C': 'warning', // success
        '#e9ab50': 'wl-1', // 没有
        '#ebb563': 'wl-2', // hover color
        '#eebe77': 'wl-3', // 没有
        '#f0c78a': 'wl-4', // is-disabled hover color
        '#f3d19e': 'wl-5', // is-disabled color
        '#f5dab1': 'wl-6', // border-color
        '#f8e3c5': 'wl-7', // 没有
        '#faecd8': 'wl-8', // light
        '#fdf6ec': 'wl-9', // lighter

        '#dd6161': 'ds-1', // is-plain:active
        '#F56C6C': 'danger', // success
        '#f67b7b': 'dl-1', // 没有
        '#f78989': 'dl-2', // hover color
        '#f89898': 'dl-3', // 没有
        '#f9a7a7': 'dl-4', // is-disabled hover color
        '#fab6b6': 'dl-5', // is-disabled color
        '#fbc4c4': 'dl-6', // border-color
        '#fcd3d3': 'dl-7', // 没有
        '#fde2e2': 'dl-8', // light
        '#fef0f0': 'dl-9', // lighter

        '#82848a': 'is-1', // is-plain:active
        '#909399': 'info', // success
        '#9b9ea3': 'il-1', // 没有
        '#a6a9ad': 'il-2', // hover color
        '#b1b3b8': 'il-3', // 没有
        '#bcbec2': 'il-4', // is-disabled hover color
        '#c8c9cc': 'il-5', // is-disabled color
        '#d3d4d6': 'il-6', // border-color
        '#dedfe0': 'il-7', // 没有
        '#e9e9eb': 'il-8', // light
        '#f4f4f5': 'il-9'// lighter
      };
      Object.keys(colorMap).forEach((key) => {
        const value = colorMap[key];
        data = data.replace(new RegExp(key, 'ig'), value);
      });
      return data;
    }
  },
  mounted() {
    this.initTheme();
    // 默认从线上官方拉取最新css,2秒钟后做一个检查没有拉到就从本地在拉下
    // let that = this;
    // 如果是记住用户的状态就需要,在主题切换的时候记录颜色值,在下次打开的时候从新赋值
    // this.colors.primary = localStorage.getItem('color')||this.colors.primary//例如

    // setTimeout(function() {
    //     if (that.originalStyle) {
    //         return;
    //     } else {
    //         that.getIndexStyle(that.cssUrl[1]);
    //     }
    // }, 2000);
    // this.$nextTick(() => {
    //     // 获取页面一共引入了多少个style 文件
    //     this.originalStylesheetCount = document.styleSheets.length;
    // });
  }
};
</script>
<style lang="scss">

</style>

使用

.boxDiv{
  padding:40px;
  background: var(--primary-color);
  box-shadow:  0 0 10px 8px var(--primary-color-light-8);
}