vue-element如何实现动态换肤存储

需要实现的效果

选择颜色块或者颜色选择器切换网站主题色,选择主题后保存到本地,下次打开页面是缓存的主题色

-1

-2

-3

-4

原理

根据ElementUI官网的自定义主题,新建一个样式文件:element-variables(或者参考官网生成),在文件中写入:

  1. /* 改变主题色变量 */
  2. $colorprimary: #409eff;
  3. $colorsuccess: #67c23a;
  4. /*….还可以定义更多变量*/
  5.  
  6. /* 改变 icon 字体路径变量,必需 */
  7. $fontpath: ‘~element-ui/lib/theme-chalk/fonts’;
  8.  
  9. @import “~element-ui/packages/theme-chalk/src/index”;
  10.  
  11. //主要的一步:
  12. :export {
  13.      theme: $colorprimary;
  14. }
  15.  

在项目store文件夹中新建theme.js文件,用于存储主题色变化状态,代码如下

  1. //引入element-variables文件,文件路径根据自己项目存放位置来
  2. import variables from ‘@/assets/css/element-variables.scss’
  3. const state = {
  4.      theme: variables.theme
  5. }
  6.  
  7. const getters = {
  8.      theme: function (state) {
  9.          return state.theme;
  10.      }
  11. };
  12.  
  13. const mutations = {
  14.      CHANGE_SETTING: (state, { key, value }) => {
  15.      // eslint-disable-next-line no-prototype-builtins
  16.      if (state.hasOwnProperty(key)) {
  17.          state[key] = value
  18.      }
  19.      }
  20. }
  21.  
  22. const actions = {
  23.      changeSetting({ commit }, data) {
  24.      commit(‘CHANGE_SETTING’, data)
  25.      }
  26. }
  27.  
  28. export default {
  29.      namespaced: true,
  30.      state,
  31.      mutations,
  32.      actions,
  33.      getters
  34. }
  35.  

然后在store.js中引入theme.js

  1. import vue from ‘vue’;
  2. import Vuex from ‘vuex’;
  3. import theme from ‘@/store/theme.js’;
  4. Vue.use(Vuex);
  5. export default new Vuex.Store({
  6.      modules: {
  7.          theme
  8.      }
  9. });

