【Electron】开发实战

news2024/11/26 19:48:16

文章目录

  • 第一章 征程
  • 第二章-主进程与渲染进程
  • 第三章-H5拖拽读取本地文件
  • 第四章-快捷键注册及复制粘贴
  • 第五章-渲染进程调用主进程模块
  • 第六章-菜单模块
  • 第七章-渲染进程与主进程间的通信
  • 第八章-渲染进程与渲染进程间的通信
  • 第九章-管理应用程序文件及url的加载方式
  • 第十章-系统对话框模块


第一章 征程

前言
如果你是第一次接触Electron,你应该先去官网了解看看,通过官方提供的一个快速启动程序,立即看看Electron是如何运转的。


克隆示例项目的仓库

$ git clone https://github.com/electron/electron-quick-start

进入这个仓库

$ cd electron-quick-start

安装依赖并运行

$ npm install && npm start

如果你对Electron有所了解,请直接使用electron-vue进行快速开发

开始
现在,让我们一起从头到尾构建并了解Electron

//创建目录
$ mkdir electron-test
//进入
$ cd electron-test
//创建UI界面文
$ touch index.html
//创建主进程文件**
$ touch main.js
//创建package.json
$ npm init 
//安装依赖
$ npm i electron --save
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>test</title>
</head>
<body>
    hello world
</body>
</html>
//main.js
//主进程

//引入electron模块
var electron =require('electron');

//nodejs中的path模块
var path=require('path');

//创建electron引用     控制应用生命周期的模块
var app=electron.app;     

//创建electron BrowserWindow的引用          窗口相关的模块
var BrowserWindow=electron.BrowserWindow;

//变量 保存对应用窗口的引用
var mainWindow=null;

function createWindow(){
    //创建BrowserWindow的实例 赋值给mainWindow打开窗口   
    mainWindow=new BrowserWindow({width:800,height:600,webPreferences: {
        nodeIntegration: true
    }}); 
  
    mainWindow.loadFile(path.join('index.html'));
    //开启渲染进程中的调试模式
    mainWindow.webContents.openDevTools();

    mainWindow.on('closed',()=>{
        mainWindow=null;
    })    

}

app.on('ready',createWindow)


// 当所有的窗口被关闭后退出应用   Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q

    // 对于OS X系统,应用和相应的菜单栏会一直激活直到用户通过Cmd + Q显式退出
    if (process.platform !== 'darwin') {
      app.quit();
    }
  });
  
//macos
app.on('activate', () => {
// 对于OS X系统,当dock图标被点击后会重新创建一个app窗口,并且不会有其他
    if (mainWindow === null) {
        createWindow();
    }
});
//package.json
{
...
  //新增npm指令
  "scripts": {
    +"start": "electron ."
  },
 ...
}

启动

npm start

结果
在这里插入图片描述

第二章-主进程与渲染进程

我们需要先明白,主进程和渲染进程是什么?

主进程
Electron使用前端技术作为APP的GUI,可以把它当做一个小型的Chrome内核浏览器,在package.json文件中的main键值就是主进程文件,主进程只有一个!

渲染进程
依赖于chrome内核的多进程架构,我们可以在Electron的每个页面使用自己的线程,这些线程称为渲染进程与在普通浏览器不同的是,在每个页面中,我们可以使用Node.js的API,可以在一定程度上与操作系统进行交互根据上面的说法,我们马上尝试一下两个练习:

  • 打开APP的时候启动两个窗口
  • 利用nodejs获取package.json的内容

1、准备两个页面

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>test</title>
</head>
<body>
    <div class="main">
        hello world peter
    </div>
    <script src="render/index.js"></script>
</body>
</html>
//new.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>新页面</title>
</head>
<body>
    <div class="main">
        hello world envas
    </div>
</body>
</html>

2、主进程配置窗口的参数,加载页面

//主进程

//引入electron模块
var electron =require('electron');

//nodejs中的path模块
var path=require('path');

//创建electron引用     控制应用生命周期的模块
var app=electron.app;     

//创建electron BrowserWindow的引用          窗口相关的模块
var BrowserWindow=electron.BrowserWindow;

