切换elementUI主题的公共组件

实现的功能

1.切换皮肤后,登录页显示不同的背景图片,字体颜色,以及选中状态的背景颜色。

2.引入的elementUI组件,也随之切换对应的主题颜色。

3.通过vuex状态管理器,储存当前所选的主题属性(包括背景颜色,字体颜色,选中状态颜色,等等。可以根据不同需求进行配置)。在任一组件中,都可以通store调用主题属性。并且,在css文件中可以直接调用主题属性。

页面-2-首页1.png

页面-2-首页2.png

切换主题的SwitchSkin组件代码

<template>
  <!-- 主题样式选择器 -->
  <div class="theme-picker">
    <div
      class="selected-theme webkit-transition"
      :style="{ background: selectedColor || themeSelectedColor }"
    >
      <div class="logo">
        <img src="static/img/theme/skin.png" alt="" />
      </div>
      <div class="text">切换皮肤</div>
    </div>
    <div
      v-for="(item, index) in themeColorList"
      :key="index"
      class="theme-item"
      @click="themeChange(item.color)"
    >
      <div class="bg" :style="{ background: item.color + '4d' }" />
      <div class="color" :style="{ background: item.color }">
        <img
          v-show="item.color == selectedColor"
          src="static/img/theme/check.png"
          alt=""
        />
      </div>
      <div class="text">
        {{ item.text }}
      </div>
    </div>
  </div>
</template>
​
<script>
import { ThemeChangeMixin } from "@/mixins/ThemeChangeMixin";
export default {
  name: "",
  components: {},
  mixins: [ThemeChangeMixin],
  props: {},
  data() {
    return {};
  },
  computed: {},
  created() {},
  mounted() {},
  methods: {},
};
</script>
<style lang="scss" scoped>
/* 主题样式选择 */
.theme-picker {
  position: absolute;
  right: 0;
  width: 40px;
  height: 40px;
  top: 80px;
  font-size: 14px;
  border-radius: 7px 0 0 7px;
  overflow: hidden;
  -webkit-transition: all 1s ease 0.1s;
  -o-transition: all 1s ease 0.1s;
  -moz-transition: all 1s ease 0.1s;
  transition: all 1s ease 0.1s;
  &:hover {
    width: 118px;
    right: 0;
    height: 120px;
    .selected-theme {
      .text {
        display: block;
      }
    }
  }
  .selected-theme {
    display: flex;
    align-items: center;
    height: 40px;
    cursor: pointer;
    .logo {
      width: 16px;
      height: 16px;
      margin: 3px 9px 0 12px;
      -webkit-animation: swing 3s;
      -webkit-animation-iteration-count: 3;
      -webkit-animation-delay: 3s;
    }
    .text {
      display: none;
      color: #fff;
      width: 100%;
      text-align: left;
    }
  }
  .theme-item {
    display: flex;
    align-items: center;
    height: 40px;
    border: 1px solid #e5e5e5;
    background: #fff;
    cursor: pointer;
    &:hover {
      // background: #e4e6f4 !important;
      .bg {
        opacity: 1;
      }
    }
    &:last-child {
      border: none;
    }
    .bg {
      height: 40px;
      width: 100%;
      position: absolute;
      opacity: 0;
    }
    .color {
      width: 16px;
      height: 16px;
      margin: 0 9px 0 12px;
      padding: 8px;
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 4px;
      img {
        width: 11px;
        height: 10px;
        display: block;
        position: absolute;
        left: 3px;
        top: 3px;
      }
    }
    .text {
      color: #757575;
      width: 100%;
      text-align: left;
    }
  }
}
</style>
复制代码

css3属性渐变

鼠标移到选择器上,主题选择的下拉框展开。通过css3 -webkit-transition(属性渐变)来实现,呈现一个柔和的渐变效果。

.theme-picker {
  position: absolute;
  right: 0;
  width: 40px;
  height: 40px;
  top: 80px;
  font-size: 14px;
  border-radius: 7px 0 0 7px;
  overflow: hidden;
  -webkit-transition: all 1s ease 0.1s;
  -o-transition: all 1s ease 0.1s;
  -moz-transition: all 1s ease 0.1s;
  transition: all 1s ease 0.1s;
  &:hover {
    width: 118px;
    right: 0;
    height: 120px;
    .selected-theme {
      .text {
        display: block;
      }
    }
  }
}
复制代码

