# 一步一步实现element-theme主题切换

​ 曾经玩QQ空间,就有切换主题功能,一直想给自己的系统也加上切换主题功能,今天终于抽出时间迈出一步,查阅element主题相关文档,先根据文档进行简单操作,但是过程十分艰辛,一度把项目跑死掉(捂脸)。接下来讲讲踩坑经历。

# 踩坑

1

根据文档,模仿如上代码,代码顺利运行,且改变primary变量,确实会改变主题的颜色。于是此处算是顺利度过,接下来继续查阅文档

1

查阅到此处,以为上面生成的 element-variable.scss 文件里面的变量 就是 给上面用的,于是将此处生成的变量名都粘贴到上面的文件,结果项目都跑不起来了,报各种问题。后面继续查阅文档,才发现,是两种方式。完全不一样的。

1

原来上述生成的文件是用来编译的,编译完再引入,然后编译默认不会实时编译,如需实时编译 加上参数 -w 且编译后的目录默认是 根目录下 ./theme 文件夹 如果想自定义 可 加上 参数 -o ./src/theme

然后引入方式 改为

import "@/theme/index.css"

此处说明下 安装和跑项目会遇到的坑

WARNING

npm i element-theme -g node版本12以下

npm i element-theme-chalk -D node版本 12以上

et -i node版本 12以下

运行项目,node12以上

et -w node 12以下 然后改变主题变量值就会实时刷新

# 具体操作

  1. 切换node版本到12 以下,点击前往学习nvm 管理员方式打开终端

  2. # 全局安装 element-theme
    npm i element-theme -g 
    
  3. 切换node版本到12 以上,点击前往学习nvm 管理员方式打开终端

  4. # 在项目根目录安装 element-theme-chalk
    npm i element-theme-chalk -D
    
  5. 切换node版本到12 以下,点击前往学习nvm 管理员方式打开终端

  6. # 执行以下命令  会生成  element-variables.scss 文件
    et -i 
    
  7. 修改element-variables.scss文件变量值

  8. # 执行下列命令会生成主题css文件
    et
    
  9. 项目main.js文件引入主题文件

    import "../themes/index.css"
    

    此时文件变量就能用

  10. 但是改变变量值,不能实时刷新,且生成文件的目录不是理想的目录结构,因此可以执行下列操作

    et -w -o ./src/theme
    

    -w 可以实时实时编译,-o 可以自定义生成文件目录位置

    然后重新引入对应路径的主题文件

至此我们可以使用我们自定义的主题。但是还不能实现切换不同主题

TIP

et命令是根据element-variables.scss 文件生成主题变量文件

每当想生成不同主题的变量值时,就得改到这个文件,为了避免混淆,我们得保存主题对应的变量文件

normal-element-variables.scss

night-element-variables.scss

这样需要编辑的时候,就可以把这个文件内容覆盖element-variables.scss,确定完再把element-variables.scss文件覆盖对应主题变量文件。

# 实现切换主题功能

实现主题切换有两种方式,一种就是使用命名空间的方法,将样式文件以命名空间的形式存在,然后切换类名。

一种就是动态切换引入的主题样式文件,但是样式文件还是得使用命名空间的命名方式,因为相同类名,可能会导致样式不刷新

# 命名空间方式

  1. 使用et命令生成多套主题样式文件
  2. 使用gulp生成对应命名空间的主题变量
  3. 引入全部主题变量,切换类名即可实现主题切换

​ 命名空间方式,故名思议,就是让每个样式前面添加一个主题命名空间

.title {
	color: pink;
}

//命名空间后就变成 
.theme.title {
   color: pink; 
}

​ 那么多的样式,难道要一个一个给样式新增命名空间? 作为程序员,当然不能用那么笨的方法。我们可以借助构建工具 gulp,帮我们实现命名空间。

# 搭建gulp环境
//1.安装gulp 
npm install gulp

//2.安装gulp-clean-css
npm install gulp-clean-css

//3.安装gulp-css-wrap
npm install gulp-css-wrap
# 根目录新建一个gulpfile.js文件
//引入path
var path = require("path")
//引入gulp
var gulp = require("gulp")
//引入gulp-clean-css
var cleanCSS = require("gulp-clean-css")
//引入gulp-css-wrap
var cssWrap = require("gulp-css-wrap")

//创建一个命令(任务)  normal-theme  执行此命令,会进行如下操作
gulp.task("normal-theme", function() {

  //导入文件
  return gulp.src(path.resolve("./src/themes/normal/index.css"))
    /* 找需要添加命名空间的css文件,支持正则表达式 */
    .pipe(cssWrap({
      selector: ".bu-normal" /* 添加的命名空间 */
    }))
    .pipe(cleanCSS())
    .pipe(gulp.dest("./src/styles/themes/normal")) /* 存放的目录 */
})

