electron+vue3全家桶+vite项目搭建【15】vue3+sass实现多主题一键切换,支持electron多窗口同步更新

news2024/11/15 4:38:26

文章目录

    • 引入
    • 实现效果展示
    • 实现思路整理
    • 实现步骤
      • 1.定义全局主题样式变量
      • 2.定义主题模板
      • 3.封装颜色工具类
      • 4.初始化主题色
      • 5.主进程监听颜色修改
      • 6.补充主题状态管理
      • 7.主题一键切换组件
      • 8.测试案例

引入

我们之前在这篇文章中集成了 sass,接下来我们结合sass的变量定义,在vue3+vite+electron的环境中实现一键切换主题色

  • 该方式适用于web项目和electron项目
  • 已适配electron多窗口同步更新主题风格
  • 如果只是web项目,删除掉 ipcRedner部分的代码即可

demo项目地址

实现效果展示

请添加图片描述

实现思路整理

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

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

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

4.主题初始化时渲染进程开启主题改变监听,主进程同样监听主题改变,主题切换组件在主题切换时通知主进程,然后主进程通知所有窗口同步切换主题

实现步骤

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

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

  • 该文件中的变量值咱们可以随便取,因为真实的值会在初始化时通过主题json文件进行覆盖,改文件仅仅是作为变量声明,方便我们在样式代码中调用
  • 如果使用了element这类主题框架,咱们可以通过命名同样的名字来覆盖框架的默认颜色

​ src\styles\variables.scss

/**全局SCSS变量  样式值以同文件夹下的json主题配置的值为主,命名推荐全拼小驼峰,这里定义的值可随意取*/

