Electron
图
Electron是使用JavaScript,HTML和CSS构建跨平台的桌面应用程序框架。Electron兼容Mac、Windows和Linux,可以构建出三个平台的应用程序,让前端开发者也能写出桌面端应用

安装

npm init
cnpm i electron

package.json中指定启动命令

"scripts": {
  "dev": "electron ."
},

然后根据入口文件名称创建对应的文件,写入最简单的代码

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
  let mainWindow = new BrowserWindow({
    width: 500,
    height: 300
  })
})

加载资源

// 加载网络资源
mainWindow.loadURL('http://xxxx')
// 加载本地资源
mainWindow.loadFile('src/index.html')

全屏打开及关闭事件

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
  let mainWindow = new BrowserWindow({
    // 完全关闭菜单栏
    frame: false
  })
  // 设置窗口为全屏
  mainWindow.setFullScreen(true)
  // 加载网络资源
  mainWindow.loadURL('http://localhost:7981/')
  // 当窗口关闭时调用的方法
  mainWindow.on('closed', function () {
    console.log('close win');
    mainWindow = null
  })
})

控制台

打开调试控制台配置

// 打开控制台
mainWindow.webContents.openDevTools()

关闭控制台警告

main.js中加入

process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

注意:需要在打开控制台之前执行

自定义菜单

const { app, BrowserWindow, Menu } = require('electron')

app.on('ready', () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 500,
  })

  // 加载本地资源
  mainWindow.loadFile('src/index.html')

  Menu.setApplicationMenu(Menu.buildFromTemplate([
    {
      label: '同步云端',
      submenu: [
        {
          label: '同步到Gitee',
          click() {
            console.log('点击事件');
          }
        }
      ]
    },
    {
      label: '发布笔记'
    }
  ]))

})

完全自定义头部

先关闭菜单栏

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 500,
     frame: false // 完全关闭菜单栏
  })

  // 加载本地资源
  mainWindow.loadFile('src/index.html')
})

然后自己写菜单栏并实现可拖拽

<style>
  body {
    margin: 0;
  }
  .header {
    width: 100%;
    height: 60px;
    background-color: aquamarine;
    /* 窗口可拖拽 */
    -webkit-app-region: drag;
  }
  .header span {
    /* 关闭拖拽防止影响点击事件 */
    -webkit-app-region: no-drag;
  }
</style>
<body>
  <div class="header">
    <span>保存</span>
    <span>新建</span>
  </div>
</body>
</html>

渲染进程

除了入口js文件外都属于渲染进程,一个程序只有一个主进程,而渲染进程要使用Node相关的命令和语法,需要进行配置

const mainWindow = new BrowserWindow({
  width: 800,
  height: 500,
  webPreferences: {
    // 开启渲染进程使用Node
    nodeIntegration: true,
    // 控制上下文隔离
    contextIsolation: false,
  }
})

然后就可以在子进程中使用

const { shell } = require('electron')

// 所有a标签使用浏览器打开
let allA = document.querySelectorAll('a')
allA.forEach(item => {
  item.onclick = function (e) {
    e.preventDefault()
    shell.openExternal(item.href)
  }
})

使用fs

const fs = require('fs')

渲染进程与主进程通信

注意需要先配置webPreferences,上一步有说明

const { ipcMain } = require('electron')
// 自定义事件
ipcMain.on('event-name', (event, arg) => {
  console.log(event);
  console.log(arg);
  mainWindow.maximize()
})

// 触发事件,在使用的页面加引入,支持Vue等
const { ipcRenderer } = require('electron')
ipcRenderer.send('event-name', '传递参数')

主进程可以使用回调给渲染进程传数据

// 主进程
ipcMain.on('marked', (e, a) => {
  mainWindow.webContents.send('back-html', marked.parse(a))
})

// 渲染进程
ipcRenderer.on('back-html', (e, a) => {
  console.log(a);
})

打包

下载打包依赖

cnpm i electron-packager

然后配置打包命令

"build": "electron-packager ./ my-app --platform=win32 --arch=x64 --out ./target --overwrite --icon=./favicon.ico --electron-zip-dir=D:/Bb_work/electron"
// 32位
"build": "electron-packager ./ my-app --platform=win32 --arch=ia32 --out ./target --overwrite --icon=./favicon.ico --electron-zip-dir=D:/Bb_work/electron"

最后指定了打包源文件的位置,防止因网络问题无法打包,点击前往下载地址

注意:如果打包报错一下信息:

Failed to locate module "cross-spawn" from "D:\La_temp\900\node_modules\cross-env"

        This normally means that either you have deleted this package already somehow (check your ignore settings if using electron-packager).  Or your module installation failed.

代表npm在下载依赖时只是下载了第一层的依赖,内部依赖并没有下载,进入一级依赖内执行npm i为其下载才能解决

使用JQ

<script src="lib/jquery.min.js"></script>
<script>if (typeof module === 'object') { window.jQuery = window.$ = module.exports; };</script>

窗口大小/关闭控制

// 关闭窗口
ipcMain.on('win-close', (e, a) => {
  mainWindow.close()
})
// 最大化窗口
ipcMain.on('win-max', (e, a) => {
  mainWindow.maximize()
})
// 恢复窗口大小
ipcMain.on('win-res', (e, a) => {
  mainWindow.restore()
})
// 最小化窗口
ipcMain.on('win-min', (e, a) => {
  mainWindow.minimize()
})

如果不想让可拖拽的元素双击最大化可以关闭,在主文件窗口配置中

// 禁用双击最大化窗口,自己控制
maximizable: false

结合Vite

学习文章地址

创建vite项目

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
npm i

安装各种依赖

cnpm i electron concurrently wait-on cross-env electron-packager

