Electron入门

1,389 阅读15分钟

前言

先从官网教程开始搭建一个简单的Electron,

然后通过vite脚手架重新搭建一个 vite + electron + vue3 + UI的项目。

原生态入门

初始化环境

Windows下搭建一个Electron,先贴一个官方网站Electron (electronjs.org)

检查 Node.js 是否已被安装

没安装的官网下载 Node.js (nodejs.org) 长期稳定版LTS即可

$ node -v
v16.14.2
$ npm -v
8.7.0

初始化项目

1. 创建目录并初始化项目

mkdir ele-app && cd ele-app
npm init

2. 添加electron依赖

npm install electron --save-dev

可能出现electron安装不下来,那么,换源

npm config set registry https://registry.npmmirror.com
npm config set electron_mirror https://cdn.npmmirror.com/binaries/electron/
npm config set electron_builder_binaries_mirror https://npmmirror.com/mirrors/electron-builder-binaries/

3. 添加启动入口

安装完electron后,package.json的内容如下,

注意"main": "index.js",(官网文档为main.js)以实际为准,可以改成和官方一致,方便后续编码。

{
  "name": "ele-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "honyee",
  "license": "ISC",
  "dependencies": {
    "electron": "^32.1.2"
  }
}

修改package.json,scripts中添加"start": "electron .",用于启动项目。

其中.表示当前目录中寻找入口点,就是上述的index.js

也可以指定完整的入口位置,例如:"start": "electron ./index.js

修改完后如下

{
  "name": "ele-app",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "honyee",
  "license": "ISC",
  "dependencies": {
    "electron": "^32.1.2"
  }
}

创建main.js文件,填充点内容

console.log('Hello from Electron');

目前结果如下

image.png

4. 启动项目

npm run start

image.png

创建窗体

创建您的第一个应用程序 | Electron (electronjs.org)

在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自远程 URL。

1. 创建一个 index.html 文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer!</h1>
    <p>👋</p>
  </body>
</html>

2. 将 index.js 中的内容替换成下列代码

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

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
})

app.on('window-all-closed', () => {
  // 终止app
   app.quit();
})

3. 再次运行

image.png

打开开发者工具

开发中需要调试,免不了要要使用开发者工具

main.js中添加代码

win.webContents.openDevTools();

启动时会出现如下错误,是因为nodejs新版本不支持自动填充,不影响使用,忽略即可

[23020:1010/094855.972:ERROR:CONSOLE(1)] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
[23020:1010/094855.972:ERROR:CONSOLE(1)] "Request Autofill.setAddresses failed. {"code":-32601,"message":"'Autofill.setAddresses' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)

给页面添加个按钮

创建一个index.js,并在index.html中引入

<script src="index.js"></script>

image.png

方式1 (推荐)

通过addEventListener添加click事件

<button id="myBtn">click me</button>

给按钮加个点击监听

window.onload = function () {
  document.getElementById("myBtn").addEventListener("click",
    () => {
      console.log('just do it');
    });
}

方式2(不推荐)

直接在标签上写onclick事件

<button onclick="clickMe()">click me</button>
function clickMe() {
     console.log('just do it');
}

在点击时会出现错误

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.

因为index.html中添加了安全策略,防止跨站脚本攻击等

  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>
  <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>

事件介绍

ready 应用准备完成事件,下面两种方式都可以

// 方式1
app.on('ready', ()=>{
 
});

// 方式2
app.whenReady().then(() => {
  
})

activate 应用被激活

触发场景:

  1. 当应用从没有窗口打开的状态变为有窗口打开时,比如用户点击应用图标或通过其他方式激活应用。
  2. 在 macOS 上,当用户点击 dock 中的应用图标且应用已经在运行但没有可见窗口时,也会触发这个事件。
app.on('activate', () => {

})

ready-to-show 准备展示时

win.once('ready-to-show', () => {

});

close 窗口关闭事件