//变量 保存对应用窗口的引用

var mainWindow=null;
var newWindow=null;


function createWindow(){
    //创建BrowserWindow的实例 赋值给mainWindow打开窗口   
    mainWindow=new BrowserWindow({width:800,height:600,webPreferences: {
        nodeIntegration: true
    }}); 
    //创建BrowserWindow的实例 赋值给newWindow打开窗口 
    newWindow=new BrowserWindow({width:200,height:200,webPreferences: {
        nodeIntegration: false
    }}); 
    mainWindow.loadFile(path.join('index.html'));
    newWindow.loadFile(path.join('new.html'));

    //开启渲染进程中的调试模式
    // mainWindow.webContents.openDevTools();

    mainWindow.on('closed',()=>{
        mainWindow=null;
    })    
    newWindow.on('closed',()=>{
        newWindow=null
    })
}

app.on('ready',createWindow)


// 当所有的窗口被关闭后退出应用   Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q

    // 对于OS X系统,应用和相应的菜单栏会一直激活直到用户通过Cmd + Q显式退出
    if (process.platform !== 'darwin') {
      app.quit();
    }
  });
  
//macos
app.on('activate', () => {
// 对于OS X系统,当dock图标被点击后会重新创建一个app窗口,并且不会有其他
    if (mainWindow === null) {
        createWindow();
    }
});

3、利用fs模块读取文件信息,并输出到页面中

//index.js
// nodejs中的fs模块
let fs = require('fs')

// 读取package.json的内容并输出到页面中
fs.readFile('package.json',(err,data) => {
  if(!err){
    var main = document.querySelector('.main')
    main.innerHTML = main.innerHTML + data
  }
})

启动

npm start

在这里插入图片描述

第三章-H5拖拽读取本地文件

拖拽文件
如何实现拖拽效果?
通过H5的拖拽API获取文件路径,再使用Node.js读取文件内容,并输出到指定区域内

  • 拖拽事件API

以下是实例代码

// nodejs中的fs模块
var fs=require('fs');

// 获取body
var content=document.getElementsByTagName('body')[0]

//取消H5拖拽事件默认行为 
content.ondragenter=content.ondragover=content.ondragleave=function(){
    return false; /*阻止默认行为*/
}

// 监听拖拽事件ondrop(松手)时读取文件内容,并输出到页面中
content.ondrop=function(e){
    //阻止默认行为
    e.preventDefault();     
    // 打印拖拽的文件
    console.log(e.dataTransfer.files[0]);
    var path=e.dataTransfer.files[0].path;
    fs.readFile(path,'utf-8',(err,data)=>{
        if(err){
            console.log(err);
            return false;
        }
        content.innerHTML=data;
    })
}

启动

npm start

扩展
考虑一下如何拖拽.exe文件,后自动打开该软件。

第四章-快捷键注册及复制粘贴

系统快捷键(globalShortcut)
进程:主进程
globalShortcut 模块可以在操作系统中注册/注销全局快捷键, 以便可以为操作定制各种快捷键。但只能在主进程中使用
globalShortcut主要接收两个参数,分别为注册的快捷键和回调函数

//main.js
//引入electron模块
var electron =require('electron');

//nodejs中的path模块
var path=require('path');

//创建electron引用     控制应用生命周期的模块
var app=electron.app;     

//创建electron BrowserWindow的引用          窗口相关的模块
var BrowserWindow=electron.BrowserWindow;

// 调用全局快捷键注册模块
var globalShortcut = electron.globalShortcut

//变量 保存对应用窗口的引用

var mainWindow=null;

function createWindow(){
    //创建BrowserWindow的实例 赋值给mainWindow打开窗口   
    mainWindow=new BrowserWindow({width:800,height:600,webPreferences: {
        nodeIntegration: true
    }}); 

    mainWindow.loadURL(path.join('file:',__dirname,'index.html'));
    
    //开启渲染进程中的调试模式
    // mainWindow.webContents.openDevTools();

    console.log(path.join('file:',__dirname,'index.html'));

    mainWindow.on('closed',()=>{
        mainWindow=null;
    })    

    const ret = globalShortcut.register('CommandOrControl+X', () => {
        console.log('CommandOrControl+X is pressed')
    })
    if (!ret) {
        console.log('registration failed')
    }
    // 检查快捷键是否注册成功
    console.log(globalShortcut.isRegistered('CommandOrControl+X'))
}