切换主图部分template代码如下:

  1. <el-form-item label=“可选主题” label-width=“140px”>
  2.      <div class=“color-box”>
  3.          <div
  4.              v-for=“(item,i) in themeArr”
  5.              :key=“i”
  6.              :class=“[‘color-item’,theme===item?’active’:”]”
  7.              :style={backgroundColor:item}
  8.              @click=“changeTheme(item)”>
  9.              <span v-if=“theme===item” class=“iconfont icon-f-right”></span>
  10.          </div>
  11.      </div>
  12. </el-form-item>
  13. <el-form-item label=“自定义主题” label-width=“140px”>
  14.      <el-color-picker
  15.          v-model=“theme”
  16.          :predefine=“[‘#409EFF’, ‘#1890ff’, ‘#304156′,’#212121′,’#11a983’, ‘#13c2c2’, ‘#6959CD’, ‘#f5222d’, ]”
  17.          class=“theme-picker”
  18.          popper-class=“theme-picker-dropdown”
  19.      />
  20. </el-form-item>

script代码

  1. const version = require(‘element-ui/package.json’).version // element-ui version from node_modules
  2. const ORIGINAL_THEME = ‘#409EFF’ // default color
  3.  
  4. export default {
  5.      data() {
  6.          return {
  7.              themeArr: [‘#409EFF’,‘#202225’,‘#F56C6C’,‘#FFAB42’,‘#17a969’,‘#888C8F’],
  8.              chalk: ,
  9.              theme: ORIGINAL_THEME,
  10.          }
  11.      },
  12.      watch: {
  13.          //异步监听theme的变化
  14.          async theme(val,oldVal) {
  15.      if (typeof val !== ‘string’) return
  16.      const themeCluster = this.getThemeCluster(val.replace(‘#’, ))
  17.      const originalCluster = this.getThemeCluster(oldVal.replace(‘#, ))
  18.      // console.log(themeCluster, originalCluster)
  19.      const $message = this.$message({
  20.      message: ‘ Compiling the theme’,
  21.      customClass: ‘theme-message’,
  22.      type: ‘success’,
  23.      duration: 0,
  24.      iconClass: ‘el-icon-loading’
  25.      })
  26.      const getHandler = (variable, id) => {
  27.      return () => {
  28.      const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace(‘#’, ))
  29.      const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
  30.      let styleTag = document.getElementById(id)
  31.      if (!styleTag) {
  32.      styleTag = document.createElement(‘style’)
  33.      styleTag.setAttribute(‘id’, id)
  34.      document.head.appendChild(styleTag)
  35.      }
  36.      styleTag.innerText = newStyle
  37.      }
  38.      }
  39.          // 初次进入或刷新时动态加载CSS文件
  40.      if (!this.chalk) {
  41.      const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
  42.      await this.getCSSString(url, ‘chalk’)
  43.      }
  44.      const chalkHandler = getHandler(‘chalk’, ‘chalk-style’)
  45.      chalkHandler()
  46.      const styles = [].slice.call(document.querySelectorAll(‘style’))
  47.      .filter(style => {
  48.      const text = style.innerText
  49.      return new RegExp(oldVal, ‘i’).test(text) && !/Chalk Variables/.test(text)
  50.      })
  51.      styles.forEach(style => {
  52.      const { innerText } = style
  53.      if (typeof innerText !== ‘string’) return
  54.      style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
  55.      })
  56.      // 将修改的主题保存到本地,下次打开页面是保存的主题色
  57.      localStorage.setItem(‘theme’,val)
  58.      //调用vuex中改变主题色方法
  59.              this.$store.dispatch(‘theme/changeSetting’, {
  60.          key: ‘theme’,
  61.          value: val
  62.          })
  63.      $message.close()
  64.      },
  65.      },
  66.      methods: {
  67.          updateStyle(style, oldCluster, newCluster) {
  68.      let newStyle = style
  69.      oldCluster.forEach((color, index) => {
  70.      newStyle = newStyle.replace(new RegExp(color, ‘ig’), newCluster[index])
  71.      })
  72.      return newStyle
  73.      },
  74.      getCSSString(url, variable) {
  75.      return new Promise(resolve => {
  76.      const xhr = new XMLHttpRequest()
  77.      xhr.onreadystatechange = () => {
  78.      if (xhr.readyState === 4 && xhr.status === 200) {
  79.      this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, )
  80.      resolve()
  81.      }
  82.      }
  83.      xhr.open(‘GET’, url)
  84.      xhr.send()
  85.      })
  86.      },
  87.      getThemeCluster(theme) {
  88.      const tintColor = (color, tint) => {
  89.      let red = parseInt(color.slice(0, 2), 16)
  90.      let green = parseInt(color.slice(2, 4), 16)
  91.      let blue = parseInt(color.slice(4, 6), 16)
  92.      if (tint === 0) { // when primary color is in its rgb space
  93.      return [red, green, blue].join(‘,’)
  94.      } else {
  95.      red += Math.round(tint * (255  red))
  96.      green += Math.round(tint * (255  green))
  97.      blue += Math.round(tint * (255  blue))
  98.      red = red.toString(16)
  99.      green = green.toString(16)
  100.      blue = blue.toString(16)
  101.      return `#${red}${green}${blue}`
  102.      }
  103.      }
  104.      const shadeColor = (color, shade) => {
  105.      let red = parseInt(color.slice(0, 2), 16)
  106.      let green = parseInt(color.slice(2, 4), 16)
  107.      let blue = parseInt(color.slice(4, 6), 16)
  108.      red = Math.round((1  shade) * red)
  109.      green = Math.round((1  shade) * green)
  110.      blue = Math.round((1  shade) * blue)
  111.      red = red.toString(16)
  112.      green = green.toString(16)
  113.      blue = blue.toString(16)
  114.      return `#${red}${green}${blue}`
  115.      }
  116.      const clusters = [theme]
  117.      for (let i = 0; i <= 9; i++) {
  118.      clusters.push(tintColor(theme, Number((/ 10).toFixed(2))))
  119.      }
  120.      clusters.push(shadeColor(theme, 0.1))
  121.      return clusters
  122.      },
  123.          // 颜色块点击切换主题事件
  124.          changeTheme(item) {
  125.          this.theme = item
  126.      this.$store.dispatch(‘theme/changeSetting’, {
  127.      key: ‘theme’,
  128.      value: item
  129.      })
  130.          },
  131.      },
  132.      mounted() {
  133.          //从本地存储获取保存的主题
  134.          if(localStorage.getItem(“theme”)) {
  135.              this.theme = localStorage.getItem(“theme”)
  136.              this.$store.dispatch(‘theme/changeSetting’, {
  137.          key: ‘theme’,
  138.          value: this.theme
  139.          })
  140.          }
  141.      }
  142. }

颜色块选择样式style:

  1. .colorbox {
  2.      display: Flex;
  3.      flexwrap: wrap;
  4.      .coloritem {
  5.          position: relative;
  6.          margin: 2px;
  7.          width: 34px;
  8.          height: 34px;
  9.          borderradius: 3px;
  10.          border: 2px solid transparent;
  11.          cursor: pointer;
  12.          span {
  13.              position: absolute;
  14.              right: 0;
  15.              top: 0;
  16.              width: 15px;
  17.              height: 15px;
  18.              backgroundcolor: #000;
  19.              color: #fff;
  20.              fontsize: 10px;
  21.      textalign: center;
  22.          }
  23.          &.active {
  24.              bordercolor: #000;
  25.          }
  26.      }
  27. }

最后感谢:vue-element-admin作者 本实现方案借鉴于此

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

标签

发表评论