win.on('closed', () => {

})

window-all-closed 当全部窗口关闭时退出。

app.on('window-all-closed', () => {

})

上下文隔离

官方推荐上下文隔离,通过预加载preload暴露api,渲染进程通过api执行electron的方法。

从 Electron 20 开始,预加载脚本默认沙盒化 ,不再拥有完整 Node.js 环境的访问权。

为简化代码,下面都是通过关闭上下文隔离的方式示例代码

预加载(推荐)

  1. 创建preload.js提供暴露的方法
const {contextBridge} = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  print: (content) => console.log('content', content),
})
  1. main.js中指定preload
const {
  app, ipcMain,
  BrowserWindow
} = require('electron')
const {join} = require("node:path");

let win;
const createWindow = () => {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: join(__dirname, 'preload.js')
    }
  })

  win.loadFile('index.html');

  win.webContents.openDevTools();
}

app.whenReady().then(createWindow);

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  app.quit();
})
  1. index.js中调用暴露的API
window.myAPI.print('print test')

关闭上下文隔离(不推荐)

BrowserWindow配置项说明:

  • contextIsolation:用于控制渲染进程的上下文隔离,当设置为 false 时,渲染进程可以直接访问主进程的全局对象,例如 require('electron').remote
  • nodeIntegration:用于控制渲染进程中是否启用 Node.js 集成,当设置为true时才可以使用如require此类方法,且contextIsolation必须为false
  • enableRemoteModule:用于控制是否启用remote模块。

进程间通信

进程间通信 | Electron (electronjs.org)

在electron中,把main.js称为主进程,每个打开的BrowserWindow称为渲染进程。

electron 进程间通信的方式主要有两种,ipc和remote

ipc

ipc分别有ipcMainipcRendereripcMain用于主进程,ipcRenderer用于渲染进程。

  • 模式 1:渲染器进程到主进程(单向),即没有返回值,使用ipcRenderer.send发送消息,ipcMain.on接收消息
  • 模式 2:渲染器进程到主进程(双向),即有返回值,使用ipcRenderer.invokeipcMain.handle
  • 模式 3:主进程到渲染器进程,使用WebContents.send发送消息,ipcRenderer.on接收消息
  • 模式 4:渲染器进程到渲染器进程,没有直接的方法

模式1 渲染器进程到主进程(单向)

main.js完整代码

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

let win;
const createWindow = () => {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      contextIsolation: false,
      nodeIntegration: true
    }
  })

  win.loadFile('index.html');
   
  // 打开控制台,查看打印内容
  win.webContents.openDevTools();

  // 监听渲染进程发送的消息
  ipcMain.on('async-message', (event, arg) => {
    console.log('args: ', arg);
    // 发送消息到主进程
    event.sender.send('async-reply', 'Main has received');
  });
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  // 终止app
   app.quit();
})

index.js完整代码

const {ipcRenderer} = require('electron')

window.onload = function () {

  document.getElementById("myBtn").addEventListener("click",
    () => {
      // 使用 ipcRenderer.send 向主进程发送消息。
      ipcRenderer.send('async-message', 'Child call Main');
    });

  // 监听主进程返回的消息
  ipcRenderer.on('async-reply', function (event, arg) {
    console.log('Child receive Main Message:',arg);
  });
}

模式 2:渲染器进程到主进程(双向)

main.js完整代码

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


let win;
const createWindow = () => {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      contextIsolation: false, // 控制渲染进程的上下文隔离,false表示能直接访问主进程的内容
      nodeIntegration: true, // 控制渲染进程中是否启用 Node.js 集成
    }
  })

  win.loadFile('index.html');

  win.webContents.openDevTools();

  // 监听渲染进程发送的消息
  ipcMain.handle('invoke-message', (event, arg1, arg2) => {
    console.log('args: ', arg1, arg2);
    return 'main result'
  });
}

app.whenReady().then(createWindow);

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  app.quit();
})

