前言:目前,在众多的后台管理系统中,换肤功能已是一个很常见的功能。用户可以根据自己的喜好,设置页面的主题,从而实现个性化定制。
目前,我所了解到的换肤方式,也是我目前所掌握的两种换肤方式,想同大家一起分享。
一、Less/Scss变量换肤
具体实现:
1、初始化vue项目
2、安装插件:
npm install style-resources-loader -D
npm install vue-cli-plugin-style-resources-loader -D
当然也要安装less、less-loader等插件,具体的安装配置,请自行google
3、新建theme.less文件用于全局样式配置。在src目录下新建theme文件夹,在这个文件夹下新建theme.less文件。具体如下:
/src/theme/theme.less// 默认的主题颜色
@primaryColor: var(--primaryColor, #000);
@primaryTextColor: var(--primaryTextColor, green);
// 导出变量
:export {name: "less";primaryColor: @primaryColor;primaryTextColor: @primaryTextColor;
}
4、配置vue.config.js文件,实现全局使用变量实现换肤
const path = require("path");
module.exports = {pluginOptions: {"style-resources-loader": {preProcessor: "less",patterns: [// 这个是加上自己的路径,不能使用(如下:alias)中配置的别名路径path.resolve(__dirname, "./src/theme/theme.less"),],},},
};
5、具体的使用:
<template><div class="hello"><p>我是测试文字</p></div>
</template><script>
export default {name: "HelloWorld",
};
</script>
<style scoped lang="less">
.hello {p {color: @primaryTextColor;}
}
</style>
备注:如果是用scss也基本同以上用法,只是scss的变量名用‘$’作为前缀,less使用@
至此,我们已经实现了静态更换皮肤,那如何实现动态换肤呢,最重要的就是以下的文件了。
我们可以多配置几种默认主题
6、在theme文件夹下新建model.js文件,用于存放默认主题
// 一套默认主题以及一套暗黑主题
// 一套默认主题以及一套暗黑主题
export const themes = {default: {primaryColor: `${74}, ${144},${226}`,primaryTextColor: `${74}, ${144},${226}`,},dark: {primaryColor: `${0},${0},${0}`,primaryTextColor: `${0},${0},${0}`,},
};
7、实现动态切换:
在/src/theme文件夹下新建theme.js文件,代码如下:
import { themes } from "./model";
// 修改页面中的样式变量值
const changeStyle = (obj) => {for (let key in obj) {document.getElementsByTagName("body")[0].style.setProperty(`--${key}`, obj[key]);}
};
// 改变主题的方法
export const setTheme = (themeName) => {localStorage.setItem("theme", themeName); // 保存主题到本地,下次进入使用该主题const themeConfig = themes[themeName];// 如果有主题名称,那么则采用我们定义的主题if (themeConfig) {localStorage.setItem("primaryColor", themeConfig.primaryColor); // 保存主题色到本地localStorage.setItem("primaryTextColor", themeConfig.primaryTextColor); // 保存文字颜色到本地changeStyle(themeConfig); // 改变样式} else {let themeConfig = {primaryColor: localStorage.getItem("primaryColor"),primaryTextColor: localStorage.getItem("primaryTextColor"),};changeStyle(themeConfig);}
};
8、切换主题
this.setTheme('dark')
二、element-UI组件的换肤
1、一般elementUI主题色都有这样一个文件element-variables.scss:
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**//* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #ffba00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;$--button-font-weight: 400;// $--color-text-regular: #1f2d3d;$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;$--table-border: 1px solid #dfe6ec;/* icon font path, required */
$--font-path: "~element-ui/lib/theme-chalk/fonts";@import "~element-ui/packages/theme-chalk/src/index";// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {theme: $--color-primary;
}
2、main.js中引用
import './styles/element-variables.scss'
3、在store文件夹下新建settings.js文件,用于页面基础设置
import variables from '@/styles/element-variables.scss'
const state = {theme: variables.theme
}
const mutations = {CHANGE_SETTING: (state, { key, value }) => {// eslint-disable-next-line no-prototype-builtinsif (state.hasOwnProperty(key)) {state[key] = value}}
}
const actions = {changeSetting({ commit }, data) {commit('CHANGE_SETTING', data)}
}
export default {namespaced: true,state,mutations,actions
}
4、一般换肤都是需要有个颜色选择器,用于皮肤设置
在src目录下新建ThemePicker文件夹,新建index.vue文件。
<template><el-color-pickerv-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 colorexport default {data() {return {chalk: '', // content of theme-chalk csstheme: ''}},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_THEMEif (typeof val !== 'string') returnconst themeCluster = this.getThemeCluster(val.replace('#', ''))const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))console.log(themeCluster, originalCluster)const $message = this.$message({message: ' Compiling the theme',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) {const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.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.innerTextreturn new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)})styles.forEach(style => {const { innerText } = styleif (typeof innerText !== 'string') returnstyle.innerText = this.updateStyle(innerText, originalCluster, themeCluster)})this.$emit('change', val)$message.close()}},methods: {updateStyle(style, oldCluster, newCluster) {let newStyle = styleoldCluster.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 spacereturn [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>
5、在使用ThemePicker组件的位置,去调用vuex中的changeSetting函数
<theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
import ThemePicker from '@/components/ThemePicker'export default {components: { ThemePicker },
methods:{themeChange(val) {this.$store.dispatch('settings/changeSetting', {key: 'theme',value: val})}
}
}
至此,就可以实现elementUI组件的换肤功能了
总结:其实上边两种方式换肤的实现思路都差不多,下边那篇自己理解得不是很好,欢迎补充