当前位置: 首页 > article >正文

electron编写一个macOS风格的桌面应用

electron编写一个macOS风格的桌面应用

基于vue3+vite,看一下最后的效果:

image-20250114111720892

针对原始的electron模板,做了如下几点调整:

  • 背景边框进行了圆角处理
  • 隐藏了原始的titleBar
  • 增加了macOS风格的窗口管理工具,就是交通灯按钮组实现最大化/最小化/还原、关闭,并增加按钮点击事件

一、背景边框圆角处理

electron并不支持对桌面应用进行圆角处理,需要相对较为复杂的逻辑处理,思路如下:

  1. 主进程背景设置透明
  2. 渲染进程的入口组件设置背景圆角
  3. 入口页面隐藏滚动条

主进程中的核心代码:

// main.js
const { app, BrowserWindow } = require('electron');

app.whenReady().then(() => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false, // 移除默认框架
    transparent: true, // 使窗口背景透明
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
    },
  });

  mainWindow.loadURL('http://localhost:3000'); // 加载Vue应用或HTML文件
});

入口组件中的核心代码:

// App.vue
<template>
  <div class="mainWin">
    <RouterView />
  </div>
</template>

<script setup>
import { RouterView } from 'vue-router';
import { ref } from 'vue';
</script>

<style lang="scss" scoped>
.mainWin {
  width: 100%;
  height: 100vh;
  background-color: #fff;
  border-radius: 5px;
  overflow-y: hidden;
  display: flex;
  flex-direction: column;
}

</style>

入口页面文件中的核心代码:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Electron</title>
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <!-- <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
    /> -->
    <style>
      body,
      html {
        overflow: hidden;
        border-radius: 5px;
      }
    </style>
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

overflow: hidden;是用于隐藏滚动条,经测试,写在index.html文件中才生效,边框圆角写在app组件中生效

二、隐藏原始titleBar,增加交通灯按钮

1、隐藏titleBar

主进程中设置titleBarStyle: ‘hidden’,即可

如下:

function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    titleBarStyle: 'hidden',
    transparent: true,
    frame: process.platform === 'darwin',
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })
  .....省略其他代码
}

2、交通灯样式

逻辑是在app组件中添加header标签,添加按钮组,并设置样式

// App.vue
<script setup>
import { RouterView } from 'vue-router'
import { ref } from 'vue'

// 方法用于最小化、关闭窗口以及切换最大化/还原状态
const isMaximized = ref(false)
const minimizeWindow = () => {
  if (window.api.platform !== 'darwin') {
    window.api.minimize()
  }
}

const maximizeWindow = () => {
  if (isMaximized.value) {
    window.api.unmaximize()
  } else {
    window.api.maximize()
  }
  isMaximized.value = !isMaximized.value
}

const closeWindow = () => {
  window.api.close()
}

const toggleMaximize = () => {
  window.api.toggleMaximize()
}
</script>

<template>
  <div class="mainWin">
    <header class="window-header" @dblclick="toggleMaximize">
      <!-- <span class="title">My App</span> -->
      <div class="controls">
        <!-- 自定义交通灯按钮 -->
        <button
          @click="minimizeWindow"
          class="traffic-light"
          :class="'minimize'"
          title="最小化"
        ></button>
        <button
          @click="maximizeWindow"
          class="traffic-light"
          :class="isMaximized ? 'restore' : 'maximize'"
          :title="isMaximized ? '还原' : '最大化'"
        ></button>
        <button @click="closeWindow" class="traffic-light" :class="'close'" title="关闭"></button>
      </div>
    </header>
    <RouterView />
  </div>
</template>

<style lang="scss" scoped>
.mainWin {
  /* 添加背景图片 */
  background-image: url('./assets/imgs/background.jpg');
  backdrop-filter: blur(10px); /* 模糊背景 */
  background-size: cover; /* 自适应填充 */
  background-position: center; /* 居中显示 */
  background-repeat: no-repeat; /* 避免重复 */
  width: 100%;
  height: 100vh;
  // margin: 0;
  // padding: 0;
  background-color: #fff;
  border-radius: 5px;
  // overflow-y: hidden;
  .window-header {
    -webkit-app-region: drag;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
    background-color: #f0f0f0;
    border-bottom: 1px solid #ddd;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    flex-shrink: 0;
  }

  .controls {
    display: flex;
    align-items: center;
  }

  .traffic-light {
    -webkit-app-region: no-drag;
    width: 12px;
    height: 12px;
    margin-left: 8px;
    border-radius: 50%;
    background-color: transparent;
    border: none;
    cursor: pointer;
    transition: all 0.2s ease;

    &:hover {
      transform: scale(1.2);
    }

    &.minimize {
      background-color: #ffcc00; // 黄色
    }

    &.maximize {
      background-color: #9fac00; // 绿色
    }

    &.restore {
      background-color: #9fac00; // 恢复按钮也用绿色
    }

    &.close {
      background-color: #ff4f38; // 红色
    }
  }

  .title {
    -webkit-app-region: drag;
    font-size: 14px;
    font-weight: bold;
  }
}
</style>