//把字体文件也导入
gulp.task("normal-theme-font", function() {
  return gulp.src(["./src/themes/normal/fonts/**"]).pipe(gulp.dest("./src/styles/themes/normal/fonts"))
})

//创建一个命令(任务)  normal-theme  执行此命令,会进行如下操作
gulp.task("night-theme", function() {
  //导入文件
  return gulp.src(path.resolve("./src/themes/night/index.css"))
    /* 找需要添加命名空间的css文件,支持正则表达式 */
    .pipe(cssWrap({
      selector: ".bu-night" /* 添加的命名空间 */
    }))
    .pipe(cleanCSS())
    .pipe(gulp.dest("./src/styles/themes/night")) /* 存放的目录 */
})

//把字体文件也导入
gulp.task("night-theme-font", function() {
  return gulp.src(["./src/themes/night/fonts/**"]).pipe(gulp.dest("./src/styles/themes/night/fonts"))
})

执行上述命令,就会生成两套命名空间的样式。

# 使用主题
computed: {
    //获取当前主题
    theme() {
      return this.$store.state.setting.theme
    }
  },
  watch: {
    theme: {
      handler(val) {
        //监听主题,然后设置主题命名空间 的类名
        const body = document.getElementsByTagName("body")
        body[0].className = val
      },
      immediate: true
    }
  },

这样就可以切换element-ui的主题了。但是对于普通我们自定义的变量,还不能跟随主题变换,这个最后面一起说。

# 动态添加主题css样式文件

对于需要很多套主题的情况下,上面的方式就会把这两个主题都打包在了项目里,如果你的项目主题需要七八种,这种模式就不适合了。我们就需要动态的加载css。
# 生成多套主题文件

​ 生成多套主题文件,并执行命令生成相应命名空间方式的文件,然后放到服务器供链接访问。

# 使用
computed: {
    theme() {
      return this.$store.state.setting.theme
    }
  },
  watch: {
    theme: {
      handler(val) {
         const body = document.getElementsByTagName("body")
        body[0].className = val
          
        const theme = val
        this.setHeadStyle(theme)
      },
      immediate: true
    }
  },
  methods: {
    /**
     * @description: 动态加载主题样式
     * @return {*}
     * @author: syx
     */
    setHeadStyle(themeName) {
      var head = document.getElementsByTagName("HEAD").item(0)
      var style = document.createElement("link")
      //给样式添加一个id 用于切换主题时删除标签
      style.id = "theme_link"
      style.href = `服务器访问路径${themeName}/index.css`
      style.rel = "stylesheet"
      style.type = "text/css"

      const themeLinkTag = document.getElementById("theme_link")
      if (themeLinkTag) {
        head.removeChild(themeLinkTag)
      }
      head.appendChild(style)
    }
  }

这样就可以实现多套element-ui主题的动态切换了。

# 最全样式切换

​ 上面说的切换主题仅仅只是切换了 element-ui的主题。对于我们自定义的变量,并不会切换,所以我们要将自己的主题变量也按不同主题来控制。

​ 因此我们需要用到强大的scss。

目录结构大致如下

src

styles

themes //主题文件夹

index.scss //引入所有主题

normal.scss //正常模式主题变量

night.scss //夜间模式主题变量

themes //其实把这主题放在服务器上,项目以链接方式访问更ok

night //基于element-variables.scss 生成的主题样式文件

night-gulp //经过gulp命令生成的命门空间样式文件

normal //基于element-variables.scss 生成的,然后使用gulp构建的命名空间样式

normal-gulp //经过gulp命令生成的命门空间样式文件

// /src/styles/themes/index.scss

@import "./night.scss";
@import "./normal.scss";
$themes: (
  normal: $normal_theme,
  night: $night_theme
);

//遍历主题map
@mixin themeify {
	@each $theme-name,
	$theme-map in $themes {
		//global 把局部变量强升为全局变量
		$theme-map: $theme-map !global;
 
		//判断html的data-theme的属性值  #{}是sass的插值表达式
		//& sass嵌套里的父容器标识   @content是混合器插槽,像vue的slot
		[data-theme="#{$theme-name}"] & {
			@content;
		}
	}
}
 
//声明一个根据Key获取颜色的function
@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;
	}
}
 