index.js完整代码

const {ipcRenderer} = require('electron')

window.onload = function () {
  document.getElementById("myBtn").addEventListener("click",
    () => {
      ipcRenderer.invoke('invoke-message', 'a', 'b')
        .then(res => console.log('res', res))
    });

}

模式 3:主进程到渲染器进程

main.js完整代码

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


let win;
const createWindow = () => {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      contextIsolation: false, // 控制渲染进程的上下文隔离,false表示能直接访问主进程的内容
      nodeIntegration: true, // 控制渲染进程中是否启用 Node.js 集成
    }
  })

  win.loadFile('index.html');

  win.webContents.openDevTools();

  // 发送消息
  setTimeout(()=>{
    win.webContents.send('notify-message', 'a', 'b');
  },1000)

}

app.whenReady().then(createWindow);

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  app.quit();
})

index.js完整代码

const {ipcRenderer} = require('electron')

window.onload = function () {
  ipcRenderer.on('notify-message', (event, arg1, arg2) => {
    console.log('args', arg1, arg2);
  })
}

模式 4:渲染器进程到渲染器进程

没有直接的方法可以使用 ipcMainipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。 为此,您有两种选择:

  1. 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
  2. 从主进程将一个 MessagePort 传递到两个渲染器。 这将允许在初始设置后渲染器之间直接进行通信。

remote

使用remote可以直接调用主进程对象的方法,已不推荐使用

从 Electron 10 开始,remote模块默认被禁用以提高安全性。从Electron 14以后需要单独安装引入

  1. 仅需启用即可的使用:

main.js

win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      contextIsolation: false,
      enableRemoteModule: true
    }
  })

index.js

const {remote} = require('electron')

const remote = require('electron').remote

const {app, BrowserWindow} = require('electron').remote
  1. 单独安装引入后的使用:

安装依赖

npm install --save @electron/remote

main.js中启用

const remote = require('@electron/remote/main')
remote.initialize()
remote.enable(win.webContents);

index.js中调用

const remote = require("@electron/remote")

打包应用

打包您的应用程序 | Electron (electronjs.org)

官方文档使用 Electron Forge打包

npm install --save-dev @electron-forge/cli
npx electron-forge import

创建一个可分发版本,注意,package.json中的author和description不能为空(串),打包完后输出到项目底下的out目录中,近300MB,真就打包了一个浏览器进去

npm run make

image.png

发布和更新

发布和更新 | Electron (electronjs.org)

将发布应用到 GitHub 版本中心,并将自动更新功能整合到应用代码中。

使用vite构建

上面的流程是js + electron,现在用 ts + electron + vite + vue3 重新搭建一次

什么是vite

vite官网

简单讲,vite和webpack一样是一个前端开发与构建工具,方便开发和打包。然后直接开整……

初始化项目

为了方便,直接用electron-vite搭建,手动配置vite + electron 太麻烦……

npm create electron-vite@latest
  1. 填入项目名称
  2. 选择Vue
  3. cd到刚创建的目录里npm install

关于type

package.json中的type属性

  • module: Node.js 将以 ES 模块(ECMAScript modules)的方式加载模块,在代码中使用importexport语句来导入和导出模块。
import { someFunction } from './someModule.js';

export const anotherFunction = () => {
  // 函数实现
};
  • commonjs:Node.js 将以 CommonJS 的方式加载模块,使用 requiremodule.exports 来导入和导出模块。
const someModule = require('./someModule');

module.exports = {
  anotherFunction: () => {
    // 函数实现
  }
};

如果type配置与实际使用不符合,运行项目会出现如下错误:

image.png

了解目录和文件配置

image.png

  • electron 存放electron的代码
  • public 存放公共资源,如图片这些
  • release 默认打包后的输出目录,可在electron-builder.json5中修改
  • src 存放vue代码
  • electron-builder.json5 打包配置
  • index.html 首页入口
  • package.json 依赖和项目配置
  • tsconfig.json 配置typescript
  • tsconfig.node.json 配置TypeScript 在 Node.js 环境中的编译和开发
  • vite.config.ts 配置vite

