文章目录
1. 背景
很多前端项目都有换肤的需求,笔者在此分享笔者当前所使用的换肤方案。本文将讲述Vue+Less
前端项目换肤的设计思路以及开发皮肤主题的步骤。虽然笔者在本文中所使用的是Vue+Less,但是针对该方案而言,亦可扩展为:Vue/React/Angular+Less/Scss。读者在阅读本文时,请着重关注换肤的设计思路,不必局限于具体的技术。
2. 设计思路
此小节将描述主题换肤的设计思路。整体思路是以 mixins + variable
的形式实现换肤的功能。在具体的主题文件,只需定义主题中的变量以及执行 mixins 函数即可。
2.1 定义主题配置
// src/store/theme-map.js
export default {
BLUE: {
value: 'blue',
name: '蓝色主题',
color: '#2d8cf0',
},
ORANGE: {
value: 'orange',
name: '橙色主题',
color: '#e96500',
},
RED: {
value: 'red',
name: '红色主题',
color: '#e43e31',
},
}
2.2 保存当前主题设置
- 在 vuex 中存储当前主题
- 在 localStorage 中存储当前主题
- 调用后台 api 接口,将主题数据保存到数据库
2.2.1 Vuex 部分示例代码
import THEME_MAP from './theme-map'
export const THEME = 'APP_THEME'
if (!LocalStorageUtils.readJSON(THEME)) {
LocalStorageUtils.writeJSON(THEME, THEME_MAP.BLUE)
}
export default new Vuex.Store({
namespaced: true,
state: {
theme: LocalStorageUtils.readJSON(THEME),
},
mutations: {
[THEME] (state, payload) {
state.theme = payload
},
},
actions: {},
modules: {},
})
特别说明:
- 关于 LocalStorageUtils:请移步前端 storage 使用分享。
2.3 读取当前主题设置
- 刷新页面时:从 localStorage 读取当前主题设置并同步到 vuex
- 重新登录时:从登录接口返回的数据里读取用户主题设置并同步到 vuex、localStorage
2.4 编写主题 mixins 文件
.themeMixins() {
.el-button--primary {
background-color: @primary-color;
border-color: @primary-color;
color: @button-text-color;
}
.component.layout-menu .el-menu .is-active {
background-color: @primary-color;
}
.el-tag {
color: @primary-color;
}
}
2.5 在样式入口文件引入主题 mixins 文件
@import './theme-mixins.less';
2.6 编写主题文件
// src/assets/styles/themes/red.less
示例:
body.red {
@primary-color: #ff503e;
@button-text-color: #fff;
.themeMixins();
}
2.7 在样式入口文件引入主题文件
@import './theme-mixins.less';
@import './themes/red.less';
2.8 编写切换主题的业务组件
<template>
<el-dropdown trigger="click" class="theme-switch">
<span class="el-dropdown-link cursor">
<i class="icon icon-tshirt" :style="{ color: theme.color }"></i>
</span>
<el-dropdown-menu slot="dropdown" class="theme-switch">
<el-dropdown-item
v-for="i in themeList"
:key="i.value"
@click.native="onThemeItemClick(i)"
>
<div class="theme-item" :class="[i.value]">
<i class="circle" :style="{ backgroundColor: i.color }"></i>
<span :style="{ color: i.color }">{{ i.name }}</span>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { THEME } from '@/store'
import THEME_MAP from '@/store/theme-map'
import { mapState } from 'vuex'
import { LocalStorageUtils } from '@/utils/storage'
export default {
name: 'theme-switch',
computed: {
...mapState(['theme']),
},
data () {
return {
themeList: Object.values(THEME_MAP),
}
},
methods: {
onThemeItemClick (i) {
this.$store.commit(THEME, i)
LocalStorageUtils.writeJSON(THEME, i)
// TODO: 调用后台 api 接口将主题设置保存到数据库中
},
},
}
</script>
<style lang="less" scoped>
.theme-switch {
vertical-align: top;
.icon.icon-tshirt {
font-size: 24px;
}
.theme-item {
color: #fff;
display: flex;
align-items: center;
i.circle {
@size: 20px;
display: inline-block;
vertical-align: top;
width: @size;
height: @size;
border-radius: 50%;
}
}
}
</style>
2.9 将当前皮肤的值,置于 DOM 中
将当前皮肤主题的值作为 class 置于 body 即可。
示例:
// src/app.vue
<template>
<div id="app" :class="[theme.value]">
<router-view></router-view>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'app',
computed: {
...mapState(['theme']),
},
watch: {
theme (newTheme, oldTheme) {
this.removeThemeFromBody(oldTheme.value)
this.addThemeToBody(newTheme.value)
},
},
components: {},
created () {},
mounted () {
this.addThemeToBody(this.theme.value)
},
methods: {
addThemeToBody (themeValue) {
document.body.classList.add(themeValue)
},
removeThemeFromBody (themeValue) {
document.body.classList.remove(themeValue)
},
},
}
</script>
<style lang="less" scoped>
#app {
width: 100%;
height: 100%;
}
</style>
效果:
<body class="red"></body>
3. 新增皮肤主题
此小节将描述添加主题的具体步骤。
3.1 编写主题文件
// src/assets/styles/themes/green.less
示例:
body.green {
@primary-color: #00a854;
@button-text-color: #fff;
.themeMixins();
}
3.2 注册主题文件
在src/assets/styles/index.less
引入主题文件即可。
示例:
@import './theme-mixins.less';
@import './themes/green.less';
3.3 添加主题配置
// src/store/theme-map.js
export default {
// ...
GREEN: {
value: 'green',
name: '绿色主题',
color: '#00a854',
},
// ...
}