//获取边框颜色
@mixin border_color($color) {
	@include themeify {
		border-color: themed($color) !important;
	}
}
//获取边框颜色
@mixin border_line_color($color,$num:1px) {
	@include themeify {
		border: $num  solid themed($color) !important;
	}
}
// 阴影
@mixin box-shadowr($shadows,$color) {
	@include themeify {
		box-shadow: $shadows themed($color) !important;
	}
}

//还可以自定义其他属性....

// /src/styles/themes/normal.scss
$night_theme: (
  main_color: #217acb,
}
// /src/styles/themes/night.scss
$night_theme: (
  main_color: #001e3d,
}

然后加载项目时先加载主题变量

//vue.config.js
module.exports = {
  css: {
    // 是否使用css分离插件 ExtractTextPlugin
    //如果需要css热更新就设置为false,打包时候要改为true
    extract: false,
    // 开启 CSS source maps?
    sourceMap: true,
    // css预设器配置项
    loaderOptions: {
      sass: {
        // @/ is an alias to src/
        // so this assumes you have a file named `src/variables.scss`
        prependData: `@import "@/styles/themes/index.scss";`
      }
    }
  },
}

页面上使用主题变量

//设置背景颜色
@include background_color("main_color");
//设置字体颜色
@include font_color("main_color");
//设置边框颜色
@include border_color("main_color");
//设置 1px边框   1px可不传 或传 其他像素值
@include border_line_color("main_color", "1px");
//设置 阴影
@include shadowr("main_color");

在store状态管理器新增一个theme主题变量值

<template>
  <div id="app">
    <!-- 页面切换容器 -->
    <router-view />
  </div>
</template>

<script>

export default {
  name: "App",
  computed: {
    theme() {
      return this.$store.state.setting.theme
    }
  },
  watch: {
    theme: {
      handler(val) {
        //命名空间切换样式类
        const body = document.getElementsByTagName("body")
        body[0].className = val
        
        const theme = val
        this.setHeadStyle(theme)
        window.document.documentElement.setAttribute("data-theme", theme)
      },
      immediate: true
    }
  },
  methods: {
    /**
     * @description: 动态加载主题样式
     * @return {*}
     * @author: syx
     */
    setHeadStyle(themeName) {
      var head = document.getElementsByTagName("HEAD").item(0)
      var style = document.createElement("link")
      style.id = "theme_link"
      style.href = require(`@/themes/${themeName}-gulp/index.css`)
      style.rel = "stylesheet"
      style.type = "text/css"

      const themeLinkTag = document.getElementById("theme_link")
      if (themeLinkTag) {
        head.removeChild(themeLinkTag)
      }
      head.appendChild(style)
    }
  }
}
</script>

<style>
#app {
}
</style>

//引入path
var path = require("path")
//引入gulp
var gulp = require("gulp")
//引入gulp-clean-css
var cleanCSS = require("gulp-clean-css")
//引入gulp-css-wrap
var cssWrap = require("gulp-css-wrap")

//创建一个命令(任务)  normal-theme  执行此命令,会进行如下操作
gulp.task("normal-theme", function() {
  //导入文件
  return gulp.src(path.resolve("./src/themes/normal/index.css"))
    /* 找需要添加命名空间的css文件,支持正则表达式 */
    .pipe(cssWrap({
      selector: ".normal" /* 添加的命名空间 */
    }))
    .pipe(cleanCSS())
    .pipe(gulp.dest("./src/themes/normal-gulp")) /* 存放的目录 */
})

//把字体文件也导入
gulp.task("normal-theme-font", function() {
  return gulp.src(["./src/themes/normal/fonts/**"]).pipe(gulp.dest("./src/themes/normal-gulp/fonts"))
})

//创建一个命令(任务)  normal-theme  执行此命令,会进行如下操作
gulp.task("night-theme", function() {
  //导入文件
  return gulp.src(path.resolve("./src/themes/night/index.css"))
    /* 找需要添加命名空间的css文件,支持正则表达式 */
    .pipe(cssWrap({
      selector: ".night" /* 添加的命名空间 */
    }))
    .pipe(cleanCSS())
    .pipe(gulp.dest("./src/themes/night-gulp")) /* 存放的目录 */
})

//把字体文件也导入
gulp.task("night-theme-font", function() {
  return gulp.src(["./src/themes/night/fonts/**"]).pipe(gulp.dest("./src/themes/night-gulp/fonts"))
})

WARNING

执行gulp命令时,别忘了同时字体文件也要执行一下。

gulp normal-theme

gulp normal-theme-font

gulp 还得在 node12版本以下运行

TIP

上述目录结构不是您喜欢的也可修改代码,生成别的目录结构

# 总结

​ 实践是检验真理的唯一标准,坚持就是胜利。珍爱头发,原理编程。