Vue + Element UI+Scss + Vuex一键换肤 , 一键换字体大小 ,动态替换全局主题颜色

2023-10-31

一、前言

其实我这个写法每个UI库都通用 , 不局限于ElementUI , 看明白思路就知道怎么写了

一键换肤 , 动态替换全局主题颜色功能已经实现很久了 , 在项目验收的时候出现了一个小问题 , 想改动一下 , 于是来记录一下
前段时间公司项目里需要实现一键换服 , 换主题颜色 , 网上看了一部分 大多数都是用js获取标签元素添加Attribute , 如document.XXXXXXX ,或者是建theme.css文件什么的 , 不太符合我想要实现的用最少的代码实现换肤的功能 , 如果用了框架 , 还要用一大堆代码实现一个不复杂的功能的话 , 我觉得他是在凑code量, 凑代码绩效 , 冗余且不易理解

二、效果图

老规矩 , 观众老爷们先看效果图

在这里插入图片描述

三、实现思路

前言已经讲了那么多废话 , 这段来捋一下实现的思路 , UI库用的是ElementUi , 要实现一键换肤 , 唯一的一个变量是色值 ,
一个变量 来控制颜色 , 首选scss , 想要全局可控一个变量的话 , 肯定是vue里面的vuex状态管理 , 思路到这就够了 , 还想其他什么呢

四、贴上代码

1. 第一步封装一个theme-picker颜色选择器组件 , 是子组件

子组件第一种 ( 不包括element里面的组件色值,比如按钮颜色,单选选中颜色 ,下拉级联等等等等 )

<template>
  <el-color-picker
    v-model="themeColor"
    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />
</template>


<script>

export default {
  name: "ThemePicker",
  data() {
    return {
      chalk: "", // content of theme-chalk css
      themeColor: variables.mainTone
    };
  },
  mounted() {
  	// 也可以从localStorage里拿值, 看个人需求
    this.themeColor = this.defaultTheme;
  },
  computed: {
  	// 计算store里面的theme值
    defaultTheme() {
      return this.$store.state.settings.theme;
    }
  },
  watch: {
    async themeColor(newVal, oldVal) {

      if (typeof newVal !== "string") return;

        this.$emit("change", newVal); //将更换的颜色存入store
    }
  },
  methods: {
  }
};
</script>

<style>
.theme-message,
.theme-picker-dropdown {
  z-index: 99999 !important;
}

.theme-picker .el-color-picker__trigger {
  height: 26px !important;
  width: 26px !important;
  padding: 2px;
}

.theme-picker-dropdown .el-color-dropdown__link-btn {
  display: none;
}
</style>

子组件第二种 ( 包括element里面的组件色值,比如按钮颜色,单选选中颜色 ,下拉级联等等等等 ,但是有局限性 , 具体局限看代码注解 )

<template>
  <el-color-picker
    v-model="theme"
    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />
</template>