打包输出结果 image.png

package.json 说明

通过electron-vite安装的electron版本可能不是最新,例如这里是^30.0.1

本来想使用npm update electron更新版本,发现不起作用,原因未知,所以可以考虑手动改版本号……

scripts

  • dev : 启动electron
  • build:打包,会输出到release,生成exe安装包,建议关闭部分代码检查,否则可能会出现xxx is declared but its value is never read.之类的错误,关闭方法在下面tsconfig.json说明
  • preview:预览,可以在浏览器中访问,而不是通过electron
{
  "name": "vit-ele-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build && electron-builder",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.4.21"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "typescript": "^5.2.2",
    "vite": "^5.1.6",
    "vue-tsc": "^2.0.26",
    "electron": "^30.0.1",
    "electron-builder": "^24.13.3",
    "vite-plugin-electron": "^0.28.6",
    "vite-plugin-electron-renderer": "^0.14.5"
  },
  "main": "dist-electron/main.js"
}

tsconfig.json说明

strict:严格检查模式

noUnusedLocals:检查未使用的变量,建议改为false

noUnusedParameters:检查未使用的函数参数,建议改为false

noFallthroughCasesInSwitch:检查switch的case是否使用break、return、throw来明确终止

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "electron"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

引入UI

以下几款UI,这里以 Element+ 为例

安装依赖

npm install element-plus --save

使用依赖 /src/main.ts

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

createApp(App).use(ElementPlus).mount('#app') // 省略其他部分

使用组件/src/App.vue中随便写个组件试试有没有生效

<el-button type="primary">Primary</el-button>

image.png

发起http请求

引入axios

npm i axios -S

electron默认开启站点安全机制,只允许主进程发起http请求,渲染进程发起的http请求皆视为跨域CORS。

image.png

image.png 解决方式:

  • 方式1:通过preload间接调用

/electron/main.ts中引入axios,并通过ipcMain双向通信暴露接口

import axios from 'axios'

//...忽略其他的代码

ipcMain.handle('http-get', async (_event, url, params) => {
  const response = await axios.get(url, params)
  console.log('http-get', url, params,response.data)
  return response.data
});
ipcMain.handle('http-post', async (_event, url, params) => {
  const response = await axios.post(url, params)
  console.log('http-post', url, params,response.data)
  return response.data
});

/electron/preload.ts中暴露接口

contextBridge.exposeInMainWorld('http', {
  get<T>(url: string, params?: object): Promise<T> {
    return ipcRenderer.invoke('http-get', url, params);
  },
  post<T>(url: string, params?: object): Promise<T> {
    return ipcRenderer.invoke('http-post', url, params);
  }
})

在渲染进程中调用接口,例如 HelloWorld.vue

window.http.get('http://localhost:1222/api/test/get')
  .then(res => {
    console.log(res)
  })
  • 方式2:禁用web安全性webSecurity: false

好处是可以直接在渲染进程请求http,且开发者工具的NetWork可以直接查看请求

坏处是安全性降低

/electron/main.ts

win = new BrowserWindow({
    icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'),
    webPreferences: {
      preload: path.join(__dirname, 'preload.mjs'),
      webSecurity: false // 禁用web安全性
    },
})

/src/components/HelloWorld.vue

import axios from "axios";

axios.get('http://localhost:1222/api/test/get')
  .then(res => {
    console.log(res)
})

image.png

使用Sass

Sass 是一个 CSS 预处理器。

Sass 是 CSS 扩展语言,可以帮助我们减少 CSS 重复的代码,节省开发时间。

Sass 完全兼容所有版本的 CSS。

  1. 安装Sass,网上默认推荐-g全局安装,但是这里使用局部安装