其中:concurrently用于多命令执行,加入wait-on实现热更新,cross-env用于指定开发环境和生产环境,electron-packager用于打包

配置

第一步:创建electron文件夹,加入两个js文件

preload.js用于隔离electron和vue的环境

window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const dependency of ['chrome', 'node', 'electron']) {
    replaceText(`${dependency}-version`, process.versions[dependency])
  }
})

main.js为程序的主要入口

// 控制应用生命周期和创建原生浏览器窗口的模组
const { app, BrowserWindow } = require('electron')
const path = require('path')

const NODE_ENV = process.env.NODE_ENV

function createWindow() {
  // 创建浏览器窗口
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // 通过环境加载不同的资源
  mainWindow.loadURL(NODE_ENV === 'development' ? 'http://localhost:5173' : `file://${path.join(__dirname, '../dist/index.html')}`);

  // 开发中打开控制台
  if (NODE_ENV === 'development') {
    // 打开控制台
    mainWindow.webContents.openDevTools()
  }
}

// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
  createWindow()
  app.on('activate', function () {
    // 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他
    // 打开的窗口,那么程序会重新创建一个窗口。
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
// 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

第二步:配置package.json中的脚本,加入以下内容

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview",
  // 加入下列内容
  "electron": "wait-on tcp:5173 && cross-env NODE_ENV=development electron .",
  "electron:serve": "concurrently -k \"npm run dev\" \"npm run electron\"",
  "electron:preview": "vite build && electron .",
  "electron:build": "vite build && electron-packager ./ my-app --platform=win32 --arch=x64 --out ./target --overwrite --icon=./favicon.ico --electron-zip-dir=D:/Bb_work/electron"
}

同时删除"type": "module",新增"main": "electron/main.js"

第三步:配置vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  base: "./", // 新增
  plugins: [vue()],
})

此操作是让Vue打包后的资源以相对路径进行访问,否则无法加载资源

打包依赖问题

npm和cnpm有个问题是存在递归引用问题,在打包时需要一个一个安装依赖中的依赖,比较麻烦,而yarn的管理方式不存在这些问题,所以改为yarn包管理工具

npm i -g yarn
yarn --version

yarn init - 初始化新项目并创建 package.json 文件
yarn add [package] - 将一个或多个包添加到项目中
yarn remove [package] - 从项目中移除一个或多个包
yarn install - 安装项目中所有依赖项
yarn upgrade [package] - 更新一个或多个包至最新版本
yarn run [script] - 运行在 package.json 中定义的脚本
yarn build - 执行构建命令以生成生产环境所需的文件
yarn start - 启动应用程序或服务器
yarn test - 运行测试套件

安装electron一直转圈问题

// npm包管理器
npm config set ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron/
yarn包管理器
yarn config set ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron/

electron-packager打包忽略

electron-packager ./ desktopClient --platform=win32 --arch=x64 --out ./target --overwrite --icon=./favicon.ico --electron-zip-dir=./pack_dep --ignore=pack_dep

日志问题

如果不使用控制(生产环境没有),需要electron-log完成

yarn add electron-log

使用

const log = require('electron-log')

// 设置日志输出位置
// 设置日志文件的路径
log.transports.file.resolvePath = () => path.join(__dirname, '/logs/main.log');

// 开启控制台输出
log.transports.console.level = 'debug'

// 初始化electron-log模块
log.info('Application starting...')
// log.debug()、log.info()、log.warn()和log.error()

资源内容限制访问问题

删除index.htmlmeta,然后配置

// 创建浏览器窗口
mainWindow = new BrowserWindow({
  // 完全关闭菜单栏
  frame: false,
  webPreferences: {
    // 禁用内容安全策略
    webSecurity: false,
    // 开启渲染进程使用Node
    nodeIntegration: true, // 控制上下文隔离
    contextIsolation: false, preload: path.join(__dirname, 'preload.js'),
  }
})

process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

选择文件夹

// 选择文件夹
ipcMain.on('select-folder', () => {
  dialog
    .showOpenDialog({ properties: ['openDirectory'] })
    .then((result) => {
      if (!result.canceled) {
        const folderPath = result.filePaths[0]
        console.log('Selected folder:', folderPath)
        // 返回选择的文件夹,绝对值
        mainWindow.webContents.send('select-folder-back', folderPath)
      }
    })
    .catch((err) => {
      console.log(err)
    })
})

允许加载本地资源

// 创建浏览器窗口
mainWindow = new BrowserWindow({
  // 窗口宽度
  width: 1200,
  // 窗口高度
  height: 800,
  // 完全关闭菜单栏
  frame: false,
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    // 开启渲染进程使用Node
    nodeIntegration: true,
    // 关闭上下文隔离
    contextIsolation: false,
    // 关闭安全策略,允许加载本地资源
    webSecurity: false,
  },
})

PNG转ICO

https://bfotool.com/zh/png-to-ico

窗口总是置顶

// 创建浏览器窗口
mainWindow = new BrowserWindow({
  // 完全关闭菜单栏
  frame: false,
  // 总是置顶
  alwaysOnTop: true,
  webPreferences: {
    // 禁用内容安全策略
    webSecurity: false,
    // 开启渲染进程使用Node
    nodeIntegration: true,
    // 控制上下文隔离
    contextIsolation: false,
    preload: path.join(__dirname, 'preload.js'),
  },
})

开启透明

app.on("ready", () => {
  let mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    transparent: true, // 开启透明
    frame: false, // 去掉窗口边框,透明才会生效
  });
  // 加载本地资源
  mainWindow.loadFile("index.html");
});

加flash插件

https://newsn.net/say/electron-flash-win.html
https://www.flash.cn/download-wins