app.on('ready',createWindow)


// 当所有的窗口被关闭后退出应用   Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q

    // 对于OS X系统,应用和相应的菜单栏会一直激活直到用户通过Cmd + Q显式退出
    if (process.platform !== 'darwin') {
      app.quit();
    }
  });
  
//macos
app.on('activate', () => {
// 对于OS X系统,当dock图标被点击后会重新创建一个app窗口,并且不会有其他
    if (mainWindow === null) {
        createWindow();
    }
});

复制粘贴板(clipboard)
进程:主进程,渲染进程
以下是完整实例

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>electron</title>
    <style>
    
    </style>
</head>
<body>
    <div>
        <span id="paste">粘贴</span>
        <input id='copyInput' type="text" placeholder='已复制! 请在这里执行粘贴'>
    </div>
    <script src="render/index.js"></script>
</body>
</html>
//index.js
//创建Electron并引用复制模块
const { clipboard } = require('electron')

//提前复制好一段文本
clipboard.writeText('Example String')

let copyInput = document.querySelector('#copyInput')
let paste = document.querySelector('#paste')

//监听事件 并将复制内容输出到对应的DOM元素中
paste.addEventListener('click', function() {
    if (copyInput.value !== '') copyInput.value = ''
    copyInput.value = clipboard.readText()
})

启动

npm start

在这里插入图片描述
点击粘贴即可获取复制的内容
clipboard提供了不止复制粘贴文本的方法,还包括html、图片等许多方法

第五章-渲染进程调用主进程模块

渲染进程调用主进程模块(remote)
进程:渲染进程

remote模块帮助我们在渲染进程中调用主进程的模块。根据官方的说法,remote模块返回的对象(函数),是一个远程对象(函数),它不是简单的在渲染进程中创建一个新的对象实例,而是通知到主进程创建新的对象,随后发送到渲染进程,这种机制保证了主进程与渲染进程共用一个对象(内存),同步了进程消息。

1、在渲染进程通过调用remote模块获取BrowserWindow对象打开新的窗口加载新的页面

//index.js
//nodejs的path模块
const path = require('path')

//获取dom
const item = document.querySelector('#openWindow')

//通过Electron的remote模块引用BrowserWindow模块
const { BrowserWindow } = require('electron').remote

//监听点击事件
item.addEventListener('click', () => {
  let win = new BrowserWindow({ width: 400, height: 400 })
  var file = path.join('file:///', __dirname, 'new.html')
  win.loadURL(file)
  win.on('closed', function() {
    win = null
  })
})

启动

npm start

扩展
remote是怎么实现远程对象数据传递的?官方解释类似于 Java 的RMI 。

第六章-菜单模块

菜单模块(Menu)
进程:主进程
Menu 可以两种菜单分别是原生应用菜单和上下文菜单,如下所示
接下来我来演示一个原生应用菜单

//在渲染进程调用Menu模块
const {Menu}=require('electron').remote;

//定义菜单
var template=[
    {

        label:'文件',
        submenu:[
            {
                label:'新建文件',
                // 注册快捷键
                accelerator:'ctrl+n',
                // 点击触发回调
                click:function(){ 
                    console.log('ctrl+n');
                }
            },
            {
                label:'新建窗口',
                click:function(){ 
                    console.log('new window');
                }
            }
        ]
    },
    {
        label:'编辑',
        submenu:[
            {
                label:'复制11',
                //使用内置角色
                role:'copy'
            },
            {
                label:'截切',
                role:'cut'
            }
        ]
    }
]

var menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

//右键菜单事件
window.addEventListener('contextmenu',function(e){
    //阻止当前窗口默认事件
    e.preventDefault();

    //在当前窗口点击右键的时候弹出  定义的菜单模板
    menu.popup({window:remote.getCurrentWindow()})

},false)

