Electron使用记录

Electron

参考引用

参考文档:
Electron+Vue3.2+TypeScript+Vite开发桌面端 - 掘金 (juejin.cn)
如何用Electron+vue+vite构建桌面端应用(一) - 掘金 (juejin.cn)
Electron教程(三)如何打包 electron 程序:electron-forge 的使用教程-CSDN博客

主要参考:
小满Vue3(第三十九章 electron桌面程序)_哔哩哔哩_bilibili
Electron开发实践(3)——环境&工程搭建(Vite+Electron+React) - 掘金 (juejin.cn)

创建vue项目

PS S:\VS Code> npm create vite@latest
Need to install the following packages:create-vite@4.4.1
Ok to proceed? (y) y
√ Project name: ... fastvo
√ Package name: ... fastvo(需小写即package.json中的name)
√ Select a framework: » Vue
√ Select a variant: » TypeScript
Scaffolding project in S:\VS Code\wxMiniProject...
Done. Now run:cd fastvonpm installnpm run dev
PS S:\VS Code>npm install
PS S:\VS Code>code .

集成electron

# 安装electron依赖 到开发环境
npm install electron electron-builder -D
npm install electron electron-builder --save-dev
# 注意:有时候更换源也会导致下载失败多试几次
# 还是安装失败时可以用:淘宝镜像+cnpm来安装这两个依赖

安装后在vue 项目 src 中新增 background.ts 文件 作为electorn 主进程文件

  • background.ts

在vue 项目中 新建 plugins 文件夹 新增配置文件

  • vite.electron.dev.ts // 开发环境的配置文件
  • vite.electron.build.ts // 生产环境的配置文件