npm i sass -D
  1. /src/main.css改为/src/main/scss注意scss而不是sass

  2. 使用Sass,比如使用变量--el-color-success,该变量是element-plus已经定义的颜色

@import "../../node_modules/element-plus/dist/index";

.use-color {
  color: var(--el-color-success);
}
  1. 启动项目时,会提示API已过期,可以添加如下配置

vite.config.ts

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        api: "modern-compiler" // or 'modern'
      }
    }
  },
});

打包

由于引入了element-plus,在打包时,会提示存在index-xxxxx-.js体积过大,解决方式如下

方式1: 修改全局的包大小告警阈值

export default defineConfig({
  build: {
    chunkSizeWarningLimit: 800, // 构建时的 chunk 大小警告阈值,默认500kb
  }
});

方式2: 自定义分包规则

import {ManualChunkMeta} from "rollup";
export default defineConfig({
  build: {
    chunkSizeWarningLimit: 800, // 构建时的 chunk 大小警告阈值
    rollupOptions: {
      output: {
        manualChunks(id: string, meta: ManualChunkMeta) {
          // 未配置时默认直接返回index,所以打包后文件名是index-xxxxx-.js
          // return 'index'
          
          // 根据id分包
          if (id.includes('node_modules')) {
            const arr = id.toString().split('node_modules')[1].split('/');
            // 单独将大包分离出来
            if (arr[1].includes('element-plus')) {
              return '__vendor_' + arr[1];
            }
          }
          return 'index'
        },
      }
    }
  },
});

解决一些环境、编译问题

控制台的中文乱码

如下图,中文乱码

image.png

解决方式: 修改注册列表

  1. Windows + R,输入regedit打开注册列表
  2. 来到计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor
  3. 添加一个字符串值,名称为:autorun 数据为:chcp 65001

image.png

image.png

implicitly has an 'any' type

error TS7016: Could not find a declaration file for module .... implicitly has an 'any' type.

因为是typescript,存在未指定类型代码,可以强制关闭该校验

tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": false, // 强制关闭该校验
    }
}

Electron Security Warning

这个警告,是上面提到过的安全策略问题

Electron Security Warning (Insecure Content-Security-Policy) This renderer process has either no Content Security
  Policy set or a policy with "unsafe-eval" enabled. This exposes users of
  this app to unnecessary security risks.

image.png

/index.html中加入如下内容即可

<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline';">

完整版

<meta http-equiv="Content-Security-Policy" content="default-src *; connect-src * ws://* wss://*; style-src * 'unsafe-inline' 'unsafe-eval'; media-src * ; img-src * data:; font-src * ; script-src * 'unsafe-inline' 'unsafe-eval';" />

打包时找不到preload中暴露的API

通过electron-vite初始化项目时,已经自动初始化了如下代码

/electron/preload.ts

// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld("ipcRenderer", {
  on(...args: Parameters<typeof ipcRenderer.on>) {
    const [channel, listener] = args;
    return ipcRenderer.on(channel, (event, ...args) =>
      listener(event, ...args),
    );
  },
  off(...args: Parameters<typeof ipcRenderer.off>) {
    const [channel, ...omit] = args;
    return ipcRenderer.off(channel, ...omit);
  },
  send(...args: Parameters<typeof ipcRenderer.send>) {
    const [channel, ...omit] = args;
    return ipcRenderer.send(channel, ...omit);
  },
  invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
    const [channel, ...omit] = args;
    return ipcRenderer.invoke(channel, ...omit);
  },

  // You can expose other APTs you need here.
  // ...
});

/electron/electron-env.d.ts

// Used in Renderer process, expose in `preload.ts`
interface Window {
  ipcRenderer: import("electron").IpcRenderer;
}

/electron/preload.ts中的接口的实现,如果在打包build时没有在/electron/electron-env.d.ts中声明暴露的API,会出现错误:

error TS2339: Property 'ipcRenderer' does not exist on type 'Window & typeof globalThis'.