启动

npm start

接下来你自己实现一个上下文菜单吧,参考这份菜单模板配置参数等相关信息

第七章-渲染进程与主进程间的通信

渲染进程与主进程的通信模块(ipcMain,ipcRenderer)
ipcMain:主进程
从主进程到渲染进程的异步通信。

ipcRenderer: 渲染进程
从渲染器进程到主进程的异步通信。

下面是一个完整的代码实例

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
  <div>
      <button class="sendMain">发送一条异步消息到主进程</button>
      <button class="sendMainSync">发送一条同步消息到主进程</button>
      <script src="./renderer/ipcRenderer.js"></script>
  </div>
</body>
</html>
//ipcRenderer.js
// 渲染进程下的通信模块
const {ipcRenderer} = require('electron')

// 获取DOM
const sendMain = document.querySelector('.sendMain')
const sendMainSync = document.querySelector('.sendMainSync')

// 向主进程发送名为sendMain的异步信息
sendMain.addEventListener('click', function() {
    ipcRenderer.send('sendMain','this is a sync news')
})

// 监听主进程对sendMain事件的异步返回结果
ipcRenderer.on('replay', function(event,arg) {
    alert(arg)
})

// 向主进程发送名为sendMainSync的同步信息
sendMainSync.addEventListener('click', function() {
    // 同步信息可直接获取返回结果
    var retunValue = ipcRenderer.sendSync('sendMainSync','this is a news')
    alert(retunValue)
})
// ipcMain.js
// 主进程通信模块
const {ipcMain} = require('electron')

// 监听sendMain事件下的异步信息
ipcMain.on('sendMain', function(event, data) {
    event.reply('replay',data+'主进程接收到的异步信息')
})

// 监听sendMainSync事件下的同步信息
ipcMain.on('sendMainSync', function(event, data) {
    // 同步信息直接返回结果
    event.returnValue = data+'主进程接收到的同步信息'
})

启动:

npm start

第八章-渲染进程与渲染进程间的通信

渲染进程与渲染进程的通信(webContents)
渲染以及控制 web 页面

进程:主进程
在主进程与渲染进程一文中,我提到依赖于chrome内核的多进程架构,我们可以在Electron的每个页面使用自己的线程,于是我们可能出现这样一种需求。我们需要在多个页面中进行通信,从前端的角度来讲,我们可以使用localStorage,来保存会话。但是本文将以Electron的方式来解决这个问题:
1、首先我们会在渲染进程中利用ipcRenderer发送消息到主进程中

//openWindow.js

// 渲染进程下的通信模块
const { ipcRenderer } = require('electron')

// 获取DOM
const sendNews = document.querySelector('.sendNews')

// 渲染进程向主进程openWindow事件
sendNews.addEventListener('click', function() {
    var news = Math.random();
    ipcRenderer.send('openWindow',news);
})

// 监听新页面传递回来的toIndex事件
ipcRenderer.on('toIndex', function(event,data) {
    alert('新页面传递过来的信息'+data)
})

2、紧接着,在主进程中,通过ipcMain模块监听到渲染进程发来的消息,再利用BrowserWindow创建窗口,最后就是本文的重点,使用webContents属性监听窗口创建完成后,将消息发送到新的窗口利用webContents发送的消息中还包含了BrowserWindow.getFocusedWindow().id传递的当前窗口的指针id。

这一做法的目的,是为了新窗口在接收到信息的同时,获取到发送消息的窗口的对象BrowserWindow对象

// ipcMain.js
// 主进程通信模块
const {ipcMain,BrowserWindow} = require('electron')
const path = require('path')
let win = null

// 监听openWindow事件下的异步信息
ipcMain.on('openWindow', function(event, data) {
    // 通过getFocusedWindow静态方法 获取当前焦点窗口的指针ID
    let winId =  BrowserWindow.getFocusedWindow().id
    // 配置窗口
    win = new BrowserWindow({
        width:400,
        height:600,
        webPreferences:{
            nodeIntegration: true
        }
    })
    // 加载页面
    win.loadURL(path.join('file:',__dirname,'../news.html'));
    /*
    窗口加载完毕后(did-finish-load)发送toNews消息 
    发送的内容分别为,index页面发送的消息及index页面的窗口Id
    */
    win.webContents.on('did-finish-load', function() {
        win.webContents.send('toNews',data,winId)
    })
})

