Vue项目利用SCSS实现一键换肤

1,111 阅读2分钟

1. 环境准备

安装scss解析环境:

npm i sass
// 注意 sass-loader 安装需要指定版本 如果安装最新版本会报错 this.getOptions 这个方法未定义 
npm i -D sass-loader@10.1.0 
// 利用 normalize.css 初始化页面样式 
npm i -S normalize.css

vue.config.js 配置项处理:

module.exports = {
  ...
  css: {
    // 是否使用css分离插件 ExtractTextPlugin
    extract: true,
    // 开启 CSS source maps?
    sourceMap: false,
    // css预设器配置项
    loaderOptions: {
      scss: {
        additionalData: (content) => {
          // 注入全局, 在每个vue文件中都会自动引入,方便做主题切换
          let filePath = ''
          filePath += `@import "@/styles/variables.scss"; `
          return filePath + content
        }
      }
    }
  },
}

定义主题

在src/styles/theme下编定义主题:

image.png

src/styles/theme/main.scss定义整体风格(深色、浅色):

/**      整体风格-深色        */
$main-dark: (
  header-background: #1B213B,
  header-color: #FFFFFF,
  menu-background: #31374E,
  menu-text: #fff,
  menu-bborder: 1px solid rgba(70, 75, 96, .88),
  menu-hover: #141826,
  menu-active: #141826,
  sidebar-close-icon: #FFFFFF,
  sidebar-text: #FFFFFF,
  sidebar-submenu3-bg: #31374E,
  sidebar-submenu1-bborder: 1px solid rgba(255, 255, 255, .16)
);

/**     整体风格-浅色         */
$main-light: (
  header-background: #FFFFFF,
  header-color: #333333,
  menu-background: #FFFFFF,
  menu-text: #333333,
  menu-bborder: 1px solid rgba(221, 221, 221, .88),
  menu-hover: #F0F0F0,
  menu-active: #F0F0F0,
  sidebar-close-icon: #333333,
  sidebar-text: #333333,
  sidebar-submenu3-bg: #D3D4D8,
  sidebar-submenu1-bborder: 1px solid #DDDDDD
);

src/styles/theme/color.scss定义主题色(红色、蓝色):

/**      主题色-红色        */
$color-red: (
  submenu-active: linear-gradient(135deg, #F55448 0%, #FF8076 100%),
  sidebar-text1: #F55448,
  menu-background-imag: url('~@/assets/theme/menuBgRed.png') no-repeat bottom,
  button-color: #4E5A70,
  tab-active-color: #F55448
);

/**      主题色-蓝色        */
$color-blue: (
  submenu-active: linear-gradient(135deg, #356FFB 0%, #6E98FF 100%),
  sidebar-text1: #2D82F9,
  menu-background-imag: url('~@/assets/theme/menuBgBlue.png') no-repeat bottom,
  button-color: #2D82F9,
  tab-active-color: #2D82F9
);

src/styles/theme/mixed.scss定义混合样式(整体风格和主题色共同决定的样式):

// 深-红
$dark-red: (
  more-menu: url('~@/assets/theme/dark-red/more.png')
);
// 深-蓝
$dark-blue: (
  more-menu: url('~@/assets/theme/dark-blue/more.png')
);
// 浅-红
$light-red: (
  more-menu: url('~@/assets/theme/light-red/more.png')
);
// 浅-蓝
$light-blue: (
  more-menu: url('~@/assets/theme/light-blue/more.png')
);

src/styles/theme/index.scss定义皮肤:

@import "./main.scss";
@import "./color.scss";
@import "./mixed.scss";

// 整体风格
$main-themes: (
  dark: $main-dark,
  light: $main-light
);
// 主题颜色
$color-themes: (
  red: $color-red,
  blue: $color-blue
);
// 多个主题共同控制的样式
$mixed-themes: (
  dark-red: $dark-red,
  dark-blue: $dark-blue,
  light-red: $light-red,
  light-blue: $light-blue
);


$theme-map: null;

// 定义混合指令, 切换主题,并将主题中的所有规则添加到theme-map中
@mixin themify() {

  // 将main-themes中规则放入theme-map
  @each $theme-name, $map in $main-themes {
    // & 表示父级元素  !global 表示覆盖原来的
    [main-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      // 根据主题命名,循环出对应作用域下的样式键值对
      @each $key, $value in $map {
        $theme-map: map-merge(
          $theme-map,
          (
            $key: $value,
          )
        ) !global;
      }
      // 表示包含下面函数 themed(), 类似于插槽
      @content;
    }
  }

  // 将color-themes中规则放入theme-map
  @each $theme-name, $map in $color-themes {
    [color-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      @each $key, $value in $map {
        $theme-map: map-merge(
          $theme-map,
          (
            $key: $value,
          )
        ) !global;
      }
      @content;
    }
  }

  // 将mixed-themes中规则放入theme-map
  @each $theme-name, $map in $mixed-themes {
    [mixed-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      @each $key, $value in $map {
        $theme-map: map-merge(
          $theme-map,
          (
            $key: $value,
          )
        ) !global;
      }
      @content;
    }
  }
}

@function themed($key) {
  @return map-get($theme-map, $key);
}

切换主题

image.png

定义切换组件,并绑事件:

<template>
    <div class="style-conf-container">
        <div class="style-conf-item-theme">
          <div>
            <i class="theme-color-item red-theme" @click.stop="changeThemeColor('main', 'light')" />
            <i class="theme-color-item blue-theme" @click.stop="changeThemeColor('main', 'dark')" />
          </div>
        </div>
        <div class="style-conf-item theme-color-bar">
          <span>{{ $t('navbar.theme') }}</span>
          <div>
            <i class="theme-color-item red-theme" @click.stop="changeThemeColor('color', 'red')" />
            <i class="theme-color-item blue-theme" @click.stop="changeThemeColor('color', 'blue')" />
          </div>
        </div>
      </div>
</template>

<script>
    changeThemeColor(flag, type) {
      document.documentElement.setAttribute('main-theme', this.mainTheme)
      document.documentElement.setAttribute('color-theme', this.colorTheme)
      document.body.setAttribute('mixed-theme', this.mainTheme + '-' + this.colorTheme)
    }
</scrtpt>

在需要跟随皮肤切换样式的页面中使用主题变量,如下:

<style lang="scss" scoped>
@import "~@/styles/theme/index.scss";

.header {
  width: 100%;
  height: 48px;
  // 使用主题
  @include themify() {
    background: themed("header-background");
    color: themed("header-color");
  }
  box-shadow: 0 1px 4px rgba(0,21,41,.08);
}
</style>

实现效果

深-红主题: image.png

image.png 浅-蓝主题: image.png

image.png

第一次写文章,请大家多多提意见哈~ 有问题也欢迎大家留言,我会及时回复噢~