混入(mixin)文件

import { ThemeChangeMixin } from "@/mixins/ThemeChangeMixin";
export default {
  name: "",
  components: {},
  mixins: [ThemeChangeMixin],
  props: {},
  data() {
    return {};
  },
  computed: {},
  created() {},
  mounted() {},
  methods: {},
};
复制代码

主题皮肤的数据,以及切换主题的方法,在ThemeChangeMixin文件中。作为混入(mixin)对象,在切换主题的组件中使用。

关于混入(mixin)官网有详尽讲解

cn.vuejs.org/v2/guide/mi…

主题选项的数组themeColorList

 themeColorList: [
      {
        color: '#3E50B3',
        text: '蓝色',
        skin: {
          themeImg: 'blue',
          themeBgColor: '#D7DBEA',
          themeNavColor: '#6573C4',
          botColor: 'white'
        }
      },
      {
        color: '#CD2E18',
        text: '红色',
        skin: {
          themeImg: 'red',
          themeBgColor: '#FAEAE8',
          themeNavColor: '#DA523E',
          botColor: 'black'
        }
      }
]
复制代码

Vuex状态管理器,切换主题

切换主题的方法如下。切换主题后,通过状态管理器Vuex改变当前的主题状态。

 /* 切换主题 */
themeChange(val) {
  if (this.selectedColor == val) return
  this.selectedColor = val
  this.$store.dispatch('settings/changeSetting', {
    key: 'theme',
    value: val
  })
  const list = this.themeColorList
  const idx = list.findIndex((item) => item.color == val)
  Object.keys(list[0]['skin']).forEach((item) => {
    const value = list[idx]['skin'][item]
    this.$store.dispatch('settings/changeSetting', {
      key: item,
      value
    })
  })
  this.changeElementTheme(val)
},
复制代码

Vuex相关store文件夹下的modules里settings.js文件,配置了主题相关的state,以及mutations和actions对象。

关于vuex配置,我专门有一篇文章讲的很详尽,不再啰嗦。

const state = {
  theme: '#3E50B3',
  themeBgColor: '#D7DBE',
  themeNavColor: '#6573C4',
  themeImg: 'blue',
  botColor: 'white',
  websiteName: "",//系统名称
  copyright: "",
  companyLogo: "",
}
​
const mutations = {
  CHANGE_SETTING: (state, { key, value }) => {
    // eslint-disable-next-line no-prototype-builtins
    if (state.hasOwnProperty(key)) {
      state[key] = value
    }
  }
}
​
const actions = {
  changeSetting({ commit }, data) {
    commit('CHANGE_SETTING', data)
  }
}
​
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
复制代码

切换elementUI主题

在changeElementTheme方法中,加载不同elementUI主题颜色文件。

ThemeChangeMixin.js文件


/**
  切换皮肤,改变主题的mixin
 */
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // deexport const ThemeChangeMixin = {
  data() {
    return {
      chalk: '', // content of theme-chalk css
      selectedColor: '',
      themeSelectedColor: this.$store.state.settings.theme,
      themeColorList: [
        {
          color: '#3E50B3',
          text: '蓝色',
          skin: {
            themeImg: 'blue',
            themeBgColor: '#D7DBEA',
            themeNavColor: '#6573C4',
            botColor: 'white'
          }
        },
        {
          color: '#CD2E18',
          text: '红色',
          skin: {
            themeImg: 'red',
            themeBgColor: '#FAEAE8',
            themeNavColor: '#DA523E',
            botColor: 'black'
          }
        }
      ]
    }
  },
  created() {
    this.selectedColor = this.themeSelectedColor
  },
  methods: {
    /* 切换主题 */
    themeChange(val) {
      if (this.selectedColor == val) return
      this.selectedColor = val
      this.$store.dispatch('settings/changeSetting', {
        key: 'theme',
        value: val
      })
      const list = this.themeColorList
      const idx = list.findIndex((item) => item.color == val)
      Object.keys(list[0]['skin']).forEach((item) => {
        const value = list[idx]['skin'][item]
        this.$store.dispatch('settings/changeSetting', {
          key: item,
          value
        })
      })
      this.changeElementTheme(val)
    },
    async changeElementTheme(val) {
      const oldVal = this.chalk ? this.themeSelectedColor : ORIGINAL_THEME
      if (typeof val !== 'string') return
      const themeCluster = this.getThemeCluster(val.replace('#', ''))
      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
      const getHandler = (variable, id) => {
        return () => {
          const originalCluster = this.getThemeCluster(
            ORIGINAL_THEME.replace('#', '')
          )
          const newStyle = this.updateStyle(
            this[variable],
            originalCluster,
            themeCluster
          )
          let styleTag = document.getElementById(id)
          if (!styleTag) {
            styleTag = document.createElement('style')
            styleTag.setAttribute('id', id)
            document.head.appendChild(styleTag)
          }
          styleTag.innerText = newStyle
        }
      }
      if (!this.chalk) {
        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
        await this.getCSSString(url, 'chalk')
      }
      const that = this
      const chalkHandler = getHandler('chalk', 'chalk-style')
      chalkHandler()
      const styles = [].slice
        .call(document.querySelectorAll('style'))
        .filter((style) => {
          const text = style.innerText
          return (
            new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
          )
        })
      styles.forEach((style) => {
        const { innerText } = style
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(
          innerText,
          originalCluster,
          themeCluster
        )
      })
    },
    updateStyle(style, oldCluster, newCluster) {
      let newStyle = style
      oldCluster.forEach((color, index) => {
        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
      })
      return newStyle
    },
    getCSSString(url, variable) {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
            resolve()
          }
        }
        xhr.open('GET', url)
        xhr.send()
      })
    },
    getThemeCluster(theme) {
      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
    }
  }
}
复制代码