3、在新的窗口,通过ipcRenderer监听webContents发送来的toNews事件消息

利用BrowserWindow.fromId(winId)我们可以获取原窗口BrowserWindow对象,再次调用webContents.send往原窗口,发送消息,通过这样的方式,就完成了渲染进程间的通信:

const { ipcRenderer } = require('electron') 
const { BrowserWindow } = require('electron').remote

ipcRenderer.on('toNews',function(event,data,winId) {
        alert(`该消息${data}${winId}窗口传递的`)
        // 通过winId  返回 该窗口BrowserWindow的对象 
        var firstWin = BrowserWindow.fromId(winId);
        // 通过webContents向该窗口传递信息
        firstWin.webContents.send('toIndex','this is news');
})

启动:

npm start

从上文代码示例,我们可以知道渲染进程间的通信关键在于webContentswebContentsEventEmitter 的一个实例, 负责渲染和控制网页, 是 BrowserWindow 对象的一个属性

扩展
文章本来到这里就应该结束了,最近发现了关于webContents的一些其他用法,直接上代码了

ipcMain.on('open-music-file', (event) => {
    dialog.showOpenDialog({
      properties: ['openFile','multiSelections'],
      filters: [
        { name: 'Music', extensions: ['mp3'] }
      ]
    },(filesPath) => {
      if(filesPath) {
        event.sender.send('selected-file', filesPath);
      }
    })
  })

从以上代码中可以看到,我使用ipcMain监听渲染进程一个名为open-music-file的事件,通过dialog模块打开文件对话框,当选中文件后,我设置在dialog.showOpenDialog的回调函数就可以获取选中的文件路径,接下来就是重点,我使用了ipcMain回调函数中的event对象触发了selected-file事件传递了文件路径信息。重点就是:event.sender会返回webContents对象,所以event.sender.send可以出发对应地渲染进程的事件监听

第九章-管理应用程序文件及url的加载方式

管理文件及url(shell)
使用默认应用程序管理文件和 URL。

进程:主进程、渲染进程

shell模块支持在主进程与渲染进程中一同使用,通过它,我们可以完成不少实用的功能,它的用法非常简单,以下将通过代码实例将它的主要功能展示出来

1、配置一个菜单,包含所有shell的功能

// menu.js
const { Menu,shell,BrowserWindow } = require('electron')
const path = require('path')

// 在本地浏览器加载url
function openWeb(url) {
    shell.openExternal(url)
}
// 在webview标签中加载url
function openWebview(url) {
    // 获取当前窗口
    var win = BrowserWindow.getFocusedWindow();
    // 利用webContents发送信息给当前窗口
    win.webContents.send('openWebview',url);
}
// 打开当前目录
function opendirname (fullPath) {
    shell.showItemInFolder(fullPath)
}
// 打开文件
function openfile (fullPath) {
    shell.openItem(fullPath)
}
// 删掉文件
function deletefile (fullPath) {
    shell.moveItemToTrash(fullPath)
}

//菜单配置
const template = [
    {
        label: 'shell主要功能',
        submenu: [
            {
                label: 'web',
                click:function(){
                    console.log('x')
                    openWeb('https://www.github.com')
                }
            },
            {
                type: 'separator'
            },
            {
                label: 'webview',
                click:function(){
                    openWebview('https://www.github.com')
                }
            },
            {
                type: 'separator'
            },
            {
                label: 'dir',
                click:function(){
                    opendirname(__dirname)
                }
            },
            {
                type: 'separator'
            },
            {
                label: 'file',
                click:function(){
                    let file = path.join(path.resolve(),'news.html')
                    openfile(file)
                }   
            },
            {
                type: 'separator'
            },
            {
                label: 'deletefile',
                click:function(){
                    let file = path.join(path.resolve(),'news.html')
                    deletefile(file)
                }   
            },
            {
                type: 'separator'
            },
            {
                label: 'beep',
                click:function(){
                    // 播放声音
                    shell.beep()
                }   
            }
        ]
    },
]

