当前位置: 代码迷 >> 综合 >> vue-cli3,vue+elementui实现用户自定义主题色,切换主题,(2022-12-23增加功能:后台可配置主题色以及功能色)
  详细解决方案

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

热度:83   发布时间:2023-11-29 12:42:10.0

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

  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

②新建utils文件夹。如下图 

color.js 文件内容

import color from 'css-color-function'
import formula from './formula.json'const generateColors = (type,colorval)=> {let colors = {}Object.keys(formula).forEach(key => {const value = formula[key].replace(/primary/g, colorval)colors[type+""+key] = color.convert(value)})console.log(colors)return colors
}export default generateColors

formula.json文件内容:

{"s-1": "color(primary shade(10%))","l-1": "color(primary tint(10%))","l-2": "color(primary tint(20%))","l-3": "color(primary tint(30%))","l-4": "color(primary tint(40%))","l-5": "color(primary tint(50%))","l-6": "color(primary tint(60%))","l-7": "color(primary tint(70%))","l-8": "color(primary tint(80%))","l-9": "color(primary tint(90%))"
}

③ 从 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);
}