CSS var() 函数

以上,通过JS改变了主题颜色。那么,可以在CSS文件中直接使用主题颜色吗?答案是可以的,通过CSS var() 函数。

我在App.vue文件中用如下方式,引入了主题颜色,包括背景颜色,主题图片,底部颜色,等等。可以根据具体需求自己配置。

<template>
  <div
    id="app"
    :style="{
      '--themeColor': theme,
      '--themeBgColor': themeBgColor,
      '--themeNavColor': themeNavColor,
      '--themeImg': themeImg,
      '--botColor': botColor,
    }"
  >
    <router-view />
  </div>
</template>
复制代码

然后在css文件中,就可以通过css的var函数调用该属性

.name {
    &:hover {
      color: var(--themeColor);
      transform: scale(1.1);
    }
}
复制代码

App.vue完整代码


<template>
  <div
    id="app"
    :style="{
      '--themeColor': theme,
      '--themeBgColor': themeBgColor,
      '--themeNavColor': themeNavColor,
      '--themeImg': themeImg,
      '--botColor': botColor,
    }"
  >
    <router-view />
  </div>
</template>
​
<script>
import { ThemeChangeMixin } from '@/mixins/ThemeChangeMixin'const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // deexport default {
  name: 'App',
  mixins: [ThemeChangeMixin],
  computed: {
    theme() {
      return this.$store.state.settings.theme
    },
    themeBgColor(){
      return this.$store.state.settings.themeBgColor
    },
    themeNavColor(){
      return this.$store.state.settings.themeNavColor
    },
    themeImg() {
      return this.$store.state.settings.themeImg
    },
    botColor() {
      return this.$store.state.settings.botColor
    }
  },
  mounted() {
    this.handleUserTheme()
  },
  methods: {
    async handleUserTheme() {
      const val = this.theme
      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
      if (typeof val !== 'string') return
      const themeCluster = this.getThemeCluster(val.replace('#', ''))
      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
      const getHandler = (variable, id) => {
        return () => {
          const originalCluster = this.getThemeCluster(
            ORIGINAL_THEME.replace('#', '')
          )
          const newStyle = this.updateStyle(
            this[variable],
            originalCluster,
            themeCluster
          )
          let styleTag = document.getElementById(id)
          if (!styleTag) {
            styleTag = document.createElement('style')
            styleTag.setAttribute('id', id)
            document.head.appendChild(styleTag)
          }
          styleTag.innerText = newStyle
        }
      }
      if (!this.chalk) {
        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
        await this.getCSSString(url, 'chalk')
      }
      const that = this
      const chalkHandler = getHandler('chalk', 'chalk-style')
      chalkHandler()
      const styles = [].slice
        .call(document.querySelectorAll('style'))
        .filter((style) => {
          const text = style.innerText
          return (
            new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
          )
        })
      styles.forEach((style) => {
        const { innerText } = style
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(
          innerText,
          originalCluster,
          themeCluster
        )
      })
    }
  }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享