let m = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(m)

electron的更新迭代貌似还是比较频繁,截止笔者使用的版本是5.0.1,在这个版本中,如果想使用webview标签,我们还需在主进程声明一下,否则将无法加载…

问题描述:electron/electron#18145

// main.js
...
function createWindow(){
  ...
        webPreferences: {
            // 开启node
            nodeIntegration: true,
            // 新增允许使用webview标签
            webviewTag: true
        },
  ...
}


启动:

npm start

shell的所有功能都已经配置在菜单中了,它的使用非常简单!

第十章-系统对话框模块

系统对话框(dialog)
进程:主进程
electron已升级为7.0以上版本,所以的dialog的API都新增了Promise功能,我将用一个最简单的例子演示

// 7.0以下版本
const option = {
    type: 'info',
    title: '信息',
    message: '这是一条信息对话框',
    buttons: ['yes','no']
}
dialog.showMessageBox(option,function (index) {
    alert(index)
})
// 7.0版本 新增promise用法
const option = {
    type: 'info',
    title: '信息',
    message: '这是一条信息对话框',
    buttons: ['yes','no']
}
dialog.showMessageBox(option).then((result) {
    alert(result)
})

Electron为我们提供了桌面端的提示对话框形式,下面让我们共同了解一下

下面是一个完整的代码实例

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
  <div>
      <button class="errBox">错误提示对话框</button>
      <button class="infoBox">信息提示对话框</button>
      <button class="dirBox">打开目录对话框</button>
      <button class="saveBox">保存文件对话框</button>
      <script src="./renderer/dialog.js"></script>
  </div>
</body>
</html>
// dialog.js
// dialog本身是主进程的模块,这里为了方便我通过渲染进程的remote模块调用,原因后面讲
const { dialog,BrowserWindow } = require('electron').remote
const errBox = document.querySelector('.errBox')
const dirBox = document.querySelector('.dirBox')
const infoBox = document.querySelector('.infoBox')
const saveBox = document.querySelector('.saveBox')

errBox.addEventListener('click', function() {
    dialog.showErrorBox('错误提示','这是一条错误信息')
})

infoBox.addEventListener('click', function() {
    const option = {
        type: 'info',
        title: '信息',
        message: '这是一条信息对话框',
        buttons: ['yes','no']
    }
    dialog.showMessageBox(option,function (index) {
        // index是你选择按钮的索引
        alert(index)
    })
})

dirBox.addEventListener('click', function() {
    let windows = BrowserWindow.getFocusedWindow()
    dialog.showOpenDialog(windows,{
        // 允许选择文件及文件夹
        properties: ['openFile','openDirectory']
    },function(filePaths) {
        // 弹出你所选择的文件或文件夹的路径
        alert(filePaths)
    })
})

saveBox.addEventListener('click', function() {
    const options = {
        title: '保存图片',
        // 可以指定可显示文件的数组类型
        filters: [
          { name: 'Images', extensions: ['jpg', 'png', 'gif'] }
        ],
        // 保存文件默认名
        defaultPath: __filename
      }
    dialog.showSaveDialog(options, function (filename) {
        // 保存文件所在路径
        alert(filename)
    })
})

启动:

npm start

dialog模块的使用非常简单,它的参数配置也十分丰富,足够满足我们的需求,一般来说dialog都会放在主进程来使用,通过渲染进程与主进程间的通信我们可以实现复杂的交互。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机械硬盘HDD

硬盘&#xff08;英语&#xff1a;Hard Disk Drive&#xff0c;缩写&#xff1a;HDD&#xff0c;有时为了与固态硬盘相区分称“机械硬盘”或“传统硬盘”&#xff09;是电脑上使用坚硬的旋转盘片为基础的非易失性存储器&#xff0c;它在平整的磁性表面存储和检索数字数据&#…