如果要新增一个自定义API

  1. /electron/preload.ts编写接口
contextBridge.exposeInMainWorld("myApi", {
  print() {
    console.log('myApi print')
  },
});
  1. 新建一个接口文件/electron/preload-interface.ts·
interface MyApi {
  print();
}
  1. 暴露接口 /electron/electron-env.d.ts
interface Window {
  myApi: import("./preload-interface.ts").MyApi;
}
  1. 在渲染进程中使用
window.myApi.print();

组件被强制居中了

因为style.css#app的样式默认居中了,可以在使用其他组件时同时设置自身text-align

#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

令组件充满窗口

修改style.css

#app {
  width: 100vw; /*占满可视宽度的100%*/
  height: 90vh; /*占满可视高度的90%*/
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

窗口设置

最大化

win.maximize();

无边框

win = new BrowserWindow({
    frame: false,
})

隐藏菜单栏

import { Menu } from "electron";

Menu.setApplicationMenu(null);

使用dialog的问题

官方dialog

我要调用系统的文件操作窗口,例如保存文件,会出现dialog出现后,依旧可以点击其他窗体的问题。

因为dialog.showSaveDialogSync([window, ]options)中的window是可选参数,不传递则不会“硬控”

ipcMain.on('dialog-save', (_event, base64ImageData) => {
  // 提取图像格式
  const extendName = base64ImageData.split(';')[0].split('/')[1];
  // 打开保存对话框获取保存路径
  const filePath = dialog.showSaveDialogSync(win, {
    filters: [
      {name: 'Images', extensions: [extendName]},
    ],
  });
  if (filePath) {
    // 去除 Base64 数据的头部信息
    const base64Data = base64ImageData.split(',')[1];
    // 将 Base64 数据转换为 Buffer 对象并保存为文件
    fs.writeFileSync(filePath, Buffer.from(base64Data, 'base64'));
  }
})

image.png

路径别名

原先使用 src/components/hello/world/

采用路径别名后 @components/hello/world

添加如下配置:

vite.config.ts

import path from 'node:path'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.join(__dirname, 'src'),
      '@lib': path.join(__dirname, 'src/lib'),
      '@components': path.join(__dirname, 'src/components'),
    },
  },
});

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@lib/*": ["src/lib/*"],
      "@components/*": ["src/components/*"],
    },
}

关闭Linting校验

打包时会检查变量是否使用、是否严格模式等

tsconfig.json

{
  "compilerOptions": {
    {
        /* Linting */
        "strict": false,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        "noFallthroughCasesInSwitch": false
    }
}

拼写检查

使用element-plusinput->textarea,会出现红色波浪号,这是因为默认会进行拼写检查,在el-input关闭即可

:spellcheck="false"

image.png

Unable to move the cache: 拒绝访问

打开了不止一个Electron

[7992:1022/085703.670:ERROR:cache_util_win.cc(20)] Unable to move the cache: 拒绝访问。 (0x5)
[7992:1022/085703.670:ERROR:cache_util_win.cc(20)] Unable to move the cache: 拒绝访问。 (0x5)
[7992:1022/085703.670:ERROR:cache_util_win.cc(20)] Unable to move the cache: 拒绝访问。 (0x5)
[7992:1022/085703.673:ERROR:disk_cache.cc(208)] Unable to create cache
[7992:1022/085703.673:ERROR:gpu_disk_cache.cc(711)] Gpu Cache Creation failed: -2
[7992:1022/085703.673:ERROR:disk_cache.cc(208)] Unable to create cache
[7992:1022/085703.673:ERROR:gpu_disk_cache.cc(711)] Gpu Cache Creation failed: -2
[7992:1022/085703.673:ERROR:disk_cache.cc(208)] Unable to create cache
[7992:1022/085703.674:ERROR:gpu_disk_cache.cc(711)] Gpu Cache Creation failed: -2

image.png