Vue 在sass中的主题切换方案

    xiaoxiao2025-08-09  5

    (一) 前言

    主题切换,是目前很多产品需要定制的功能,常见的比如需要给用户提供两套主题,日间和夜间模式,那么我们需要找出一种在实际项目中使用的方案

    (二) 切换方案

    已知道的主题切换方案有如下几种

    DWZ富客户端 实现方式:

    将不同主题的样式抽取出来。 生成多份不同的主题样式文件。 动态引入。

    比如

    // theme1.css .demo { color: red; } // theme2.css .demo { color: green; } // theme3.css .demo { color: blue; }

    然后将3个css作为不同的css文件上传到服务器,在用户点击切换时候,link不同的css文件样式,但是弊端也很明显,需要同时维护多份主题文件,而且随着主题的扩展,将会增大维护成本

    饿了么换肤功能

    实现方式:

    先把默认主题文件中涉及到颜色的 CSS 值替换成关键词:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L250-L274

    根据用户选择的主题色生成一系列对应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/formula.json

    把关键词再换回刚刚生成的相应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/color.js

    直接在页面上加 style 标签,把生成的样式填进去:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L198-L211

    饿了么这种实现方式严格来说只能算换肤,因为只是改变了皮肤颜色。而且全局替换颜色变量是比较暴力的一种方式。对于大型项目不建议采用。

    打包不同主题的样式在一个css

    我需要的主题切换方式是不同主题的样式代码打包在一起,通过应用不同主题的class来实现主题切换。类似下面的样子

    .theme1 .demo { ... } .theme2 .demo { ... } .theme3 .demo { ... }

    这里我们通过sass的mixin来实现这样的主题映射。

    (三) 具体实现

    我们以vue为例子,实现一个主题切换方案

    在vuex中定义一个主题字段并给其对应的mutations方法和getters方法 // store/types/global.js export const GET_THEME = 'APP/GET_THEME'; export const UPDATE_THEME = 'APP/UPDATE_THEME'; // store/index.js const initState = { // ... theme: 'light', // light / dark }; const actions = {}; const getters = { // ... [globalTypes.GET_THEME](state) { return state.theme; }, }; const mutations = { // ... [globalTypes.UPDATE_THEME](state, theme) { state.theme = theme; }, }; const store = new Vuex.Store({ state: initState, actions, getters, mutations, modules: { // ... }, }); 在入口的App.vue增加对应的代码 <template> <div class="app-layout-container" :class="getThemeName"> <div class="section-view"> <div class="theme-text">{{theme}}</div> <button @click="onChangeTheme(theme === 'light' ? 'dark' : 'light')">切换主题</button> </div> </div> </template> <script> import { mapGetters, mapMutations } from 'vuex'; // constants import * as globalTypes from '@/store/types/global'; export default { name: 'app-layout', mixins: [], data() { return { }; }, computed: { ...mapGetters({ theme: globalTypes.GET_THEME, }), getThemeName() { return `theme-${this.theme}`; }, }, methods: { ...mapMutations({ onChangeTheme: globalTypes.UPDATE_THEME, }), }, mounted() { }, }; </script> <style lang="scss" scoped> </style>

    上面步骤完成后,我们已经可以使用按钮切换vuex里面对应的主题字段,然后给最外层的class增加对应的主题name字段,当我们点击时候,就可以看到效果

    设置sass混合 现在我们来给不同的主题提供选择的混合样式 // themes/common/variables.scss 配置主题变量 // theme $themes: ( light: ( // color variables primary-color: #f44, // header header-bg-color: #191a25, header-text-color: $white-color, ), dark: ( // color variables primary-color: #2977b5, // header header-bg-color: #e4393c, header-text-color: $black-color, ), ); // themes/common/mixins.scss 主题映射方法 @mixin themify($themes: $themes) { @each $theme-name, $map in $themes { .theme-#{$theme-name} & { $theme-map:() !global; @each $key, $value in $map { $theme-map: map-merge($theme-map, ($key: $value)) !global; } @content; $theme-map: null !global; } } } @function themed($key) { @return map-get($theme-map, $key); }

    这里有三点需要注意:

    您可以在主题图中定义任何CSS属性,而不仅仅是颜色;你在@themify mixin中包装所有主题属性,并通过调用themed()具有属性名称的函数来访问在themes中定义的属性。在themed()外面使用@themify不起作用。这种方法的一个限制是你不能嵌套@themify mixin。因为保持代码化的代码段尽可能短,以保持最终的CSS尽可能小,这是一个好的限制。 使用sass定制css样式

    我们给theme-text 增加对应的样式

    <style lang="scss" scoped> .section-view { .theme-text { @include themify() { color: themed('primary-color'); } } } </style>

    然后点击按钮,我们可以看到文本的主题,在我们点击按钮的时候,已经产生变化了

    // light css样式 .theme-light .section-view .theme-text[data-v-a83bd3b0] { color: #f44; } // dark 样式 .theme-dark .section-view .theme-text[data-v-a83bd3b0] { color: #2977b5; }

    这样我们就将不同的样式打包成一个文件

    定义不同的静态资源

    这里我们要使用require进行动态导入,如果不理解import和require区别的请自行去查阅资料,

    <template> <div class="app-layout-container" :class="getThemeName"> <div class="section-view"> <div style="background-color: #000; height: 40px;"> <img :src="closeIcon" alt="close" style="width: 20px; height: 20px;"> </div> <div class="theme-text">{{theme}}</div> <button @click="onChangeTheme(theme === 'light' ? 'dark' : 'light')">切换主题</button> </div> </div> </template> <script> import { mapGetters } from 'vuex'; // constants import * as globalTypes from '@/store/types/global'; // components export default { name: 'app-layout', mixins: [], data() { return { }; }, computed: { ...mapGetters({ theme: globalTypes.GET_THEME, }), getThemeName() { return `theme-${this.theme}`; }, closeIcon() { return require(`./assets/back-${this.theme}.png`); }, }, }; </script> <style lang="scss" scoped> </style>

    当我们再次点击切换时候,已经可以根据具体的项目,切换到对应的静态资源了。

    最新回复(0)