3、交通灯按钮组点击响应逻辑

窗口的最大最小化以及还原操作需要调用window底层api,因此需要渲染进程和主进程进行通讯,逻辑如下:

  1. 渲染进程点击对应的按钮,就是上面代码中的点击事件

  2. 在预加载进程中分别添加最大化、最小化、还原、关闭以及拖动窗口的api,同时通过ipcRenderer向主进程发送触发信号

    // preload/index.js
    import { contextBridge, ipcRenderer } from 'electron'
    import { electronAPI } from '@electron-toolkit/preload'
    
    // Custom APIs for renderer
    const api = {
      // 添加一个方法来打开新窗口
      openNewWindow: async () => {
        try {
          const response = await ipcRenderer.invoke('open-new-window')
          return response
        } catch (error) {
          console.error('Failed to open new window:', error)
          throw error
        }
      },
      // 窗口变化的api
      minimize: () => ipcRenderer.send('minimize-window'),
      maximize: () => ipcRenderer.send('maximize-window'),
      unmaximize: () => ipcRenderer.send('unmaximize-window'),
      close: () => ipcRenderer.send('close-window'),
      toggleMaximize: () => ipcRenderer.send('toggle-maximize'),
      platform: process.platform,
    }
    
    // Use `contextBridge` APIs to expose Electron APIs to
    // renderer only if context isolation is enabled, otherwise
    // just add to the DOM global.
    if (process.contextIsolated) {
      try {
        contextBridge.exposeInMainWorld('electron', electronAPI)
        contextBridge.exposeInMainWorld('api', api)
      } catch (error) {
        console.error(error)
      }
    } else {
      window.electron = electronAPI
      window.api = api
    }
    

    platform: process.platform,是为了在渲染进程中可以获取到操作系统的信息

  3. 主进程通过ipcMain接收指令,并对窗口进行对应操作

    ipcMain.on('minimize-window', () => {
        mainWindow.minimize()
      })
    
      ipcMain.on('maximize-window', () => {
        mainWindow.maximize()
      })
    
      ipcMain.on('unmaximize-window', () => {
        mainWindow.unmaximize()
      })
    
      ipcMain.on('close-window', () => {
        mainWindow.close()
      })
    
      ipcMain.on('toggle-maximize', () => {
        if (mainWindow.isMaximized()) {
          mainWindow.unmaximize()
        } else {
          mainWindow.maximize()
        }
      })
    

http://www.kler.cn/a/503926.html

相关文章:

  • 华为2024嵌入式研发面试题
  • golang运维开发-gopsutil(2)
  • 【绝对无坑】Mongodb获取集合的字段以及数据类型信息
  • Entity 的材质(棋盘、条纹、网格)
  • FastDDS安装测试记录
  • LeetCode-493. Reverse Pairs
  • 硬件知识:显示器发展历程介绍
  • 算法题之反转字符串
  • 深入理解观察者模式 —— Qt信号槽机制的实现
  • 通过一个算法的设计来了解栈的一些应用
  • 【Audition】Audition如何在波形中插入静音且插入边缘不做平滑处理
  • 使用sqlplus的easy connect时如何指定是链接到shared server还是dedicated process
  • 【01】AE特效开发制作特技-Adobe After Effects-AE特效制作快速入门-制作飞机,子弹,爆炸特效以及导出png序列图-优雅草央千澈
  • 三相无刷电机控制|FOC理论04 - 克拉克变换 + 帕克变换的最终目标
  • ubuntu Android : adb logcat 过滤多个log
  • RAG 测评基线
  • git相关操作
  • Linux入门——权限
  • 学习笔记080——如何备份服务器中Docker创建的MySQL数据库数据?
  • [Linux] GDB 和 CGDB的使用及理解
  • 国产编辑器EverEdit - 打印与打印预览
  • 如何编写和运行 Lua 脚本优化复杂的 Redis 操作
  • 计算机视觉算法实战——视频分析(Video Analysis)
  • Linux 服务器挖矿木马防护实战:快速切断、清理与加固20250114
  • 【自然语言处理】P1 自然语言处理概述
  • 【SpringSecurity】SpringSecurity安全框架登录校验流程与登录配置示例