https://github.com/iLx11/electron-vue3-template
npm create vite@latest
# -- Vue
# -- Customize with create-vue
# -- Typecript -> Yes
# -- JSX Support -> No
# -- Router xxxx -> Yes
# -- Pinia -> Yes
# -- Vitest xxxx -> No
# -- End ot End -> No
# -- ESlint -> Yes
# -- Prettier -> Yes
cd <project-name>
npm install
npm run dev
npm install electron --save-dev
electron 的入口文件,创建主窗口并监听事件
const { app, protocol, BrowserWindow, globalShortcut } = require('electron')
// 需在当前文件内开头引入 Node.js 的 'path' 模块
const path = require('path')
app.commandLine.appendSwitch("--ignore-certificate-errors", "true");
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: "app", privileges: { secure: true, standard: true } }
]);
const createWindow = () => {
const win = new BrowserWindow({
minWidth: 960,
minHeight: 540,
width: 960,
height: 540,
// 窗口是否在屏幕居中. 默认值为 false
center: true,
// 设置为 false 时可以创建一个无边框窗口 默认值为 true。
frame: false,
// 窗口的透明属性
transparent: true,
// 窗口是否在创建时显示。 默认值为 true。
show: true,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: path.join(__dirname, 'preload.js'),
webSecurity: false,
}
})
win.setMenu(null)
if (app.isPackaged) {
win.loadURL(`file://${path.join(__dirname, '../dist/index.html')}`)
} else {
win.loadURL('http://127.0.0.1:5173/')
// 如果显示白屏则使用下面的地址
// win.loadURL('http://localhost:5173/')
win.webContents.openDevTools()
}
globalShortcut.register("CommandOrControl+Shift+i", function () {
win.webContents.openDevTools();
});
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
渲染进程与主进程以此文件为媒介进行交流
const { contextBridge, ipcRenderer } = require('electron')
// 最小化
const minimizeWindow = () => {
ipcRenderer.send('window-min')
}
// 最大化
const maximizeWindow = () => {
ipcRenderer.send('window-max')
}
// 关闭窗口
const closeWindow = () => {
ipcRenderer.send('window-close')
}
// 裁剪图片
const resizeImage = async (resizeWidth, resizeHeight, editorPicData) => {
const data = await ipcRenderer.invoke('pic-data-editor', resizeWidth, resizeHeight, editorPicData)
return data
}
// 生成数据
const generateResultArray = async ( picData, configArray0, configArray1, configArray2, configArray3) => {
const data = ipcRenderer.invoke('pic-data-parse', picData, configArray0, configArray1, configArray2, configArray3)
return data
}
contextBridge.exposeInMainWorld('myApi', {
minimizeWindow,
maximizeWindow,
closeWindow,
resizeImage,
generateResultArray
})
// 所有的 Node.js API接口 都可以在 preload 进程中被调用.
// 它拥有与Chrome扩展一样的沙盒。
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])
}
})
"name": "xxxxxx",
"version": "1.0.0",
"private": true,
"main": "electron/main/main.js", // here
"author": "iLx1",
"description": "xxxxxxxx",
"scripts": {
"start": "vite | electron .",
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"electron:build": "vite build && electron-builder"
},
......
执行下面命令看显示的效果
pnpm start / npm run start
electron/controller/windowControl.js
const { ipcMain, BrowserWindow } = require('electron')
// 最小化
ipcMain.on('window-min', event => {
const webContent = event.sender
const win = BrowserWindow.fromWebContents(webContent)
win.minimize()
})
// 最大化
ipcMain.on('window-max', event => {
const webContent = event.sender
const win = BrowserWindow.fromWebContents(webContent)
if (win.isMaximized()) {
win.restore()
} else {
win.maximize()
}
})
// 关闭
ipcMain.on('window-close', event => {
const webContent = event.sender
const win = BrowserWindow.fromWebContents(webContent)
win.close()
})
main.js
require('../controller/windowControl.js')
electron/controller/tray.js
// 创建系统托盘
const { Tray, Menu } = require('electron')
// const { ipcRenderer } = require('electron')
const path = require('path')
const createTray = (app, win) => {
let tray = new Tray(path.join(__dirname, '../public/favicon.ico'))
// if (process.env.NODE_ENV === 'development') {
// tray = new Tray(path.join(__dirname, '../public/favicon.ico'))
// } else {
// tray = new Tray(path.join(path.dirname(app.getPath('exe')), '/resources/public/logo.ico'))
// }
tray.setToolTip('xxxxx') // 鼠标放在托盘图标上的提示信息
tray.on('click', (e) => {
if (e.shiftKey) {
app.quit()
} else {
win.show()
}
})
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: '退出',
click: () => {
app.quit()
}
}
])
)
}
module.exports = createTray
main.js
require('../controller/tray.js')
const createWindow = () => {
...
createTray(app, win)
}
该包集成了Vite
和Electron
,比如使用它之后可以让我们方便的在渲染进程中使用Node API
或者Electron API
,或者 vite 热更新 electron
详细使用用法可以去官网学习:vite-plugin-electron。
npm install vite-plugin-electron -D
ts
后缀vite.config.ts
import electron from 'vite-plugin-electron'
plugins: [
// optimizer(getReplacer()),
vue(),
electron({
main:{
// 入口文件的路径
entry: "electron/main/main.ts",
vite: {
build: {
// 将入口文件转为 js 后输出到指定目录
outDir: "appDir/main"
}
}
},
preload: {
// 预加载文件的绝对路径
input: path.join(__dirname, "./electron/main/preload.ts"), // 预加载文件
vite: {
build: {
// 将预加载文件转为 js 后输出到指定目录
outDir: "appDir/preload"
}
}
}
})
],
package.json
{
"name": "app-name",
"version": "0.0.1",
"private": true,
"main": "appDir/main/main.js",
"author": "iLx1",
"description": "description",
...
"files": [
"./dist",
"./electron",
// 入口文件与预加载文件输出的目录
"./appDir"
],
}
https://www.electronjs.org/zh/docs/latest/api/app
setIgnoreMouseEvents 可以使窗口忽略窗口内的所有鼠标事件
设置以下参数,只有点击会穿透窗口,鼠标移动则会正常触发
setIgnoreMouseEvents(true, {forward: true})
在 App.vue 组件增加 mounted 钩子函数
mounted() {
const remote = require("electron").remote;
let win = remote.getCurrentWindow();
window.addEventListener("mousemove", event => {
// 根元素 html
let flag = event.target === document.documentElement;
if(flag) {
win.setIgnoreMouseEvents(true, {forward: true});
} else {
win.setIgnoreMouseEvents(false);
}
});
win.setIgnoreMouseEvents(true, { forward: true});
}
给 html body 设置
pointer-events: none;
给 app 设置
pointer-events: auto;
window.onbeforeunload = function() {
const remote = require("electron").remote;o
let win = remote.getCurrentWindow();
win.destroy;
}
使用 Node.js 提供 fs.watch
来监视文件变化
打开模拟窗口
const remote = require("electron").remote;
this.win = new remote.BrowerWindow({
parent: remote.getCurrentWindow(),
// 模态窗口会禁用父窗口
modal: true,
webPreferences: {
nodeIntegration: true
}
})
let webContent = win.webContents;
const {webContents} = requitre('electron')
let webContent = webContents.getFocusedWebContents();
let webContent = remote.getCurrentWebContents();
let webContent = webContents.fromId(yourId);
let webContentArr = webContents.getAllWebContents();
可以通过此标签在网页中嵌入另一个网页的内容
<webview id="id_" src="xxxxx"></webview>
此标签默认不可用,需要在创建窗口时,设置 webviewTag 属性
webPreference: {
webviewTag: true
}
子窗口的形式,依托于 BrowserWindow 存在,可以绑定在具体区域,像其中的一个元素一样
let view = new BrowserView({
webPreferences: {preload}
});
win.setBrowserView(view);
let size = win.getSize();
view.setBounds({
x: 0,
y: 60,
width: size[0],
height: size[1] - 60
});
view.setAutoResize({
width: true,
height: true
});
view.webContents.loadURL(url);
动画对象可以 pause() play() reverse() onfinish
let keyframe = [{
transform: "xxx",
opacity: 0
}, {
xxxx
}]
let options = {
iterations: 1,
delay: 0,
duration: 800,
easing: "ease"
}
let animationObj = document.querySelector(".xx").animate(keyframes, options);
const popBoxShow = ref<boolean>(false)
const popBoxRef = ref<HTMLElement | null>(null)
const popBoxText = ref<string>('')
let timer: any
const showPop = (text: string) => {
popBoxShow.value = true
popBoxText.value = text
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
const backAnimationEffect = new KeyframeEffect(
(popBoxRef as any).value, // element to animate
[
{
width: '30%',
opacity: '100%',
top: '10%'
},
{
width: '26%',
opacity: '100%',
top: '10%',
height: '50px'
},
{
width: '35%',
opacity: '0%',
top: '8%',
height: '30px'
}
],
{
duration: 250
} // keyframe options
)
const backAnimation = new Animation(backAnimationEffect, document.timeline)
backAnimation.play()
backAnimation.onfinish = () => {
popBoxShow.value = false
}
}, 2000)
}
defineExpose({
showPop
})
win: C:\Users\[your user name]\AppData\Roaming
mac: /Users/[]/Library/Application Support/
Linux: /home/[]/.config/xiangxuema
可以获取路径
app.getPath("userData");
home destop documents downloads pictures music video
app.setPath('appData', 'D:\\xxx\\xxx')
let fs = require("fs-extra")
let path = require("path")
let dataPath = app.getPath("userData")
dataPath = path.join(dataPath, "test.data")
fs.writeFileSync(dataPath, yourData, {encoding: 'utf8'})
let fs = require("fs-extra")
let path = require("path")
let dataPath = app.getPath("userData")
dataPath = path.join(dataPath, "test.data")
let yourData = fs.readFileSync(dataPath, {encoding: 'utf8'})
基于 Lodash 的小巧 JSON 数据库
// 创建数据库访问对象
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json')
const db = low(adapter)
// 查找数据
db.get('posts').find({id: 1}).value();
// 更新数据
db.get('posts').find({title: 'low'}).assign({
title: 'hi'
}).write();
// 删除数据
db.get('posts').remove({
title: 'low'
}).write();
// 排序数据
db.get('posts').filter({
published: true
}).sortBy('views').take(5).value();
const Store = require('electron-store')
const store = new Store()
store.set('key', 'value')
store.get('key')
const {dialog, app} = require("electron").remote
let filePath = await dialog.showOpenDialog({
title: "选择一个文件",
buttonLabel: "打开文件",
defaultPath: app.getPath('pictures'),
// 多选文件
properties: "multiSelections",
filters: [
// 文件类型
{name: "图片", extensions: ["jpg", "png", "gif"]},
{name: "视频", extensions: ["xxx", "xxx", "xxx"]},
]
})
// 拿到文件可以进行读写操作
let win = new BrowserWindow({
webPreferences: {nodeIntergration: true},
// 隐藏系统菜单
autoHideMenuBar: true
})
let Menu = require('electron').Menu
let templateArr = [{
label: '菜单 1',
submenu: [{label: "菜单 1 - 1"}]
},{
label: '菜单 1',
submenu: [{label: "菜单 1 - 1"}]
}]
let menu = Menu.buildFromTemplate(templateArr)
Menu.setApplactionMenu(menu)
写完 html 与 css 之后用 js 关联响应
window.oncontextmenu = function(e) {
e.preventDefault()
const menu = document.querySelector("#xxx")
menu.style.left = e.clientX + 'px'
menu.style.top = e.clientY + 'px'
menu.style.display = 'block'
}
window.onclick = function(e) {
document.querySelector('#xxx').style.display = 'none'
}
let {Menu} = require('electron').remote
let menu = Menu.buildFromTemplate([
{
label: "",
click() {
console.log("xxx")
}
}
])
window.oncontextmenu = function(e) {
e.preventDefault()
menu.popup()
}
window.onkeydown = function() {
if((event.ctrlKey || event.metakey) && xxxxx))
}
在非激活状态下也能监听到用户按键
const {globalShortcut} = require('electron')
globalShortcut.register('CommandOrControl+K', () => {
console.log("按键监听")
})
// 取消注册快捷键
globalShortcut.unregister
设置托盘
let {app, BrowserWindow, Tray} = require('electron')
let path = require('path')
let tray
app.on('ready', () => {
let iconPath = path.join(__dirname, 'icon.png')
tray = new Tray(iconPath)
})
托盘闪烁
let iconPath2 = path.join(__dirname, 'icon2.png')
let flag = true
setInterval(() => {
if(flag) {
tray.setImage(iconPath2)
flag = false
} else {
tray.setImage(iconPath)
flag = true
}
},600)
// double-click right-click
tray.on('click', () => {
win.show()
})
let {Tray, Menu} = require('electron')
let menu = Menu.buildFromTemplate([
{
click() {
win.show()
},
label: '显示窗口',
type: 'normal'
},
{
click() {
app.quit()
},
label: '退出应用',
type: 'normal'
}
}])
tray.setContextMenu(menu)
let {clipboard} = require("electron")
clipboard.writeText("你好")
clipboard.writeHTML("<h1>你好</h1>")
let path = require("path")
let {clipboard, nativeImage} = require("electron")
let imgPath = path.join(__static, "icon.png")
let img = nativeImage.createFromPath(imgPath)
clipboard.writeImage(img)
clipboard.clear()
clipboard.readText()
clipboard.readHTML()
let img = clipboard.readImage()
let dataUrl = img.toDataURL()
let imgDom = document.createElement('img')
imgDom.src = dataUrl
document.body.appendChild(imgDom)
可以获取文件路径
// win
let filePath = clipboard.readBuffer('FileNameW').toString('ucs2')
filePath = filePath.replace(RegExp(String.fromCharCode(0), 'g'), '')
// mac
var filePath = clipboard.read('public.file-url').replace('file://', '')
或者借助 clipboard-files
Node.js 模块
const clipboard = require('clipboard-files')
let fileNames = clipboard.readFiles()
首先需要获取用户授权,在渲染进程中可以不需要授权,直接创建实例
Notification.requestPermission(function(status) {
if(status === "granted") {
let notification = new Notification('新的信息', {
body: '消息内容'
})
} else {
// 拒绝授权
}
})
如果点击通知就会触发
notification.onclick = function() {
console.log('xxx')
}
可以多次调用显示同样通知,click 事件不能用 onclick 注册
const {Notification} = require("electron").remote
let notification = new Notification({
title: "消息",
body: "消息正文"
})
notification.show()
notification.on("click", () => {
console.log("点击了消息")
})
const {shell} = require("electron")
// 打开 URL 链接
shell.openExternal("http://xxx")
是一个同步方法,可能会阻塞执行
let openFlag = shell.openItem("D:\\xxx\\xxx.docx")
let delFlag = shell.moveItemToTrash("D:\\xx\\")
document.addEventListener('dragover', ev => {
// 保证 drop 事件正确触发
ev.preventDefault()
})
document.addEventListener('drop', ev => {
// File 数组
console.log(ev.dataTransfer.files)
ev.preventDefault()
})
let fr = new FileReader()
fr.onload = () => {
var buffer = new Buffer.from(fr.result)
fs.writeFile(newFilePath, buffer, err => {
// 保存
})
}
fr.readAsArrayBuffer(fileObj)
// fr.readAsText() -> 文本方式
// fr.readAsDataURL() -> base64
// fr.readAsBinaryString() -> 二进制
app.addRecentDocument('C:\XXX\XXX')
app.clearRecentDocuments();
Node.js 访问
let https = require("https")
let url = "https://xxxxx"
https.get(url, res => {
let html = ""
res.on("data", data => (html += data))
res.on("end", () => console.log(html))
})
webSecurity: false
let websocket = new WebSocket("ws://localhost:8000/")
// 打开时触发
websocket.onopen = function(evt)
// 关闭时触发
websocket.onclose
// 收到消息
websocket.onmessage
// 产生异常
websocket.onerror
// 发送数据
websocket.send("xxxx")
// 关闭连接
websocket.close()
IPC 命名管道技术,包含服务端和客户端,可以持久连接双向通信
如果有第三方程序需要发送数据,可以在 Electron 创建命名管道服务端接收数据
let net = require('net')
let PIPE_PATH = "\\\\.\\ pipe\\ mypipe"
let server = net.createServer(function(conn) {
conn.on('data', d => console.log(`接收到数据: ${d.toString()}`))
conn.on('end', () => console.log("客户端已关闭连接"))
conn.write('建立连接后,发送信息')
})
server.on('close', () => console.log('服务关闭'))
server.listen(PIPE_PATH, () => console.log("服务启动,正在监听"))
客户端
let net = require('net')
let PIPE_PATH = "\\\\.\\ pipe\\ mypipe"
let client = net.connect(PIPE_PATH, () => {
console.log("连接成功")
client.write("第一个数据包")
})
client.on("data", d => {
console.log("接收数据 -> ", d)
client.end("最后的消息,发完就关闭")
})
client.on("end", () => console.log("服务端关闭了连接"))
var http = require('http')
let server = http.createServer((request, reponse) => {
if(request.url == "/electron") {
let jsonString = ''
request.on('data', data => jsonString += data)
request.on('end', () => {
let post = JSON.parse(jsonString)
// 业务逻辑
response.writeHead(200, {
"Content-Type": "text/html",
"Access-Control-Allow-Origin": "*"
})
let result = JSON.stringify({
"ok": true,
"msg": "请求成功"
})
reponse.end(result)
})
return
}
})
server.on("error", err => {
// 可以发给渲染进程
})
server.listen(9416)
// 如果设置 listen(0),需要获取具体监听的端口
server.on('listening', () => {
console.log(server.address().port)
})
Electron 可以自由使用操作硬件设备的能力,且默认有硬件的访问权限
获取主显示信息
let remote = require("electron").remote
let mainScreen = remote.screen.getPrimaryDisplay()
console.log(mainScreen)
控制窗口显示在外接显示器上
let {screen} require('electron')
let displays = screen.getAllDisplays()
let externalDisplay = displays.find(display => {
// 判断是否为外接屏幕
return display.bounds.x !== 0 || diplay.bounds.y !== 0
})
if(externalDisplay) ){
win = new BrowserWindow({
x: externalDisplay.bounds.x + 50,
y: externalDisplay.bounds.y + 50,
webPreferences: {
nodeIntegration: true
}
})
win.loadURL('https://xx')
}
const exec = require("child_process").exec
exec("osk.exe")
let option = {
audio: true,
video: true
}
let mediaStream = await navigator.mediaDevices.getUserMedia(option)
let viode = document.querySelector("video")
video.srcObject = mediaStream
video.onloadedmetadata = function(e) {
video.play()
}
// 设置视频的大小
video: {widht: xxx, height: xxx}
// 取设备前置摄像头
video: {facingMode: "user"}
// 后置摄像头
video: {facingMode: "environment"}
获取数组中包含视频和音频设备信息
let devices = await navigator.mediaDevices.enumerateDevices()
此方法如果配置的设备不可用,将会随机返回可用设备
video: {deviceId: myPreferencedCameraDeviceId}
可以指定,不可用则会抛出异常
video: {deviceId: {exect: myPreferencedCameraDeviceId} }
const {desktopCapturer} = require("electron")
let sources = await desktopCapturer.getSources({
types: ["window", "screen"]
})
let target = sources.find(v => v.name == "微信")
let mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: target.id
}
}
})
var video = document.querySelector("video")
video.srcObject = mediaStream
video.onloadedmetadata = function(e) {
video.play()
}
let batteryManager = await navigator.getBattery()
const {powerMonitor} = require("electron").remote
powerMonitor.on("suspend", () => {
console.log("sys is going to sleep")
})
powerMonitor.on("resume", () => {
console.log("sys is going to wakeup")
})
powerMonitor.on("lock-screen", () => {
console.log("sys is lock screen")
})
powerMonitor.on("unlock-screen", () => {
console.log("sys is unlock screen")
})
const {powerSaveBlocker} = require("electron")
// 阻止锁屏
cosnt id = powerSaveBlocker.start("prevent-display-sleep")
// 取消阻止
powerSaveBlocker.stop(id)
// 判断是否阻止
powerSaveBlocker.isStarted(id)
let {remote} = require("electron")
let path = require("path")
let webContents = remote.getCurrentWebContents()
let data = await webContents.printToPDF({})
let filePath = path.join(__static, "xxx.pdf")
fs.writeFile(filePath, data, err => {
id(err) throw err
console.log("保存成功")
})
let pathObj = await remote.dialog.showSaveDialog({
title: "保存 PDF",
filters: [{name: "pdf", extensions: ["pdf"]}]
})
if(pathObj.cancled) return
fs.writeFile(pathObj.filePath, data, err => {
if(err) throw err
console.log("保存成功")
})
// 内存使用量
let memoryUseage = process.getSystemMemoryInfo()
// CPU 使用情况
let cpuUseage = process.getCPUUseage()
// 每次调用是取本次与上次的评价值,所以用定时器读取
setInterval(() => {
cpuUseage = process.getCPUUseage()
console.log(cpuUseage)
})
let si = require('systeminformation')
(async function() {
// cpu 信息
let cpuInfo = await si.cpu()
// 内存信息
let memInfo = await si.mem()
// 网卡信息
let networkInterfaces = await si.networkInterfaces()
// 获取磁盘信息
let diskLayout = await si.diskLayout()
})
pnpm i electron-icon-builder --dev
"build-icon": "electron-icon-builder --input=./public/icon.png --output=build --flatten"
setState() {
let win = remote.getCurrentWindow();
// 返回 Rectangle 对象,包含窗口在屏幕上的坐标和大小
let rect = win.getBounds();
let isMaxsize = win.isMaximized();
}
// pinia
ipcMain.on('store-set', (event, objData) => {
// 遍历窗口发送
for(const cur of BrowserWindow.getAllWindows()) {
if(cur != BrowserWindow.fromWebContents(event.sender)) {
cur.webContents.send('store-get', objData)
}
}
})
// Pinia store 设置主动更改同步
const setConfigStore = (obj: object) => {
// console.log(obj)
ipcRenderer.send('store-set', obj)
}
contextBridge.exposeInMainWorld('myApi', {
setConfigStore,
// Pinia store 设置被动同步监听
storeChangeListen: (callbacka) =>
ipcRenderer.on('store-get', (event, data) => {
callbacka(data)
})
})
// 窗口 1
onMounted(() => {
// 主页面监听
win.myApi.storeChangeListen((objData: object) => {
console.info('homePage listening', objData)
// 有 get 属性
if (objData.get) {
let storeValue = objData.get
let tempObj: object = {}
tempObj[storeValue] = configStore[storeValue]
// 发送其他窗口同步
win.myApi.setConfigStore(tempObj)
return
}
try {
// 同步信息
const keys = Object.keys(objData)
for (let key of keys) {
// 设置对应 store 的值
configStore[`set${key.replace(key.charAt(0), key.charAt(0).toUpperCase())}`](objData[key])
}
} catch (error) {
console.error(error)
}
})
})
// 窗口 2 获取窗口 1 的 store
onMounted(() => {
win.myApi.storeChangeListen((objData: object) => {
console.info('keyConfigPage listening')
const keys = Object.keys(objData)
for(let key of keys) {
// 设置对应 store 的
configStore[`set${key.replace(key.charAt(0), key.charAt(0).toUpperCase())}`](objData[key])
}
})
// 获取 配置的索引
win.myApi.setConfigStore({
get: 'configIndex'
})
})
安装electron打包开发依赖
最新版本:npm install –save-dev electron-builder
指定版本:npm install –save-dev electron-builder@23.6.0
"build": {
"appId": "com.xxxxx.xxxx",//包名
"productName": "xxxxxx", //项目名 这也是生成的exe文件的前缀名
"asar": false,
"copyright": "Copyright © 2022 electron",//版权 信息
"publish": {
"provider": "github",// 服务器提供商 也可以是GitHub等等
"url": ""// 服务器地址
"owner": "iLx11",
"repo": "screen-go"
},
"directories": { // 输出文件夹
"output": "dist_electron/"
},
"extraResources": [
{
"from": "./public",
"to": "./public"
}
],
"files": [ // 打包的electron需要包含的文件,一般就是与electron的有关的打包进去
"./dist", // vue 打包文件
"./electron" // electron 主文件
],
"mac": {
"artifactName": "${productName}_${version}.${ext}",
"target": [
"dmg"
]
},
"win": {
"icon": "public/favicon.ico",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
],
"artifactName": "${productName}_${version}.${ext}"
},
"nsis": {
"oneClick": false,// 是否一键安装
"perMachine": false,
"allowToChangeInstallationDirectory": true,// 允许修改安装目录
"deleteAppDataOnUninstall": false,
"installerIcon": "public/favicon.ico",// 安装图标
"uninstallerIcon": "public/favicon.ico",// 创建桌面图标
"createDesktopShortcut": true,// 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "xxxxx" // 图标名称
},
"releaseInfo": {
"releaseNotes": "版本更新的具体内容"
}
}
pnpm electron:build / npm run electron:build
https://blog.csdn.net/weixin_43239880/article/details/129563632?spm=1001.2014.3001.5501
https://www.electronjs.org/zh/docs/latest/tutorial/ipc
win.webContents.send
发送消息ipcRenderer.on
接收消息ipcRenderer.send
或者 ipcRenderer.invoke
发送消息ipcMain.on
或者ipcMain.handle
接收消息https://blog.csdn.net/weixin_39607450/article/details/110647391
-
https://www.jb51.net/article/278408.htm
content:
vite官方文档的解释:https://vitejs.bootcss.com/guide/assets.html
我们看到实际上我们不希望资源文件被webpack编译可以把图片放到public 目录会更省事,不管是开发环境还是生产环境,可以始终以根目录保持图片路径的一致,这点跟webpack是一致的
import homeIcon from '@/assets/images/home/home_icon.png'
<img :src="homeIcon"/>
推荐,这种方式传入的变量可以动态传入文件路径!!
静态资源处理 | Vite 官方中文文档
new URL() + import.meta.url
这里我们假设:
工具文件目录: src/util/pub-use.ts
pub-use.ts
// 获取assets静态资源export defaultconst getAssetsFile = (url: string) => { returnnewURL(`../assets/images/${url}`, import.meta.url).href}
使用
import usePub from '@/util/public-use'setup () { const Pub = usePub() const getAssetsFile = Pub.getAssetsFile return{ getAssetsFile }}
可以包含文件路径
<img :src="getAssetsFile('/home/home_icon.png')"/>
不推荐,这种方式引入的文件必须指定到具体文件夹路径,传入的变量中只能为文件名,不能包含文件路径
使用vite的import.meta.glob
或import.meta.globEager
,两者的区别是前者懒加载资源,后者直接引入。
这里我们假设:
工具文件目录: src/util/pub-use.ts
pub-use.ts
// 获取assets静态资源
export defaultconst getAssetsHomeFile = (url: string) => {
const path = `../assets/images/home/${url}`;
const modules = import.meta.globEager("../assets/images/home/*");
return modules[path].default;
}
使用
import usePub from '@/util/public-use'
setup () {
const Pub = usePub()
const getAssetsHomeFile = Pub.getAssetsHomeFile
return{ getAssetsHomeFile }
}
不能包含文件路径
<img :src="getAssetsHomeFile('home_icon.png')"/>
.imgText { background-image: url('../../assets/images/1462466500644.jpg');}
-
https://blog.csdn.net/qq_45284938/article/details/129707796
vite.config:
outDir: BUILD_DIR, // 指定打包文件的输出目录
emptyOutDir: true, // 打包时先清空上一次构建生成的目录
完整 build:
build: {
outDir: BUILD_DIR,
sourcemap: false,
minify: 'terser',
chunkSizeWarningLimit: 1500,
emptyOutDir: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
},
chunkFileNames: (chunkInfo) => {
const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/') : [];
const fileName = facadeModuleId[facadeModuleId.length - 2] || '[name]';
return `js/${fileName}/[name].[hash].js`;
}
}
}
},
-
https://blog.csdn.net/s5s5s5s5s/article/details/127493590
preload 文件有错,或许是代码次序问题,暴露的函数要在定义之后
-
https://blog.csdn.net/weixin_41568995/article/details/120352394
webPreferences: {
// 版本默认不支持 node,使用需添加
nodeIntegration:true,
// 不使用 contextBridge API
contextIsolation: false,
enableRemoteModule: true,
preload: path.join(__dirname, 'preload.js')
}
-
https://cloud.tencent.com/developer/ask/sof/171603
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
await timeout(3000);
return fn(...args);
}
或简短
await new Promise(resolve => setTimeout(resolve, 1000));
-
https://zhuanlan.zhihu.com/p/540056695
使用 optimizer
插件
不推荐,主进程与渲染进程分开好一点
//vite.config.ts
import { defineConfig } from "vite";
import optimizer from "vite-plugin-optimizer";
let getReplacer = () => {
let externalModels = ["electron", "os", "fs", "path", "events", "child_process", "crypto", "http", "buffer", "url", "better-sqlite3", "knex"];
let result = {};
for (let item of externalModels) {
result[item] = () => ({
find: new RegExp(`^${item}$`),
code: `const ${item} = require('${item}');export { ${item} as default }`,
});
}
return result;
};
export default defineConfig({
plugins: [optimizer(getReplacer())],
});
-
https://codeantenna.com/a/prLQN2PBl9
使用vite构建项目的时候需要用到crypto进行加密出现的错误。问题出在vite本身使用了crypto,我们如果通过npm i crypto -S会导致vite构建时报错。
换个库或者是使用上面的方法
-