三维重建之PIFuHD

Fackbook AI 研究出从一张图片生成Mesh模型的算法PIFuHD ​ Paper: https://arxiv.org/pdf/2004.00452.pdf Code: https://github.com/facebookresearch/pifuhd 一&#xff0c;Demo数据预处理 这里面需要先编译pifuhd和lightweight-human-pose-estimation.pytorch&#xf…

Unknown custom element: <el-image>无法使用该组件,升级element-ui版本后项目报错

需求背景&#xff1a; 项目中需要使用图片点击放大&#xff0c;想要使用<el-image>组件&#xff0c;引入后报了下面的错&#xff0c;需要升级element版本&#xff0c;element-ui版本过低&#xff0c;没有该组件。 过程&#xff1a; cnpm i element-ui2.14.1 --save-dev…

代码随想录67——额外题目【动态规划】:5最长回文子串、132分割回文串II、673最长递增子序列的个数

文章目录1.5最长回文子串1.1.题目1.2.解答2.132分割回文串II2.1.题目2.2.解答3.673最长递增子序列的个数3.1.题目3.2.解答1.5最长回文子串 参考&#xff1a;代码随想录&#xff0c;5最长回文子串&#xff1b;力扣题目链接 1.1.题目 1.2.解答 本题和 647.回文子串 差不多是一…

Codeforces Round #574 (Div. 2) C. Basketball Exercise

翻译&#xff1a; 最后&#xff0c;SIS已经开放了一个篮球场&#xff0c;所以Demid决定举办一个篮球训练课程。有2个⋅&#x1d45b;的学生参加了Demid的练习课&#xff0c;他将他们排成两排&#xff0c;大小相同(每排正好有&#x1d45b;人)。学生按从左到右的顺序&#xff0…

【毕业设计】15-基于单片机的交通灯系统设计(原理图+仿真+论文)

【毕业设计】15-基于单片机的交通灯系统设计&#xff08;原理图、仿真、源代码工程答辩论文答辩PPT&#xff09; 文章目录【毕业设计】15-基于单片机的交通灯系统设计&#xff08;原理图、仿真、源代码工程答辩论文答辩PPT&#xff09;任务书设计说明书摘要设计框架架构设计说明…

婴幼儿牛奶蛋白过敏危害多,教你四招早期预防

牛奶蛋白过敏&#xff08;cowsmilkproteinallergy&#xff0c;CMPA&#xff09;这是婴幼儿最常见的食物过敏之一。牛奶蛋白过敏的临床表现CMPA儿童的临床表现多种多样[1]&#xff0c;特别是对严重的牛奶蛋白过敏&#xff0c;会导致拒绝进食、腹泻、呕吐或反流&#xff0c;导致生…

CentOS7安装superset2.0

备注&#xff1a;自己在CentOS7.5下安装superset2.0成功。数据库以本地sqlite为准。Superset是由Python语言编写的Web应用&#xff0c;Superset2.0版本要求Python3.9的环境。 1、安装Miniconda 原因&#xff1a;conda是一个开源的包、环境管理器&#xff0c;可以用于在同一个…

Linux/Windows中创建共享文件夹

Linux/Windows中创建共享文件夹一、虚拟机访问主机的文件夹1、设置虚拟机共享主机的路径2、设置主机目录为共享文件夹【Windows之间共享目录】2.1 设置共享文件夹2.2 访问共享文件夹3、nfs_共享文件夹【linux之间共享目录】一、虚拟机访问主机的文件夹 1、设置虚拟机共享主机的…

计算机内存与外存的区别及使用配合(内存外存区别与搭配;快速缓存;计算机总线结构)

计算机系统结构1. 为什么计算机存储会分为内存和外存呢&#xff1f;2. 关于快速缓存3. 计算机总线结构1. 为什么计算机存储会分为内存和外存呢&#xff1f; 外部储存器断电可以存储数据&#xff0c;但是读写速度相对于cpu来说很慢&#xff0c;而内存虽然读取速度很快但是断电之…

【php环境搭建】php全栈体系(二)