$backgroundColor: var(--background-color, #fff);
$linkColor: var(--link-color, skyblue);

// 覆盖element的颜色配置 这里配置的颜色不会覆盖,初始化的时候通过代码进行覆盖
$primaryColor: var(--el-color-primary, #fe587f);
$textColor: var(--el-text-color, #232332);

2.我们在vite.config.ts文件中设置默认导入sass,并设置element的样式导入方式:

  • 配置默认导入的sass文件
  • 如果需要修改element样式,那么之前咱们配的自动导入也得补充sass的引入
export default defineConfig(({ command }) => {
    // ......
    return {
        // ......
        plugins: [
            AutoImport({
                // ......
                resolvers: [
                    ElementPlusResolver({
                        importStyle: "sass", // 设置导入样式
                    }),
                ],
            }),
            Components({
                resolvers: [
                    ElementPlusResolver({
                        importStyle: "sass", //设置导入样式
                    }),
                ],
            }),

        ],
        // ......
        css: {
            preprocessorOptions: {
                scss: {
                    additionalData: `@use '@/styles/variables.scss' as *;`, // 引入全局变量文件
                },
            },
        },
    });

3.此时大家可以直接在代码中设置预定义的颜色,看看是否生效,如下:

  • 这里给demo/index.vue中的h1设置了一个颜色,注意,我们在variables.scss中设置的element同名颜色,因为加载优先级更高,所以值会被后加载的element颜色覆盖掉,之后我们配置样式json走代码初始化覆盖即可

src\components\demo\Index.vue

h1 {
  color: $linkColor;
}

请添加图片描述

2.定义主题模板

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

  • 这里使用json单纯觉得k-v的形式很方便,你也可以用yaml等格式
  • key必须和variables中的var命名的全局名称一致,值就是当前主题设置的样式

theme-dark.json:

{
  "--background-color": "#363b40",
  "--link-color": "pink",
  "--el-color-primary": "#4e6ef2",
  "--el-text-color": "#b8bfc6"
}

theme-light.json:

{
  "--background-color": "#fff",
  "--link-color": "skyblue",
  "--el-color-primary": "#fe587f",
  "--el-text-color": "#232332"
}

目录结构如下所示:

请添加图片描述

3.封装颜色工具类

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

  • 主题颜色初始化:从缓存中获取当前主题,没有取默认值,然后加载对应的json,用json中的值进行覆盖
  • 主题颜色切换:颜色切换支持主题切换和自定义颜色切换,自定义颜色存在缓存中,切换主题颜色时,相同key优先走缓存中的值,实现不同主题可共用用户自定义的颜色
  • 全局颜色修改:利用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 cacheUtils from "@/utils/cacheUtils";
import { ipcRenderer } from "electron";
import { useAppStore } from "@/store/modules/appStore";

/**主题前缀 */
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 = () => {
  // 从缓存中取出当前主题模式,默认为 light模式
  themeChange(
    cacheUtils.get(keyThemePrefix + "mode") || themeModeEnum.light,
    false
  );

  // 监听主进程通知样式修改时,同步窗口更新样式
  ipcRenderer.on("theme-style:changed", (event, mode: string) => {
    // 有mode说明是走主题切换
    if (mode) {
      /// 必须指定electron不同步,不然会循环调用
      themeChange(mode, false);
    } else {
      // 没有传mode,则是用户自定义样式修改,此时只修改本地存储的样式
      // 取到所有样式的key 和value
      const styleKeys = Object.keys(themeLightConfig);
      for (const styleKey of styleKeys) {
        const styleValue: string = cacheUtils.get(keyThemePrefix + styleKey);
        if (styleValue) {
          /// 设置样式
          setStyleProperty(styleKey, styleValue);
        }
      }
    }
  });
};

/**
 * 切换主题模式
 * @param mode 主题模式
 * @param electronChange 窗口是否需要同步修改 默认需要
 */
export function themeChange(mode: string, electronChange = true) {
  // app状态管理
  const appStore = useAppStore();
  // 设置主题状态
  appStore.theme = mode;
  // 设置缓存为对应主题
  cacheUtils.set(keyThemePrefix + "mode", mode);
  // 通知主进程告诉其他窗口同步修改主题样式
  if (electronChange) {
    ipcRenderer.invoke("theme-style:change", 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: string =
      cacheUtils.get(keyThemePrefix + styleKey) || styleValues[i];
    /// 设置样式
    setStyleProperty(styleKey, styleValue);
  }
}

/**
 * 修改全局样式的值
 * @param param scss定义的样式名称,参考 {src\styles\variables.scss}
 * @param value 样式值
 */
export function styleChange(param: string, value: string) {
  // 修改页面的变量
  setStyleProperty(param, value);

  // 修改缓存值
  cacheUtils.set(keyThemePrefix + param, value);
}

export default {
  themeChange,
  initTheme,
  styleChange,
  keyThemePrefix,
};

4.初始化主题色

与我之前讲的多窗口,多语言同步问题类似,我们应当讲主题色的初始化放在app挂载后执行,我们同样在 src/main.ts中补充逻辑:

src\components\demo\Index.vue

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

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

如果你之前有使用覆盖element主题色的变量,你就会发现,此时element的主题色已经被我们json中配置的值覆盖掉了,例如我调整 /demo/index.vue中的代码:

h1 {
  color: $primaryColor;
}

请添加图片描述

5.主进程监听颜色修改

我们在主进程中补充样式修改的监听处理逻辑:

  • 如果主题模式是electron支持的模式,那么electron的窗口主题也同步修改主题样式
  • 通知除通知主进程修改样式的窗口外的所有窗口同步修改样式

electron\main\index.ts

import { app, BrowserWindow, shell, ipcMain, nativeTheme } from "electron";

// 主题样式修改同步
ipcMain.handle(
  "theme-style:change",
  (event, mode?: "system" | "light" | "dark") => {
    if (mode && "system,light,dark".indexOf(mode) >= 0) {
      nativeTheme.themeSource = mode;
    }
    // 通知所有窗口同步更改样式
    // 遍历window执行
    for (const currentWin of BrowserWindow.getAllWindows()) {
      const webContentsId = currentWin.webContents.id;
      if (webContentsId !== event.sender.id) {
        currentWin.webContents.send("theme-style:changed", mode);
      }
    }
  }
);

6.补充主题状态管理

我们在appStore中补充主题相关的状态管理:

  • 这里管理的全局主题状态主要是方便我们在任何页面快速获取的当前的主题
  • 关于 多窗口的pinia全局状态管理同步问题关注我后续的文章 【加急创作中】

src\store\modules\appStore.ts

import { defineStore } from "pinia";
import cacheUtils from "@/utils/cacheUtils";
import { themeMode, keyThemePrefix } from "@/utils/themeUtils";

/**应用相关状态管理 */
export const useAppStore = defineStore("appStore", {
  state() {
    return {
      lang: cacheUtils.get("lang") || "zhCn", // app的语言
      theme: cacheUtils.get(keyThemePrefix + "mode") || themeMode.light, // app的主题
    };
  },
});

7.主题一键切换组件

这里我们可以模仿多语言切换组件来写一个主题色切换组件:

src\components\ThemeSwitch.vue

<template>
  <el-dropdown @command="handleCommand">
    <span class="el-dropdown-link">
      {{ themeModeMap.get(currentTheme) }}
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          :command="themeModeEntry[0]"
          :key="themeModeEntry[0]"
          :disabled="currentTheme == themeModeEntry[0]"
          v-for="themeModeEntry in themeModeMap.entries()"
          >{{ themeModeEntry[1] }}</el-dropdown-item
        >
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script lang="ts" setup>
import { ArrowDown } from "@element-plus/icons-vue";
import { ref } from "vue";
import themeUtils, { themeModeMap } from "@/utils/themeUtils";
import { useAppStore } from "@/store/modules/appStore";

const appStore = useAppStore();
const currentTheme = ref(appStore.theme);

// 切换主题
function handleCommand(theme: string) {
  currentTheme.value = theme;
  themeUtils.themeChange(theme);
}
</script>
<style scoped lang="scss">
.example-showcase .el-dropdown-link {
  cursor: pointer;
  color: $primaryColor;
  display: flex;
  align-items: center;
}
</style>

8.测试案例

我们写一个主题一键切换案例,并且绑上路由:

src\components\demo\ThemeDemo.vue

<template>
  <div class="box">
    <h1>多主体一键切换</h1>
    <a href="">这是一个a链接</a>
    <el-button type="primary">element的primary按钮</el-button>
    <ThemeSwitch></ThemeSwitch>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.box {
  background-color: $backgroundColor;
  color: $textColor;
  h1 {
    color: $primaryColor;
  }
  a {
    color: $linkColor;
  }
}
</style>

演示效果如下:

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/464632.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

银行数字化转型导师坚鹏:宏观经济形势分析与银行发展模式创新

宏观经济形势分析与银行发展模式创新 课程背景&#xff1a; 很多学员存在以下问题&#xff1a; 不知道我国目前的宏观经济形势&#xff1f; 不清楚宏观环境对我国经济的影响&#xff1f; 不知道银行未来主要的发展模式&#xff1f; 课程特色&#xff1a; 精彩解读宏…

最新:机器学习在生态、环境经济学中的实践技术应用及论文写作

查看原文>>>最新&#xff1a;机器学习在生态、环境经济学中的实践技术应用及论文写作 目录 专题一、理论基础与软件介绍 专题二、数据的获取与整理 专题三、常用评价方法与相关软件详细教学&#xff08;案例详解&#xff09; 专题四、写作要点与案例的讲解 近年来…

Redis数据库常用语句

Redis数据库常用语句 前言1. 键(Key)的基本操作1.1 增加新的键值对1.2 访问键的值1.3 修改键值对1.4 键值对的删除1.5 判断键值对是否存在1.6 获取所有键1.7 删除所有的键&#xff1a; 2. Redis 中的列表2.1 列表加入新元素2.2 获取列表长度2.3 获取指定下标的元素2.4 获取指定…

Android App 架构 面试专题,你可能会被问到的 20 个问题

iveData 是否已经被弃用? 没有被弃用。在可以预见的未来也没有废弃的计划。 LiveData 可以使用简单的方式获取一个易于观察、状态安全的对象。虽然其缺少一些丰富的操作符&#xff0c;但是对于一些简单的 UI 业务场景已经足够。 Flow 有 LiveData 相同的功能&#xff0c;其…

1.栈的介绍-C语言调用函数(二)

1.栈的介绍-C语言调用函数&#xff08;一&#xff09;_双层小牛堡的博客-CSDN博客 接着上面 函数调用的约定 在栈帧中 主要的是主调函数如何存入实参 让被调用函数能够访问 这种是通过函数见的调用规定来规范的 并且 调用规定还规范了 函数执行完后应该由主函数实现 清除参…

[测试猿课堂]小白怎么学测试?史上最全《软件测试》学习路线

熬夜3天&#xff0c;联合3位猿计划教育的总监级授课老师&#xff0c;整理了这份《软件测试小白学习路线》&#xff0c;全文接近6000字&#xff0c;请大家耐心看完&#xff01; 对于很多想通过自学转行软件测试的同学&#xff0c;痛点并不是学习动力&#xff0c;而是找不到清晰…

Apache SeaTunnel 3 分钟入门指南

简介 新一代分布式超高性能云原生数据同步工具 - Apache SeaTunnel 已经在B站、腾讯云、字节等数百家公司使用。 SeaTunnel 是 Apache 软件基金会下的一个高性能开源大数据集成工具&#xff0c;为数据集成场景提供灵活易用、易扩展并支持千亿级数据集成的解决方案。SeaTunnel …

《计算机网络--自顶向下方法》第三章--运输层

3.1概述和运输层服务 运输层协议为运行再不同主机上的应用进程之间提供了逻辑通信&#xff08;logic communication&#xff09;功能 运输层协议是在端系统中而不是在路由器中实现的 3.1.1运输层和网络层的关系 运输层协议至工作在端系统中 在端系统中&#xff0c;运输层…

基于Mybatis使用MySql存储过程,实现数据统计功能

1、前言 作为一个工作了很多年的程序员来说&#xff0c;没有在实际工作中真正使用过存储过程&#xff0c;其实对存储过程本身有过了解和学习&#xff0c;在日常的学习中&#xff0c;也会看过一些存储过程的相关介绍&#xff0c;不过“纸上得来终是浅”&#xff0c;正好这次做统…

Linux 利用网络同步时间

yum -y install ntp ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ntpdate ntp1.aliyun.com 创建加入crontab echo "*/20 * * * * /usr/sbin/ntpdate -u ntp.api.bz >/dev/null &" >> /var/spool/cron/rootntp常用服务器 中国国家授…

力扣sql中等篇练习(十三)

力扣sql中等篇练习(十三) 1 每位学生的最高成绩 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 #先找到最大的元素 然后分组即可,不用管某些字段(grade)是不是聚合字段 SELECT e1.student_id,min(e1.course_id) course_id,e1.grade FROM Enrollment…

setup.py方式打包自己的python代码并可以用pip install安装

setup.py方式打包自己的python代码并可以用pip install安装 所需文件及目录规范示例演示引用自己打的包 所需文件及目录规范 注意setup.py文件和MANIFEST.in文件需要放在和你需要打包的目录同一级下&#xff0c;例如我这里需要打包的就是webconsole文件夹&#xff08;这里webc…

gl-opendrive插件(车俩3D仿真模拟自动驾驶)

简介 本插件基于免费opendrive开源插件、Threejs和Webgl三维技术、vue前端框架&#xff0c;blender开源建模工具等进行二次开发。该插件由本人独立开发以及负责&#xff0c;目前处于demo阶段&#xff0c;功能还需待完善&#xff0c;由于开发仓促代码还需优化。 因此&#xff…

35岁测试人,面临职场危机,打了一场漂亮的翻身仗...

“夜深知雪重&#xff0c;时闻折竹声”。雪折&#xff0c;一种在雪的载荷下&#xff0c;植物&#xff08;多指树&#xff09;的躯干或枝条被不断堆积的雪花压断的现象。我的刚刚经历了人生的第一次“雪折”。 我是一个有点聪明且勤奋好学的人&#xff0c;从考入省重点大学起&a…

Windows环境下C++ 安装OpenSSL库 源码编译及使用(VS2019)

参考文章https://blog.csdn.net/xray2/article/details/120497146 之所以多次一举自己写多一篇文章&#xff0c;主要是因为原文内容还是不够详细。而且我安装的时候碰到额外的问题。 1.首先确认一下自己的代码是Win32的还是Win64的&#xff0c;我操作系统是64的&#xff0c;忘…

java websocket实现聊天室 附源码

目录 1.Socket基础知识 2.socket代码实现 2.1 引入依赖 2.2 配置websocket 2.3 websocket的使用 2.4 webSocket服务端模块 2.5 前端代码 3.测试发送消息 4.websocket源码地址 1.Socket基础知识 Socket&#xff08;套接字&#xff09;用于描述IP地址和端口&#xff0c…

4年测试工作经验,跳槽之后面试20余家公司的总结

先说一下自己的个人情况&#xff0c;普通二本计算机专业毕业&#xff0c;懂python&#xff0c;会写脚本&#xff0c;会selenium&#xff0c;会性能&#xff0c;然而离职后到今天都没有收到一份offer&#xff01;一直在待业中&#xff0c;从离职第一天就开始准备简历&#xff0c…

【Vue 基础】尚品汇项目-02-路由组件的搭建

项目路由说明&#xff1a; 前端的路由&#xff1a;Key-Value键值对 Key&#xff1a;URL&#xff08;地址栏中的路径&#xff09; Value&#xff1a;相应的路由组件 作用&#xff1a;设定访问路径&#xff0c;并将路径和组件映射起来&#xff08;就是用于局部刷新页面&#xff0…

Vue+Openlayers+proj4实现坐标系转换

场景 Vue中使用Openlayers加载Geoserver发布的TileWMS&#xff1a; Vue中使用Openlayers加载Geoserver发布的TileWMS_霸道流氓气质的博客-CSDN博客 在上面的基础上实现不同坐标系坐标数据的转换。 Openlayers中默认的坐标系是EPSG:900913 EPSG:900913等效于EPSG:3857 可在…

kafka集群压测与优化

影响kafka集群性能的因数有多个&#xff0c;网络带宽、cpu、内存、磁盘读写速度、副本数、分区数、broker数量、内存缓存等因素都会影响kafka集群的性能 1.优化kafka集群配置 server.properties配置文件优化 num.network.threads4 num.io.threads4 socket.send.buffer.bytes…