npm 更新源
# 查询源
npm config get registry
# 更换国内源
npm config set registry https://registry.npmmirror.com
# 恢复官方源
npm config set registry https://registry.npmjs.org
# 删除注册表
npm config delete registry
# 淘宝最新源
npm config set registry https://registry.npmmirror.com
# npm 官方原始镜像网址是:https://registry.npmjs.org/
# 淘宝 NPM 镜像:https://registry.npm.taobao.org
# 阿里云 NPM 镜像:https://npm.aliyun.com
# 腾讯云 NPM 镜像:https://mirrors.cloud.tencent.com/npm/
# 华为云 NPM 镜像:https://mirrors.huaweicloud.com/repository/npm/
# 网易 NPM 镜像:https://mirrors.163.com/npm/
# 中科院大学开源镜像站:http://mirrors.ustc.edu.cn/
# 清华大学开源镜像站:https://mirrors.tuna.tsinghua.edu.cn/
background.ts
// 主进程启动文件
// electorn 
import {app,BrowserWindow} from 'electron'
// const { app, BrowserWindow } = require('electron')
// 禁用沙盒 
app.commandLine.appendSwitch('no-sandbox');// 等待Electron应用就绪后创建BrowserWindow窗口
app.whenReady().then(()=>{const win = new BrowserWindow({height:600,width:800,webPreferences:{nodeIntegration:true,// 启用Node.js集成contextIsolation:false,// 禁用上下文隔离webSecurity:false,//}})if(process.argv[2]){// 打开开发者工具win.webContents.openDevTools()win.loadURL(process.argv[2])}else{win.loadFile('index.html')}
})
vite.electron.dev.ts
// 开发环境配置
import type { Plugin } from 'vite'
import type {AddressInfo} from 'net'
import {spawn} from 'child_process'
import fs from 'node:fs'// 转编函数
const buildBackground = ()=>{// 使用 esbuild 编译ts为jsrequire('esbuild').buildSync({entryPoints: ['src/background.ts'],// 入口文件bundle:true,// 打包所以依赖outfile:'dist/background.js', //输出文件platform:'node',target:'node20',external:['electron'] // 排除依赖})
}// 创建一个配置插件
export const ElectronDevPlugin = ():Plugin => {return {name:'electron-dev',configureServer(server){buildBackground()server?.httpServer?.once('listening',()=>{// 这个地方原本的address是string, 而 address() 函数会返回 AddressInfo,所以可以 as 断言成 AddressInfo类型const addressInfo = server?.httpServer?.address() as AddressInfo// console.log(address) // { address: '::1', family: 'IPv6', port: 5173 }// 1.获取到完整的访问路径用来给 eletron 使用  | 使用 ``来实现拼接const IP = `http://localhost:${addressInfo.port}` // console.log(IP) // http://localhost:5173 // 2. 使用进程传参把 IP地址传入到主进程中// require('electron') 函数的返回是一个路径// electron 无法识别ts文件,所以需要转编成js文件 然后发送到主进程// 进程传参发 把IP发送给 electron  // 第0个参数是 require('electron') 第1个参数是'dist/background.js',第2个是IPlet ElectronProcess = spawn(require('electron'),['dist/background.js',IP])fs.watchFile('src/background.ts',()=>{ElectronProcess.kill()buildBackground()ElectronProcess = spawn(require('electron'),['dist/background.js',IP])})ElectronProcess.stdout.on('data',(data)=>{console.log(data.toString())})})}}
}
vite.electron.build.ts
// 生产环境配置
import type { Plugin } from 'vite'
import fs from 'node:fs'
import * as electronBuild from 'electron-builder'
import path from 'path'
// 转编函数
const buildBackground = ()=>{// 使用 esbuild 编译ts为jsrequire('esbuild').buildSync({entryPoints: ['src/background.ts'],// 入口文件bundle:true,// 打包所以依赖outfile:'dist/background.js', //输出文件platform:'node',target:'node20',external:['electron'] // 排除依赖})
}// 打包需要先等vite 打包完后再直接electron builder 打包
export const ElectronBuildPlugin = ():Plugin => {return {name:'electron-build',closeBundle() {buildBackground()// electron-builder 需要指定入口 const json = JSON.parse(fs.readFileSync('package.json','utf-8'))json.main = 'background.js'fs.writeFileSync('dist/package.json',JSON.stringify(json,null,4))fs.mkdirSync('dist/node_modules') // 为了预防electron下载垃圾文件  - ,{recursive:true}electronBuild.build({config:{directories:{output:path.resolve(process.cwd(),'release'),//输出到releaseapp:path.resolve(process.cwd(),'dist'),// 基于dist目录打包},asar:true,// 打包成压缩包appId:'com.suredata.app',productName:'fastvo',nsis:{oneClick:false,//取消一键安装allowToChangeInstallationDirectory:true,// 允许用户自定义安装},}})}}
}
tsconfig.node.json
// tsconfig.node.json
//添加到 tsconfig 中使项目可以检测到该配置文件
{"compilerOptions": {"composite": true,"skipLibCheck": true,"module": "ESNext","moduleResolution": "bundler","allowSyntheticDefaultImports": true},"include": ["vite.config.ts","plugins/**/*.ts"]
}
vite.config.ts
// vite.config.ts
// 注册到项目Plugin中
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入,此时就项目就可以检测到 ElectronDevPlugin
import { ElectronDevPlugin } from './plugins/vite.electron.dev'
import { ElectronBuildPlugin } from './plugins/vite.electron.build'
// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),// 注册到pluginElectronDevPlugin(),ElectronBuildPlugin()],base:'./',//默认绝对路径,需要修改为相对路径,否则会白屏resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}
})
报错处理
1.在ts转编js时 报错:error when starting dev server:

Error: Dynamic require of “file:///S:/electron/fastvo/node_modules/esbuild/lib/main.js” is not supported

// package.json
{"name": "fastvo","private": true,"version": "0.0.0","type": "module", //删除该属性即可恢复"scripts": {"dev": "vite","build": "vue-tsc && vite build","preview": "vite preview"},"dependencies": {"vue": "^3.3.4"},"devDependencies": {"@vitejs/plugin-vue": "^4.2.3","electron": "^28.0.0","electron-builder": "^24.9.1","typescript": "^5.0.2","vite": "^4.4.5","vue-tsc": "^1.8.5"}
}
// 原因:
// 在 node 支持 ES 模块后,要求 ES 模块采用 .mjs 后缀文件名。只要遇到 .mjs 文件,就认为它是 ES 模块。如果不想修改文件后缀,就可以在 package.json文件中,指定 type 字段为 module。
// 这样所有 .js 后缀的文件,node 都会用 ES 模块解释。
//不论package.json中的type字段为何值,.mjs的文件都按照es模块来处理,.cjs的文件都按照commonjs模块来处理
// type字段省略则默认采用commonjs规范
// 不太懂,不过我们只需要把 ts转成js即可
2.启动白屏或无法加载,GPU进程无法渲染;
// background.ts
import {app,BrowserWindow} from 'electron'
// const { app, BrowserWindow } = require('electron')
// 禁用沙盒 (新增解决)
app.commandLine.appendSwitch('no-sandbox');
// 创建一个渲染进程(子进程)
const createWindow =()=>{const win = new BrowserWindow({height:600,width:800,webPreferences:{nodeIntegration:true,// 启用Node.js集成contextIsolation:false,// 禁用上下文隔离webSecurity:false,//}})if(process.argv[2]){// 打开开发者工具win.webContents.openDevTools()win.loadURL(process.argv[2])}else{win.loadFile('index.html')}
}// 等待Electron应用就绪后创建BrowserWindow窗口
app.whenReady().then(createWindow)// 程序激活时,触发流程
app.on('activate', () => {// On OS X it's common to re-create a window in the app when the - 在OS X上,通常会在应用程序中重新创建一个窗口// dock icon is clicked and there are no other windows open. - 单击dock图标,没有其他窗口打开。// 当检测不到窗口时会重新创建if (BrowserWindow.getAllWindows().length === 0) {createWindow();}});// 窗口关闭时
// Quit when all windows are closed, except on macOS. There, it's common -当所有窗口都关闭时退出,除了macOS。在那里,这很常见
// for applications and their menu bar to stay active until the user quits -让应用程序及其菜单栏保持活动状态,直到用户退出
// explicitly with Cmd + Q.- 显式地使用Cmd + Q。
app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}});
打包

npm run dev //测试

npm run build // 打包

  1. 如果出现“Cannot create symbolic link”的错误,可以以管理员身份运行power shell或vscode重新进行打包
  2. 如此出现下载失败就多试几次;看下git 是否可以进入;
1.打包后无法显示页面

npm run make 后,可以看到index.html的存在,但无法显示vue路由出口文件。
解决:

// 路由器实例 由 createWebHistory -修改为-> createWebHashHistory
const router = createRouter({history: createWebHashHistory(),routes
})
// 原因: 推测为 createWebHistory 不支持 HTML5 History API导致的。

问题太多,使用下面官方推荐的方式进行测试。

Electron Forge

官方脚手架

打包时没法选择 高级选项(electron-builder)

# 初始化一个新的electron 项目 my-app
npm init electron-app@latest fastvo
# 添加模板
npm init electron-app@latest quickTrim -- --template=vite-typescript
# 官方模板
# webpack、webpack-typescript、vite、vite-typescript
# 启动
cd fastvo
npm start
# 编译 成exe按照文件
npm run make
# 发布 app 把项目发布到指定仓库
npm run publish
# 安装 electron 官方的构建工具居然不会导入electron(会导入,但是导入失败并不提示,所以需要再次手动导入)
npm install --save-dev electron

forge.config.js

​ 配置文件可以自定义配置,参考 配置文档,可选项:Options | @electron/packager

问题,白屏报错

app.commandLine.appendSwitch(‘no-sandbox’);
禁用 Chromium 沙箱。 强制渲染器进程和Chromium助手进程以非沙盒化运行。 应该只在测试时使用。

项目目录结构

Mode LastWriteTime Length Name


d----- 2024/1/17 14:23 .vite
d----- 2024/1/17 14:21 node_modules
d----- 2024/1/17 14:04 src
-a---- 2024/1/17 14:04 227 App.vue # 新增 vue页面展示
-a---- 2023/12/22 16:36 166 index.css # index.html样式
-a---- 2024/1/17 14:08 3059 main.ts # 主进程文件
-a---- 2023/12/22 16:36 158 preload.ts # 预载文件
-a---- 2024/1/17 14:09 1125 renderer.ts # 渲染进程文件(即页面渲染)
-a---- 2023/12/22 16:36 348 types.d.ts # ts文件
-a---- 2023/12/22 16:36 352 .eslintrc.json
-a---- 2023/12/22 16:36 1215 .gitignore
-a---- 2023/12/22 16:36 1240 forge.config.ts # forge配置文件
-a---- 2024/1/17 14:04 215 index.html # index.html文件,唯一
-a---- 2024/1/17 14:02 319676 package-lock.json # 版本锁定文件
-a---- 2024/1/17 13:59 1197 package.json # 依赖管理文件
-a---- 2023/12/22 16:36 333 tsconfig.json # ts配置文件
-a---- 2024/1/17 14:25 357 vite.main.config.ts # vite配置文件用于主进程
-a---- 2024/1/17 14:12 119 vite.preload.config.ts # vite配置文件用于预加载
-a---- 2024/1/17 14:26 192 vite.renderer.config.ts # vite配置文件用于渲染进程

main.ts:主进程文件,eletron 程序的入口,运行再一个Node.js环境中,负责控制您应用的生命周期,显示原生界面,执行特殊操作并管理渲染器进程(稍后详细介绍)。主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。当一个 BrowserWindow 实例被销毁时,与其相应的渲染器进程也会被终止。

preload.ts:预加载脚本
包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API 来增强渲染器,以便你的网页内容使用。
语境隔离(Context Isolation)意味着预加载脚本与渲染器的主要运行环境是隔离开来的,以避免泄漏任何具特权的 API 到您的网页内容代码中。

renderer.ts 渲染进程文件(即页面渲染),对应着一个管理应用程序窗口进行
每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

  • 以一个 HTML 文件作为渲染器进程的入口点。
  • 使用层叠样式表 (Cascading Style Sheets, CSS) 对 UI 添加样式。
  • 通过 <script> 元素可添加可执行的 JavaScript 代码。

main.ts 和 renderer.ts 是独立的两个程序,main.ts 控制着 renderer.ts
preload.ts作为两者直接中间层可以对双方交互进行一个增强;

集成VUE3

Vue 3 - 电子锻造 (electronforge.io)

需要使用 electron 模板创建程序

npm init electron-app@latest my-vue-app – --template=vite
npm init electron-app@latest my-vue-app – --template=vite-typescript

# 添加依赖到运行环境
npm install vue
# 添加依赖到开发环境
npm install --save-dev @vitejs/plugin-vue
1.index.html 修改html页面,增加挂载点app
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Hello World!</title></head><body><div id="app"></div><script type="module" src="/src/renderer.ts"></script></body>
</html>
2. src/App.vue 新增vue模板页面,即单文件入口。
<template><h1>💖 Hello World!</h1><p>Welcome to your Electron application.</p>
</template><script setup>
console.log('👋 This message is being logged by "App.vue", included via Vite');
</script>
3.src/renderer.ts 挂载APP.vue到index.html中
import './index.css';console.log('👋 This message is being logged by "renderer.ts", included via Vite');import { createApp } from 'vue';
import App from './App.vue';// 启用 vue,并挂载,到 index.html中
createApp(App).mount('#app');
4.vite.renderer.config.ts 修改配置文件导入vue插件到环境中
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config
// 渲染配置
export default defineConfig({plugins:[vue()]
});

添加其他组件

vue-router
pinia
naive-ui
axios

// 安装上面依赖后新增配置
// renderer.ts
import './index.css';
console.log('👋 This message is being logged by "renderer.ts", included via Vite');
import { createApp } from 'vue';
import App from './App.vue';
import {createPinia} from 'pinia'
import router from './router/index';// 启用 vue,并挂载,到 index.html中
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app');
// router/index.ts
// 路由组件配置
import {createRouter,createWebHistory,RouteRecordRaw} from 'vue-router'
// 1.单独使用 loadingBar 进度条
import { createDiscreteApi} from 'naive-ui'
const {loadingBar} = createDiscreteApi(['loadingBar'])// 动态路由 引入文件
// 路由信息
const routes:Array<RouteRecordRaw> = [{path: '/',name: 'Sign',component: () => import('../view/sign.vue'),meta: {namespace: 'sign'}},// {//     // 初始化加载index首页组件//     path:'/',//     component: signVue,//     redirect:'/',//     meta:{//         namespace:'sign',//     }// }// {// 测试组件// path:'/home',// namespace:'Home',// component: () => import('@/components/HelloWorld.vue'),// children:[],子组件;// meta:{requiresAuth: false},路由元信息,可以控制组件跳转权限;// },
]
// 路由器实例
const router = createRouter({history: createWebHistory(),routes
})
// export const Sleep = (ms:number)=> {
//     return new Promise(resolve=>setTimeout(resolve, ms))
//   }
// 设置前置路由守卫
router.beforeEach((to,from,next)=>{// 路由中导入-开始loadingBar.start()next()
})
// 设置后置路由守卫
router.afterEach((to,from,next)=>{// 路由中导入-结束loadingBar.finish()
})
// 对外暴露
export default router 
// pinia使用示例
// store/theme.ts
import {darkTheme,lightTheme} from 'naive-ui'
import { defineStore } from 'pinia'
import { ref} from 'vue'
import type {GlobalTheme} from 'naive-ui'// themeStore of pinia
export const useThemeStore = defineStore('themeStore',()=>{// theme ref varconst theme = ref<GlobalTheme>(lightTheme)// actions: update Theme function setTheme(themes:boolean){if(themes){// true lightThemetheme.value  = lightTheme}else{// false darkThemetheme.value  = darkTheme}}return {theme,setTheme}
})
// vue使用
// App.vue
<template><n-config-provider :theme="useTheme.theme" :locale="zhCN" :date-locale="dateZhCN"><!-- 组件渲染出口 --><router-view></router-view><!-- <h1>💖 Hello World!</h1><p>Welcome to your Electron application.</p> --><!-- <n-button  @click="emit('updateTheme')" strong secondary type="success">{{themeFlag?"光明":"黑暗"}}</n-button> -->
</n-config-provider>
</template><script setup lang="ts">
import {zhCN,dateZhCN,NConfigProvider,NButton} from 'naive-ui'
// theme
import {useThemeStore} from './store/theme'
import {ref} from 'vue'console.log('👋 This message is being logged by "App.vue", included via Vite');
const useTheme = useThemeStore()
//与父组件通信修改主题
const emit = defineEmits(["updateTheme"])
// 接受父组件数据信息
defineProps({// 接受父组件传来的参数themeFlag: Boolean,// 写法二,可以设置默认值themeFlags:{type:Boolean,default:''}
})
</script>

index.html

<!-- 清理样式 清理默认样式-->
/* :root 表示文档根元素,优先级比较高,而且再这里边定义的变量也可以作为全局变量 */
:root {font-family: Inter, Avenir, Helvetica, Arial, sans-serif;font-size: 16px;line-height: 24px;font-weight: 400;/* 颜色主题: 阳光 黑暗*/color-scheme: light dark; /* 默认黑色背景色和白色文字 *//* color: rgba(255, 255, 255, 0.87);background-color: #242424; */color: #213547;background-color: #ffffff;font-synthesis: none;text-rendering: optimizeLegibility;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;-webkit-text-size-adjust: 100%;
}
body {margin: 0;padding: 0;display: flex;width: 100%;height: 100%;/* min-width: 320px; *//* min-height: 100vh; */
}
/* body内css样式,整个页面的样式 */
#app {width: 100%;height: 100%;margin: 0;padding: 0;min-height: 100vh;text-align: center;
}

http请求封装

参考:net | Electron (electronjs.org)

import { net } from 'electron';
// --------------------网络请求封装
/*** POST请求数据接口* @param api 接口地址,如:'http://xxxxxxx:7000/user/login'* @param data 请求数据,JOSN 格式,或 object 或 string多需要异步,同步需要需要测试*/
export function sendPOST(api:string,data:JSON|object|string){// const request:RequestInit = {//  method:'POST',//  body:JSON.stringify(data),//  headers:{'Content-Type':'application/json'}// }// net.fetch(api,request)// .then(response => {//   console.log('POST 请求成功: ',response);//   return response.json();// }).catch(err => {//   console.log('POST 请求异常: ',err);//   return null;// })sendPOST_ASYNC(api,data).then(response => {console.log('POST 请求成功: ',response);return response;}).catch(err => {console.log('POST 请求异常: ',err);return null;})
}
/*** GET请求数据接口* @param api 接口地址,如:'http://xxxxx:7000/ping'*/
export function sendGET(api:string){// net.fetch(api)sendGET_ASYNC(api).then(response => {console.log('GET 请求成功: ',response);return response;}).catch(err => {console.log('GET 请求异常: ',err);return null;})
}
/*** POST请求数据接口 - 异步接口* @param api 接口地址,如:'http://xxxxxxxx:7000/user/login'* @param data 请求数据,JOSN 格式,或 object 或 string*/
async function sendPOST_ASYNC(api:string,data:JSON|object|string){const request:RequestInit = {method:'POST',body:JSON.stringify(data),headers:{'Content-Type':'application/json'}}const response = await net.fetch(api,request)if (response.ok) {const body = await response.json()return body}
}
/*** GET请求数据接口 - 异步接口* @param api 接口地址,如:'http://xxxxxxxx:7000/ping'*/
async function sendGET_ASYNC(api:string){const response = await net.fetch(api)if (response.ok) {const body = await response.json()return body}
}

IPC通信

参考:
Electron入门实践(3):进程间通讯 - 掘金 (juejin.cn)

electron+vue3全家桶+vite项目搭建【13.1】ipc通信的使用,主进程与渲染进程之间的交互_electron vite ipc-CSDN博客

IPC通信主要就是依赖preload预载脚本来实现的,一切的操作均和该脚本相关。

IPC通信[主/渲染]进程对应

方向主进程【ipcMain】渲染进程【ipcRenderer】
渲染=>主 【同步/异步】ipcMain.on()ipcRender.send() / ipcRender.sendSync() 【同步取值】
渲染=>主 【异步】ipcMain.handle()ipcRender.invoke()
主=>渲染 【异步】BrowserWindow【实例】.webContents.send()ipcRender.on()

涉及到的请求通路都要进行异常处理,否则页面无法识别到返回数据

preload:如何使用预载脚本

参考:contextBridge | Electron (electronjs.org)

// main.ts
import {ipcMain} from 'electron';
// 在初始化Electron时完成。
// 可以作为一个方便的替代检查app. isready()和订阅ready事件,
// 如果应用程序还没有准备好。
app.whenReady().then(()=>{// ipcMain 注册 openFile 通道 触发 回调handleFileOpen函数ipcMain.handle('api:ping', search_server())
})
//这个方法将在Electron完成时被调用
//初始化,并准备创建浏览器窗口。
//某些api只能在此事件发生后使用。
app.on('ready', createWindow);
// preload.ts
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
// 预加载脚本
import { contextBridge,ipcRenderer } from "electron";// 将函数暴露给  渲染页面使用 通道 electronAPI
contextBridge.exposeInMainWorld('electronAPI',{// 暴露一个单行的函数ping ,该函数会执行 主进程中的函数ping: () => ipcRenderer.invoke('api:ping')
})
// 通道 info
contextBridge.exposeInMainWorld('info',{// 暴露一个单行的函数openFile ,该函数会执行 主进程中的函数username: () => 'xxxxxxxx',pwd: () => 'xxxxxxxx'
})
<-- sign.vue -->
<script setup lang="ts">
// 直接使用
userRef.value.username = window.info.pwd()
userRef.value.pwd = window.info.username()
window.electronAPI.ping()
</script>
渲染进程->主进程(单向通信)
// main.ts
app.whenReady().then(()=>{// ipcMain 注册 openFile 通道 触发 回调handleFileOpen函数ipcMain.handle('api:ping', search_server)// 监听 api:login 通道,触发登录函数ipcMain.on('api:login',(event,username,pwd)=>{console.log('收到消息:',username,pwd)  //  收到消息: xxxxxx xxxxxxxsendPOST('http://xxxxxxxx/user/login',{username:username,pwd:pwd});})
})
// preload.ts
contextBridge.exposeInMainWorld('electronAPI',{// 暴露一个单行的函数openFile ,该函数会执行 主进程中的函数username: () => 'xxxxxxx',pwd: () => 'xxxxxx',ping: () => ipcRenderer.invoke('api:ping'),// 对渲染页面暴露登录函数 api:loginlogin: (username:string,pwd:string) => ipcRenderer.send('api:login',username,pwd)
})
// interface.d.ts 
export interface IElectronAPI {ping: () => Promise<void>,// 新增声明login: (username:string,pwd:string) => Promise<void>,
}declare global {
interface Window {electronAPI: IElectronAPI
}
}
// sing.vue
<-- sign.vue -->
<script setup lang="ts">
<-- 直接使用 -->
userRef.value.username = window.info.pwd()
userRef.value.pwd = window.info.username()
window.electronAPI.ping()
<-- vue 页面调用 -->
window.electronAPI.login(userRef.value.account,userRef.value.password)
</script>
渲染进程<=>主进程(双向通信)

1.与 单向通信不同,具有返回值,单向与双向的差别主要是 ipcMain.on() & ipcMain.handle() 和 预加载脚本中调用的 ipcRenderer.send() & ipcRenderer.invoke()的差别;

2.该返回值需要执行异步函数,否则返回值无法回到渲染页面;

// --------------- main.ts
app.whenReady().then(()=>{// ipcMain 注册 openFile 通道 触发 回调handleFileOpen函数ipcMain.handle('api:ping', search_server)// 监听 api:login 通道,触发登录函数 ipcMain.handle('api:login',async (event,username,pwd)=> {return await sendPOST_ASYNC('http://xxxxxxxxxxxxx/user/login',{username:username,pwd:pwd});})
})
/*** 查找服务器函数-异步* @returns */
const search_server = async ()=> {return await sendGET_ASYNC('http://xxxxxxxxx/ping');
}
// --------------- preload.ts 同单向不同需要异步声明
contextBridge.exposeInMainWorld('electronAPI',{// 暴露一个单行的函数openFile ,该函数会执行 主进程中的函数username: () => 'xxxxxxx',pwd: () => 'xxxxxxx',ping: async () => await ipcRenderer.invoke('api:ping'),// 对渲染页面暴露登录函数 api:loginlogin: async  (username:string,pwd:string) => await ipcRenderer.invoke('api:login',username,pwd),
})
// --------------- interface.d.ts  同单向
export interface IElectronAPI {ping: () => Promise<void>,// 新增声明login: (username:string,pwd:string) => Promise<void>,
}declare global {
interface Window {electronAPI: IElectronAPI
}
}
// --------------- sing.vue
<-- sign.vue -->
<script setup lang="ts">
<-- 直接使用 -->
userRef.value.username = window.info.pwd()
userRef.value.pwd = window.info.username()
window.electronAPI.ping()
<-- vue 页面调用 通过通道后需要再次解一次 -->
window.electronAPI.login(userRef.value.account,userRef.value.password)
.then(response => {console.log('请求登录 成功: ',response);return response;
}).catch(err => {console.log('请求登录 异常: ',err);return null;
})
</script>
主进程=>渲染进程(单向)

主线程创建后直接发送,页面会接收不到,应该是监听还没有开启就已经发送过去了。

直接发送object会报异常: Error: Failed to serialize arguments ,发送的数据未能序列化,发送基础数据可以,需要注意;
自定义的object也是可以的,如下面的修改:

// 1.创建一个基本object
const server:serverInfo = {
ip: ‘’,
ivm: ‘’,
sn: ‘’,
timestamp:0
}

// 2.初始化时给object赋值
function search_info(){
// 1.获取服务器列表
search_server().then(res=>{
console.log(‘search_info’,res)
server.ip=res.ip
server.timestamp = res.timestamp
server.ivm = res.ivm
server.sn = res.sn
}).catch(err=>{
console.log(‘search_info’,err)
})}
// 3.初始化后在启动后发送给渲染页面( 发送失败,应该是时机不对)
不过可以使用按钮发送初始化后取到值后的 server:
click: () => mainWindow.webContents.send(‘api:syncserver’,server),

// --------------- main.ts 
// 新增菜单按钮,点击后会触发事件让其发送到渲染页面
const createWindow = () => {// Create the browser window.const mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js'),nodeIntegration:true,},});const menu = Menu.buildFromTemplate([{label: '查看',submenu: [{click: () => mainWindow.webContents.send('api:syncserver', 1),label: 'getServer'},]}])Menu.setApplicationMenu(menu)// and load the index.html of the app.if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);// Open the DevTools.mainWindow.webContents.openDevTools();} else {mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));}return mainWindow
};
// 在初始化Electron时完成。
// 可以作为一个方便的替代检查app. isready()和订阅ready事件,
// 如果应用程序还没有准备好。
app.whenReady().then(()=>{// ipcMain 注册 openFile 通道 触发 回调handleFileOpen函数ipcMain.handle('api:ping', search_server) // 等待渲染页面调用ipcMain.handle('api:login',async (event,username,pwd)=> {return await sendPOST_ASYNC('http://xxxxxxx/user/login',{username:username,pwd:pwd});})// 主线程 => 渲染线程 : 取到的服务信息要发送到渲染进程中一份,方便用户查看const mainWindow = createWindow() // 创建窗口// mainWindow.webContents.send('api:syncserver',search_server) // 发送获取到的server信息 ,直接发送,页面会接收不到,应该是监听还没有开启就已经发送过去了。
})
// --------------- preload.ts 新增ipcRenderer.on监听器
contextBridge.exposeInMainWorld('electronAPI',{// 暴露一个单行的函数openFile ,该函数会执行 主进程中的函数username: () => 'xxxxx',pwd: () => 'xxxxxxxxxx',ping: async () => await ipcRenderer.invoke('api:ping'),login: async  (username:string,pwd:string) => await ipcRenderer.invoke('api:login',username,pwd),syncserver: async (callback:any) => ipcRenderer.on('api:syncserver',(_event, value) => callback(value))
})
// --------------- interface.d.ts  同单向
export interface IElectronAPI {ping:() => Promise<void>,login: (username:string,pwd:string) => Promise<T>,syncserver: (callback) => Promise<T>,
}
declare global {
interface Window {electronAPI: IElectronAPI
}
}
// --------------- sing.vue
// 初始化加载
// 调用 预载脚本中的监听函数,监听api:syncserver通道,等待主线程发送消息;
window.electronAPI.syncserver((value:any)=>{console.log('触发syncserver:',value)
})

IPC通信与Typescript一起使用时

需要新建配置文件来全局增强接口,否则无法使用接口

// interface.d.ts 需要放到src下才会编译进去
export interface IElectronAPI {ping: () => Promise<void>,
}declare global {
interface Window {electronAPI: IElectronAPI
}
}

Electron Aunet | Electron (electronjs.org)toUpdate

electron-release-server 自动更新功能

electron-forge + 静态资源更新;

# electron-forge 创建的项目,添加下面代码
# main.ts
# 设置服务器地址
autoUpdater.setFeedURL({url:'http://xxxxxxxxxx/version/'})
# 60s检测一次
setInterval(() => {autoUpdater.checkForUpdates()
}, 10000)
# 检测到更新事件,触发弹窗
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
# 设置弹窗内容dialog.showMessageBox({type: 'info',buttons: ['Restart', 'Later'],title: 'Application Update',message: process.platform === 'win32' ? releaseNotes : releaseName,detail:'A new version has been downloaded. Starta om applikationen för att verkställa uppdateringarna.'}).then((returnValue) => {if (returnValue.response === 0) autoUpdater.quitAndInstall()})
})
# 异常告警 (否则会弹窗报错)
autoUpdater.on('error', (message) => {console.error('error try catch is :',message.message)
})
# ------------优化---------------
# 检测5次后不在使用
let updateNumber = 5
setInterval(() => {try {if(updateNumber>0){autoUpdater.checkForUpdates()updateNumber--}}catch (error) {console.log(error)}
}, 10000)

静态资源目录:

  1. nginx映射静态目录
  2. electron-forge 新版本打包后的3个文件(三个文件必须)

问题:正式发布后可以检测到更新
npm run start 测试环境无法检测到更新,应该是该 electron-squirrel-startup 插件的问题,但无法同时在开发环境和正式环境同时安装;
问题:弹出更新提示后无论点击什么都会自动安装新版本;

Electron npm install

# *** 打开npm配置文件 修改electron_mirror指定镜像
npm config editregistry=https://registry.npmmirror.comelectron_mirror=https://cdn.npmmirror.com/binaries/electron/electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
# 下载
npm install --save-dev electron 

npm 执行指令异常

Failed to remove some directories [
npm WARN cleanup [
npm WARN cleanup ‘D:\V3Work\v3project\node_modules\@vue’,
npm WARN cleanup [Error: EPERM: operation not permitted, rmdir ‘D:\V3Work\v3project\node_modules@vue\reactivity\dist’] {
npm WARN cleanup errno: -4048,
npm WARN cleanup code: ‘EPERM’,
npm WARN cleanup syscall: ‘rmdir’,
npm WARN cleanup path: ‘D:\V3Work\v3project\node_modules\@vue\reactivity\dist’
npm WARN cleanup }
npm WARN cleanup ],

operation not permitted 无法执行删除操作,没有权限,可以使用管理员运行dos后再执行命令

Electron-Store

参考:
Electron入门实践(4):数据缓存 - 掘金 (juejin.cn)

电子存储 - npm (npmjs.com)

Electron食用指南: 数据持久化组件Electron-Store - 掘金 (juejin.cn)

electron-store是一个基于Node.js文件系统的数据存储库,它可以将数据以JSON文件的形式保存在本地。

优点

  • 简单易用,无需安装数据库或其他依赖;
  • 支持多进程访问,可以在主进程和渲染进程中使用;
  • 支持点符号访问嵌套属性,例如store.get(‘foo.bar’);
  • 支持默认值,自动合并用户设置和默认设置;
  • 支持加密,可以使用密码对数据进行加密和解密;
  • 支持类型检查,可以使用TypeScript或JSDoc来定义数据类型;
  • 支持观察者模式,可以监听数据变化并执行回调函数;

安装:npm install electron-store

主线程导入

import Store = require(‘electron-store’);

// ------ 初始化

const store = new Store(); // 初始化存储器

使用:

// 存储一个字符串  
store.set('name', 'Allen');  // 获取一个字符串  
console.log(store.get('name')); //=> 'Allen'  // 存储一个对象  
store.set('user', {  
id: 1,  
username: 'Allen',  
email: 'allen@example.com'  
});  // 获取一个对象  
console.log(store.get('user')); //=> {id: 1, username: 'Allen', email: 'allen@example.com'}  // 使用点符号访问嵌套属性  
store.set('user.profile.avatar', 'https://example.com/avatar.png');  
console.log(store.get('user.profile.avatar')); //=> 'https://example.com/avatar.png'  // 删除一个属性  
store.delete('name');  
console.log(store.get('name')); //=> undefined  // 判断一个属性是否存在  
console.log(store.has('name')); //=> false  // 获取所有的数据  
console.log(store.store); //=> {user: {...}}  // 清空所有的数据  
store.clear();  
console.log(store.store); //=> {}  

使用方法:通过IPC通信暴露给页面调用,存储或查询;

使用问题

electron 监听软件头部事件无法触发页面事件(必须来自手势);

触发页面事件报错:必须来自手势。规避主进程无法触发页面特殊事件(如: 打开文件事件)。

<n-button id="realClickButton"  @click="selectFile()" type="info">打开文件
</n-button>
// 主进程触发->打开文件
const realClickButton = document.getElementById('realClickButton');
realClickButton.dispatchEvent(new MouseEvent('click', {view: window,bubbles: true,cancelable: true
}))

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

asammdf python库解析MF4文件(一)cut and filter

目录 cutfilter asammdf 是一个功能强大的 Python 库&#xff0c;专门用于处理汽车行业常用的 MDF&#xff08;Measured Data Format&#xff09;文件 这篇文章主要介绍mdf库的cut和filter函数 cut cut函数主要用于裁剪数据&#xff0c;比如你的MF4文件是一个100s的数据&…

性能测试01|性能测试理论

目录 一、性能测试概述 二、性能测试的分类 1、基准测试 2、负载测试 3、稳定性测试 4、压力测试 5、并发测试 三、性能测试的指标 1、响应时间 2、并发用户数 3、吞吐量 4、点击数 5、错误率 6、资源利用率 四、性能测试流程 1、性能需求分析 2、性能测试计划…

基于SpringBoot的斯诺克球馆预约购票管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

【JavaWeb】2. 通用基础代码

以下内容来源&#xff1a;编程导航。 无论在任何后端项目中&#xff0c;都可以复用的代码。 1、自定义异常 自定义错误码&#xff0c;对错误进行收敛&#xff0c;便于前端统一处理。 &#x1f4a1; 这里有 2 个小技巧&#xff1a; 自定义错误码时&#xff0c;建议跟主流的错…

获取IP地区

包 https://packagist.org/packages/geoip2/geoip2#v3.1.0 用composer加载包 composer require geoip2/geoip2 mmdb下载 https://github.com/P3TERX/GeoLite.mmdb?tabreadme-ov-file

企业国外传输大文件到国内该怎么做?

在全球化的商业环境中&#xff0c;企业跨国传输大文件已成为日常运营的重要组成部分。无论是项目合作、数据分析还是文件备份&#xff0c;高效且安全的文件传输对于企业的竞争力和业务连续性至关重要。 企业跨国传输文件的需求重要性 首先&#xff0c;跨国传输大文件能够显著提…

HTML+CSS+JS制作中华传统文化主题网站(内附源码,含5个页面)

一、作品介绍 HTMLCSSJS制作一个中华传统文化主题网站&#xff0c;包含首页、文化艺术页、传统工艺页、文化遗产页、关于我们页等5个静态页面。其中每个页面都包含一个导航栏、一个主要区域和一个底部区域。 二、页面结构 1. 顶部导航区 包含网站 Logo、主导航菜单&#xff…

stm32week3

stm32学习 二.外设 8.TIM输出比较 OC(output compare)输出比较 输出比较可以通过比较CNT与CCR寄存器值的关系&#xff0c;来对输出电平进行置1、置0、翻转操作&#xff0c;用于输出一定频率和占空比的PWM波形 每个高级定时器和通用定时器都拥有4个输出比较通道 高级定时器的…

学习threejs,导入assimp assimp2json格式的模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.AssimpJSONLoader as…

Webstorm整合Tabnine AI 编码工具

1、打开 WebStorm 设置 Windows/Linux: File -> Settings Mac: WebStorm -> Preferences 2、安装插件 选择 Plugins 点击 Marketplace 搜索 "Tabnine" 点击 Install 重启 WebStorm 如果第一种方式在插件中搜索不到Tabnine的话 则通过第二中方式安装 1、访问Tab…

Tableau数据可视化与仪表盘搭建-数据可视化原理

目录 内容 做个小实验 数据如何变成图表 1 2 维度和度量定义 3 度量映射图形&#xff0c;维度负责区分 1 可映射的数据类型 2 可视化字典 3 使用Tableau将数据变成图表(Tableau可视化原理) 1 2 拖拽 3 具体操作 4 总结 内容 点击左下角的工作表 tableau可以自动…

ansible-api分析(Inventory)

一. 简述&#xff1a; 通过ansible 实现系统初始化功能&#xff0c; 为和平台嵌入&#xff0c; 需要通过ansible的api进行功能实现。 准确来说&#xff0c;ansible并没有纯粹的外部接入api功能&#xff0c; 只是官方提供了原生类&#xff0c;用于继承接入&#xff0c;从而实现a…

[Linux]Mysql9.0.1服务端脱机安装配置教程(redhat)

前言 本教程适用于在yum源不可用的LInux主机上安装Mysql的场景。 以redhat系主机做操作示例&#xff0c;debian系主机可参照步骤&#xff0c;将对应的rpm -ivh命令换成dpkg -i。 1. 官网下载安装包 https://dev.mysql.com/downloads/mysql/ 1.1 版本分类 MySQL Enterprise…

【JAVA】Java开发小游戏 - 简单的2D平台跳跃游戏 基本的2D平台跳跃游戏框架,适合初学者学习和理解Java游戏开发的基础概念

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把…

HTTP/HTTPS ②-Cookie || Session || HTTP报头

这里是Themberfue 上篇文章介绍了HTTP报头的首行信息 本篇我们将更进一步讲解HTTP报头键值对的含义~~~ ❤️❤️❤️❤️ 报头Header ✨再上一篇的学习中&#xff0c;我们了解了HTTP的报头主要是通过键值对的结构存储和表达信息的&#xff1b;我们已经了解了首行的HTTP方法和UR…

Spring Boot 项目自定义加解密实现配置文件的加密

在Spring Boot项目中&#xff0c; 可以结合Jasypt 快速实现对配置文件中的部分属性进行加密。 完整的介绍参照&#xff1a; Spring Boot Jasypt 实现application.yml 属性加密的快速示例 但是作为一个技术强迫症&#xff0c;总是想着从底层开始实现属性的加解密&#xff0c;…

Bash Shell的操作环境

目录 1、路径与指令搜寻顺序 2、bash的进站&#xff08;开机&#xff09;与欢迎信息&#xff1a;/etc/issue&#xff0c;/etc/motd &#xff08;1&#xff09;/etc/issue &#xff08;2&#xff09;/etc/motd 3、bash的环境配置文件 &#xff08;1&#xff09;login与non-…

最好用的图文识别OCR -- PaddleOCR(2) 提高推理效率(PPOCR模型转ONNX模型进行推理)

在实际推理过程中&#xff0c;使用 PaddleOCR 模型时效率较慢&#xff0c;经测试每张图片的检测与识别平均耗时超过 5 秒&#xff0c;这在需要大规模自动化处理的场景中无法满足需求。为此&#xff0c;我尝试将 PaddleOCR 模型转换为 ONNX 格式进行推理&#xff0c;以提升效率。…

51单片机——共阴数码管实验

数码管中有8位数字&#xff0c;从右往左分别为LED1、LED2、...、LED8&#xff0c;如下图所示 如何实现点亮单个数字&#xff0c;用下图中的ABC来实现 P2.2管脚控制A&#xff0c;P2.3管脚控制B&#xff0c;P2.4管脚控制C //定义数码管位选管脚 sbit LSAP2^2; sbit LSBP2^3; s…

mv指令详解

&#x1f3dd;️专栏&#xff1a;https://blog.csdn.net/2301_81831423/category_12872319.html &#x1f305;主页&#xff1a;猫咪-9527-CSDN博客 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 基本语法 主要功能 常用选项详解 1. …