PHP环境搭建 第七章 安装与配置MySQL 一、安装MySQL软件 1. 获取MySQL安装软件 2. 双击安装即可&#xff1a;没有特殊情况的直接下一步就可以完成 3. 选择custom&#xff0c;自定义安装&#xff1a;选择安装路径 3.1 软件安装目录&#xff1a;server/mysql 3.2 数据安装目录…

OVS DPDK VXLAN隧道处理

在学习OVS VXLAN实现之前&#xff0c;我们先回顾一下传统VTEP设备是如何处理VXLAN报文的。如下图所示&#xff1a; vxlan报文进入交换机端口后&#xff0c;根据报文头部信息进行vxlan隧道终结。隧道终结后&#xff0c;根据underlay信息进行overlay映射&#xff0c;得到overlay的…

Spark系列之SparkSubmit提交任务到YARN

title: Spark系列 第十三章 SparkSubmit提交任务到YARN 13.1 SparkSubmit提交的一些参数解释 local 本地单线程 local[K] 本地多线程&#xff08;指定K个内核&#xff09; local[*] 本地多线程&#xff08;指定所有可用内核&#xff09; spark://HOST:PORT 连接到指定的 Spar…

欢聚季报图解:营收5.87亿美元同比降10% 净利提升

雷递网 雷建平 11月29日欢聚集团(NASDAQ: YY)今日发布2022年第三季度财报。财报显示&#xff0c;欢聚集团2022年第三季度营收为5.867亿美元&#xff0c;较上年同期下降10%。欢聚集团2022年第三季度Bigo Live的平均移动MAU为3540万&#xff0c;较上年同期的3100万增长14.2%&…

固态硬盘SSD

固态硬盘或固态驱动器&#xff08;英语&#xff1a;Solid-state drive或Solid-state disk&#xff0c;简称SSD&#xff09;是一种以集成电路制作的电脑存储设备&#xff0c;由于价格及最大存储容量与机械硬盘有巨大差距&#xff0c;固态硬盘无法与机械式硬盘竞争。可以用非易失…

视频转格式用什么工具?mp4格式转换器,好用的视频格式转换器

视频转格式用什么工具&#xff1f;一条视频在不同的手机、软件上&#xff0c;所支持的格式是不同的&#xff0c;可能手机不支持部分不常见的视频格式&#xff0c;需要使用视频格式转换器将它的格式进行修改&#xff0c;就能使它匹配你所需的格式。接下来&#xff0c;小编就带大…

集成电路光刻机精密运动台控制方法

集成电路的持续发展推动了信息技术的进步。步进扫描式光刻机是集成电路装备中 技术难度最高、价格最昂贵的关键设备。其中&#xff0c;精密运动台是光刻机最为核心的子系统之一&#xff0c; 高速、高加速和高精度的要求对控制方法的研究和应用提出了挑战。文中对集成电路光刻机…

C++ Reference: Standard C++ Library reference: Containers: list: list: empty

C官网参考链接&#xff1a;https://cplusplus.com/reference/list/list/empty/ 公有成员函数 <list> std::list::empty C98 bool empty() const; C11 bool empty() const noexcept; 测试容器是否为空 返回列表&#xff08;list&#xff09;容器是否为空&#xff08;即其…

Protobuf用法和实际操作总结

Protobuf介绍 Protobuf(下文称为 PB)是一种常见的数据序列化方式&#xff0c;常常用于后台微服务之间传递数据 protobuf 的类图如下&#xff1a; 类 Descriptor 介绍 类 Descriptor 主要是对 Message 进行描述&#xff0c;包括 message 的名字、所有字段的描述、原始 proto…

SpringCloud_第1章_入门到精通()

SpringCloud_第1章_入门到精通 文章目录SpringCloud_第1章_入门到精通1.认识微服务1.0.学习目标1.1.单体架构1.2.分布式架构1.3.微服务1.4.SpringCloud1.5.总结2.服务拆分和远程调用2.1.服务拆分原则2.2.服务拆分示例2.2.1.导入Sql语句2.2.2.导入demo工程2.3.实现远程调用案例2…