<script>
const version = require('element-ui/package.json').version //获取版本号 element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
  data() {
    return {
      chalk: '', // content of theme-chalk css
      theme: ''
    }
  },
  computed: {
    defaultTheme() {
      return this.$store.state.settings.theme
    }
  },
  watch: {
    defaultTheme: {
      handler: function(val, oldVal) {
        this.theme = val
      },
      immediate: true
    },
    async theme(val) {
      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('#', ''))
      //	换肤中loading开启
      // const $message = this.$message({
      //   message: '  主题皮肤更换中 , 请稍后...',
      //   customClass: 'theme-message',
      //   type: 'success',
      //   duration: 0,
      //   iconClass: 'el-icon-loading'
      // })
      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) {
      // 此处重点!!!!
      // 本地css文件不用调用this.getCSSString()方法去下载文件 , 但是,element按钮下拉等等会不随着换色更改色值(此处不建议内网项目使用)
      // const url ="../../assets/styles/theme-chalk/index.css" //本地css文件
      // https下载线上文件, element按钮下拉等等会随着换色而改变色值(外网项目可用)
        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css` //element线上css文件
        await this.getCSSString(url, 'chalk')
      }

      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)
      })
      // 告知父组件选了什么颜色
        this.$emit('change', val);
      // $message.close() //换肤中loading关闭
    }
  },

  methods: {
    updateStyle(style, oldCluster, newCluster) {
      let newStyle = style
      oldCluster.forEach((color, index) => {
        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
      })
      return newStyle
    },
	//下载动态版本号样式css
    getCSSString(url, variable) {
      return new Promise(resolve => {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
          //剔除element样式里指定的字体样式
            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
    }
  }
}
</script>

<style>
.theme-message,
.theme-picker-dropdown {
  z-index: 99999 !important;
}

.theme-picker .el-color-picker__trigger {
  height: 26px !important;
  width: 26px !important;
  padding: 2px;
}

.theme-picker-dropdown .el-color-dropdown__link-btn {
  display: none;
}
</style>

2. 第二步父组件调用颜色选择器组件

父组件 ( 接收传递过来的色值 , 可请求接口向后台传递数据存储 , 以达到每个用户的皮肤可记忆 ,或存储到localStorage里方便调用 , 看个人项目需求情况 )

// 调用
<theme-picker ref="ThemePicker" @change="themeChange" class="themeChange"/>
// 按需引入
import ThemePicker from "@/components/ThemePicker";
// 声明
components: { ThemePicker },
// 选中的值储存
    themeChange(val) {
      console.log('val',val);
      // 可选 ,可调取接口向后台存储数据
      updatehandUser().then(res => {
        if (res.code == "200") {
        // 重点:vuex  store存储色值
          this.$store.dispatch("settings/changeSetting", {
            key: "theme",
            value: val
          });
        }
      });
    },

3. 第三步store储存色值
需要解释目录文件所在 就截图了哈
在这里插入图片描述

此处是项目需要读取后台接口返回数据里存储的个人换肤颜色色值 , 所以选择再验证是否有token后调用userInfo接口来直接更换主题颜色

在这里插入图片描述

在这里插入图片描述
5. 第五步layout父组件动态挂载
在这里插入图片描述

在这里插入图片描述

上面图中标签上绑定的 :style=“styleObject”**

styleObject是当前页面computed里计算的函数
返回的是store里面的theme值

注: 父组件上挂载的:style=“styleObject” ,子组件里可直接使用background-color: var(–mainTone); 如果嵌套太深 , 子组件里面使用不了这个var(–mainTone)变量 , 那么就标签上再挂载一次styleObject ,以及computed

variables.scss文件存储scss样式变量 , 定值,一般为项目的默认主题颜色
声明$mainTone
export暴露出去变量

在这里插入图片描述

@import “~@/assets/styles/variables.scss”;
当前.vue文件引入后可直接使用定义好的Scss变量
比如下图

<style lang="scss" scoped>
  @import "~@/assets/styles/variables.scss";
  .fixed-header {
    color: $mainTone;
  }
</style>

五、总结

到这说明已经结束了 , 言语措辞不当 , 词不达意或者描述的不够清楚还希望见谅 , 组织语言水平不太行

目前唯一感到遗憾的就是没解决掉文中注解标注的内网环境下如何改变element默认的组件的色值 , 如有同学有好的思路 , 希望不吝赐教 , 不胜感激 !!

哦对了 , 细心的同学能够发现截图中有个值 –gloabalFont 小声的说一下是一键更改字体大小 ,与一键更换主题颜色同理哈在这里插入图片描述

到此为此 , 还希望看官老爷来个一键三连 , 关注 + 评论 + 收藏 支持一下 ! thanks

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue + Element UI+Scss + Vuex一键换肤 , 一键换字体大小 ,动态替换全局主题颜色 的相关文章

  • Vue模板-渲染HTML特殊字符代码

    如何在我的 Vue 模板中完全渲染 HTML 特殊字符代码 例如我有这个 JSON 数据 id post91 slug null title Breakfast 038 Tea 我怎样才能转换Breakfast 038 Tea to Bre
  • 如何更改 Vuetify 日历日期格式

    我正在尝试在以下 Vuetify 日历上启用输入事件 https github com vuetifyjs vuetify blob master packages docs src examples calendars complex e
  • 如何突出显示 Vuetify 菜单中的所选项目?

    我的侧边栏中有一个菜单 使用 vue router
  • 如何在Vuejs中动态管理页面标题?

    我构建一个应用程序 我有一个带有页面标题的标题 目前 我使用视图路由器来定义我的标题 path events name events component Events meta title Liste des v nements 在我的刀片
  • 将 Select2(多项选择)与 vue.js 结合使用

    我是 vue 新手 并遵循了他们的 自定义指令 http vuejs org examples select2 html http vuejs org examples select2 html 当仅选择一个项目时 此方法效果很好 但当您选
  • 使用 vue.js 显示 json 结果

    您好 我尝试使用 vue js 显示 json 文件结果 目标是结果将显示在值上 这是我的代码 data return fetchData function var self this self http get api casetotal
  • node.js、vue.js 和express.js 堆栈开发

    我正在尝试使用 Linux 上的 Visual Studio Code IDE 使用 vue js express js 和 node js 创建一个 Web 应用程序 根据网上的一些文档 我读到安装 vue js 后 可以创建一个vue
  • VueJS - 访问已安装的存储数据

    我无法理解以下内容 我有一个store其中包含应用程序所需的变量 特别地 有一个globalCompanies哪些商店 globalCompanies current all currentName 在另一个组件中 我想执行以下操作 mou
  • Nuxt.js 多个资源的根路由和根级 slugs

    我正在使用 Nuxt 构建一个电子商务前端 我希望为尽可能多的资源提供根级 slugs 最重要的是目录路径和产品 URL 一个明显的方法是使用 Nuxt 文件结构进行路由创建 com category men Tshirt com cate
  • 在新窗口中打开 VueJS 组件

    我有一个只有一页的基本 VueJS 应用程序 它不是 SPA 而且我不使用 vue router 我想实现一个按钮 单击该按钮时会使用我的 Vue 组件之一的内容执行 window open 函数 查看来自的文档window open ht
  • 如何将 Jitsi Meet 添加到 Vuejs

    我已在 public html 的正文中加载了 jitsi meet 脚本 并且我有一个组件 如下所示
  • 如何以编程方式启动 Vuetify 对话框并等待响应

    我对 Vue js 和 Vuetify 相当陌生 使用 AngularJS 好几年了 但我们公司正在转向 Vue js 我想要完成的是 当用户单击 登录 按钮时 它会执行一些检查 即用户名不能为空 并启动 Vuetify 对话框来提醒用户
  • 从组件传递数据

    我对 Vue 相当陌生 我正在尝试将数据从组件传递到视图 我不确定我是否在使用props正确的 我有一个对话框 当我保存时 我想将数据插入数据库 我也想重复使用addCustomer function 这就是为什么我没有将该函数放置在组件中
  • Vue 3 Composition API 提供/注入在单文件组件中不起作用

    我正在使用 Composition API 在 VueJS 3 中创建一个库 我实现了提供 注入 如中所述docs https v3 vuejs org guide composition api provide inject html i
  • VueJS 中数据无法正确显示

    我的 VueJS 代码有一个小问题 在 输出 压缩的 GS1 数字链接 URI 部分中 When there is no result it should have nothing display like this I have remo
  • 错误:[vuex] 期望 string 作为类型,但发现未定义

    学习Vuex 我写了一个简单的登录页面示例项目 https github com vuejs vuex tree dev examples shopping cart和document https vuex vuejs org zh gui
  • 左侧导航菜单上部隐藏

    当滚动到页面最底部时 左侧导航菜单的顶部将被隐藏 The image before scrolling is shown below 滚动后的效果如下图 我不确定此问题的确切原因 因此我将不胜感激任何有关识别和解决该问题的建议或帮助 为了确
  • 如何处理 Nuxt 中导致页面渲染崩溃的 apollo 客户端错误?

    我目前正在维护一个生产 Nuxt js Vue 应用程序 该应用程序集成了 GraphQL Apollo 客户端 该客户端遇到页面渲染错误 为了增加获得回复的机会 我构建了一个简单的代码示例 仅展示我们遇到的问题 谢谢大家 源代码 Clie
  • 在重复内容区域添加

    我有一个菜单组件 简单地说 它接受一个带有一系列选项的道具 并为每个选项在菜单中呈现一个项目 我希望能够根据用例自定义每个菜单项内的标记 因此我在菜单项元素内使用了占位符 你可以在这个中看到一个例子fiddle https jsfiddle
  • 使用 Vue 的多模式组件

    我在 Vue 中实现动态模式组件时遇到问题 A common approach I follow to display a set of data fetched from the db is I dump each of the rows

随机推荐