vue3+vite 一键切换主题色

1,637 阅读4分钟

实现思路整理

1.将主题通用的全局变量定义在一个sass文件中,然后在vite中配置自动导入

2.创建多个主题json文件,key与全局变量一致,值就用对应的样式代码,例如颜色、字体样式,边框样式等等

3.项目初始化的时候从本地缓存中读取当前的主题,然后读取主题json文件,遍历覆盖相同的主题变量的值

4.主题切换组件在主题切换时切换主题

实现步骤

1.定义全局主题样式变量

首先我们在src目录下创建一个styles目录,里面创建一个common.scss文件,里面定义一些通用的样式变量,如下:

  • 该文件中的变量值咱们可以随便取,因为真实的值会在初始化时通过主题json文件进行覆盖,该文件仅仅是作为变量声明,方便我们在样式代码中调用
  • 如果使用了element这类主题框架,咱们可以通过命名同样的名字来覆盖框架的默认颜色
  • src\styles\common.scss
/**全局SCSS变量  样式值以同文件夹下的json主题配置的值为主,命名推荐全拼小驼峰,这里定义的值可随意取*/

$backgroundColor: var(--background-color, #181616);
$fontColor: var(--font-color, #f41717);
$borderStyle:var(--border-style,1px solid #ccf547);
$linkColor: var(--link-color, skyblue);

// 覆盖element的颜色配置 这里配置的颜色不会覆盖,初始化的时候通过代码进行覆盖
$primaryColor: var(--el-color-primary, #fe587f);
$textColor: var(--el-text-color, #232332);
(1).我们在vite.config.ts文件中设置默认导入sass
export default defineConfig({
  ...
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/common.scss" as *;` // 引入scss变量
      }
    }
  },
  ...
})
(2).在vue组件中使用scss变量
#app-container{
  background-color: $backgroundColor;
  color: $fontColor;
}

2.定义主题模板

我们在styles目录下新建一个json目录,里面定义两个主题模板,分别命名theme-dark.json、theme-light.json:

key必须和variables中的var命名的全局名称一致,值就是当前主题设置的样式

theme-light.json:

{
  "--background-color": "#fff",
  "--font-color": "#333",
  "--border-style": "2px dashed #fff",
  "--link-color": "#b8bfc6",
  "--el-color-primary": "#837d7d",
  "--el-color-danger": "#674508"
}

theme-dark.json:

{
  "--background-color": "#333",
  "--font-color": "#fff",
  "--border-style": "1px solid #ccf547",
  "--link-color": "skyblue",
  "--el-color-primary": "#333",
  "--el-color-danger": "#e99c11"
}

3.封装颜色工具类

有了对应的主题文件,我们就需要在项目初始化的时候,使用对应的主题文件覆盖主题变量的值,这里我们把主题相关的操作封装到单独的工具类中,src/utils目录下创建themeUtils.ts,主要实现以下功能:

  • 主题颜色初始化:从缓存中获取当前主题,没有取默认值,然后加载对应的json,用json中的值进行覆盖
  • 主题颜色切换:颜色切换支持主题切换和自定义颜色切换
  • 全局颜色修改:利用sass中定义的全局变量,直接配合document.getElementsByTagName(“body”)[0].style.setProperty(key, value);即可实现动态修改

src\utils\themeUtils.ts

import themeDarkConfig from "@/styles/json/theme-dark.json";
import themeLightConfig from "@/styles/json/theme-light.json";
import {useThemeStore} from '@/store'
/**主题前缀 */
export const keyThemePrefix = "theme_";

/**主题模式 枚举 */
export const enum themeModeEnum {
  dark = "dark",
  light = "light",
}

/**主题map,方便在主题切换组件中取值 */
export const themeModeMap = new Map<string, string>([
  [themeModeEnum.dark, "暗色主题"],
  [themeModeEnum.light, "亮色主题"],
])

/**
 * 设置样式属性
 * @param key 样式名
 * @param value  样式值
 */
function setStyleProperty(key: string, value: string) {
  document.getElementsByTagName("body")[0].style.setProperty(key, value);
}
/**
 * 从缓存或默认json样式配置中全局设置样式变量
 */
export const initTheme = () => {
  themeChange(themeModeEnum.light,false);
}
/**
 * 切换主题模式
 * @param mode 主题模式
 * @param electronChange 窗口是否需要同步修改 默认需要
 */
export function themeChange(mode: string, electronChange = true) {
  // app状态管理
  const themeStore = useThemeStore();
  // 设置主题状态
  themeStore.changeThemeTitle(mode)
  // 取到对应主题的json配置
  let themeConfig;
  switch (mode) {
    case themeModeEnum.dark:
      themeConfig = themeDarkConfig;
      break;
    // 补充任意个自定义主题色.......
    default:
      themeConfig = themeLightConfig;
      break;
  }

  // 取到所有样式的key 和 value
  const styleKeys = Object.keys(themeConfig);
  const styleValues = Object.values(themeConfig);

  // 遍历设置全局scss变量的样式
  for (let i = 0; i < styleKeys.length; i++) {
    const styleKey = styleKeys[i];
    /// 走缓存或json配置的默认值
    const styleValue = styleValues[i];
    /// 设置样式
    setStyleProperty(styleKey, styleValue as string);
  }
}
export default {
  themeChange,
  initTheme,
  setStyleProperty,
  keyThemePrefix,
};

4.初始化主题色

import { initTheme } from "@/utils/themeUtils";
// .....

app.mount("#app").$nextTick(() => {
    // ......
    // 主题色初始化
  	initTheme();
});

5.主题一键切换组件

<template>
  <div>
    <el-select v-model="value" class="m-2" placeholder="Select" size="large" @change="changeTheme">
      <el-option
        v-for="item in options"
        :key="item.value"
        :label="item.label"
        :value="item.value"
      />
    </el-select>
  </div>
</template>

<script setup>
  import { ref } from 'vue'
  import {themeChange} from '@/utils/themeUtils'
  let value = ref('light')
  let options = ref([
    {key:'浅色模式',value:'light'},
    {key:'暗色模式',value:'dark'}
  ])
function changeTheme(val){
  console.log('val:',val)
  themeChange(val)
}
</script>

<style lang="scss" scoped>

</style>

主题色1.png

主题色2.png