Init
This commit is contained in:
250
07-Other/Node.js/Electron/Electron笔记.md
Normal file
250
07-Other/Node.js/Electron/Electron笔记.md
Normal file
@@ -0,0 +1,250 @@
|
||||
## Electron Quick Start
|
||||
```
|
||||
# 克隆这仓库
|
||||
$ git clone https://github.com/electron/electron-quick-start
|
||||
# 进入仓库
|
||||
$ cd electron-quick-start
|
||||
# 安装依赖库
|
||||
$ npm install
|
||||
# 运行应用
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## 使用IPC进行GUI与原生APP进行通讯
|
||||
在main.js里添加下面的代码,从通道订阅消息:
|
||||
```
|
||||
var ipc = require('ipc');
|
||||
ipc.on('close-main-window', function () {
|
||||
app.quit();
|
||||
});
|
||||
```
|
||||
引入ipc模块后,通过通道订阅消息就变得很简单,on()方法设置订阅的通道名,定义回调函数。
|
||||
|
||||
渲染进程要通过通道发送消息,将下面代码加入index.js:
|
||||
```
|
||||
var ipc = require('ipc');
|
||||
var closeEl = document.querySelector('.close');
|
||||
closeEl.addEventListener('click', function () {
|
||||
ipc.send('close-main-window');
|
||||
});
|
||||
```
|
||||
同样,我们引入ipc模块,给关闭按钮的元素绑定一个click事件。当点击关闭按钮时,通过「close-main-window」通道的send()方法发送消息。
|
||||
|
||||
## 全局快捷键
|
||||
```
|
||||
var globalShortcut = require('global-shortcut');
|
||||
app.on('ready', function() {
|
||||
... // existing code from earlier
|
||||
globalShortcut.register('ctrl+shift+1', function () {
|
||||
mainWindow.webContents.send('global-shortcut', 0);
|
||||
});
|
||||
globalShortcut.register('ctrl+shift+2', function () {
|
||||
mainWindow.webContents.send('global-shortcut', 1);
|
||||
});
|
||||
});
|
||||
```
|
||||
```
|
||||
ipc.on('global-shortcut', function (arg) {
|
||||
var event = new MouseEvent('click');
|
||||
soundButtons[arg].dispatchEvent(event);
|
||||
});
|
||||
```
|
||||
## 保存用户配置
|
||||
使用nconf模块
|
||||
```
|
||||
npm install --save nconf
|
||||
```
|
||||
```
|
||||
|
||||
var nconf = require('nconf').file({file: getUserHome() + '/sound-machine-config.json'});
|
||||
|
||||
function saveSettings(settingKey, settingValue) {
|
||||
nconf.set(settingKey, settingValue);
|
||||
nconf.save();
|
||||
}
|
||||
|
||||
function readSettings(settingKey) {
|
||||
nconf.load();
|
||||
return nconf.get(settingKey);
|
||||
}
|
||||
|
||||
function getUserHome() {
|
||||
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveSettings: saveSettings,
|
||||
readSettings: readSettings
|
||||
};
|
||||
```
|
||||
## 系统托盘
|
||||
```
|
||||
var remote = require('remote');
|
||||
var Tray = remote.require('tray');
|
||||
var Menu = remote.require('menu');
|
||||
var path = require('path');
|
||||
|
||||
var trayIcon = null;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
trayIcon = new Tray(path.join(__dirname, 'img/tray-iconTemplate.png'));
|
||||
}
|
||||
else {
|
||||
trayIcon = new Tray(path.join(__dirname, 'img/tray-icon-alt.png'));
|
||||
}
|
||||
|
||||
var trayMenuTemplate = [
|
||||
{
|
||||
label: 'Sound machine',
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
click: function () {
|
||||
ipc.send('open-settings-window');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click: function () {
|
||||
ipc.send('close-main-window');
|
||||
}
|
||||
}
|
||||
];
|
||||
var trayMenu = Menu.buildFromTemplate(trayMenuTemplate);
|
||||
trayIcon.setContextMenu(trayMenu);
|
||||
```
|
||||
|
||||
## 打包应用
|
||||
>摘自https://www.jianshu.com/p/f134878af30f
|
||||
|
||||
安装electron-package
|
||||
```
|
||||
npm install electron-package --save-dev
|
||||
```
|
||||
添加scrip命令 ,用于打包electron app。
|
||||
```
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-packager . hello_electron --platform=darwin --arch=x64 --ignore=node_modules/electron-*",
|
||||
},
|
||||
```
|
||||
**electron-packager命令格式**
|
||||
```
|
||||
electron-packager 项目目录 app名称 --platform=平台 --arch=架构 --ignore=要忽略的目录或文件
|
||||
arch
|
||||
ia32 , x64 , armv7l , all
|
||||
|
||||
plateform
|
||||
linux , win32 , darwin , mas , all
|
||||
|
||||
OS X (also known as darwin)
|
||||
Mac App Store (also known as mas)
|
||||
```
|
||||
执行命令npm run build,将得到如下结果
|
||||
|
||||
### electron-builder与electron-packager的区别
|
||||
使用electron-builder打包应用是安装包方式,而不想electron-packager打包之后直接是一个可文件夹,交给所有的文件暴露出来。由electron-builder打出的包更为轻量,并且可以打包出不暴露源码的setup安装程序
|
||||
|
||||
## 压缩源码
|
||||
为避免源代码泄露,可对源码进行压缩。
|
||||
|
||||
**安装electron-asar**
|
||||
```
|
||||
npm install electron-asar --save-dev
|
||||
```
|
||||
**添加scrip命令 ,用于压缩源代码。**
|
||||
```
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-packager . hello_electron --platform=darwin --arch=x64 --ignore=node_modules/electron-*",
|
||||
"package":"asar pack hello_electron-darwin-x64/hello_electron.app/Contents/Resources/app hello_electron-darwin-x64/hello_electron.app/Contents/Resources/app.asar"
|
||||
},
|
||||
```
|
||||
**asar 命令格式**
|
||||
|
||||
asar pack <dir> <output>
|
||||
执行npm run package将得到app.asar文件,此时可将app文件删除。
|
||||
|
||||
## Electron启动node.js服务器
|
||||
1.直接在index.html中启动外部的node.js服务器
|
||||
2.将原生的node.js服务器代码使用module.exports = () => {}导出,之后在electron的main.js中直接导入
|
||||
```
|
||||
app.server = require(__dirname + '/app/app')();
|
||||
```
|
||||
|
||||
## 不迁移项目就可以打包双版本的可行方案
|
||||
作者并未提供web开发的支持,但是提供了非常好的web打包支持。
|
||||
只要写好逻辑我们可以不用迁移项目就可以打包桌面项目和web项目。
|
||||
|
||||
process.env.IS_WEB是暴露的一个全局变量,我们可以在渲染进程中获取,项目在electron环境下,返回false。否则为true。于此,我们可以通过设置她的值来达到web dev的效果,也可以处理不同环境的不同逻辑,一些示例:
|
||||

|
||||

|
||||

|
||||
|
||||
## 打开新窗口的“最佳”做法
|
||||
1.使用webview, allowpopups变量控制是否拦截新弹出的窗口
|
||||
|
||||
下面的例子,是webview允许.open/.showModalDialog/.showModelessDialog的例子:
|
||||
|
||||
electron的index.html:(重点是参数allowpopups)
|
||||
```
|
||||
<webview id="foo" src="https://newsn.net/test.html" allowpopups style="width:100%; height:360px;"></webview>
|
||||
```
|
||||
原文:https://newsn.net/say/electron-webview-window-open.html
|
||||
|
||||
2.因为是webpack配置,入口只有index.html ,所以打开新窗口,一般会再配置一个入口。其实还有一种更佳的做法。
|
||||
```
|
||||
>>> 主进程 定义好监听事件
|
||||
ipc.on('newPage', function(e) {
|
||||
const modalPath = process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:9080/#/newPage'
|
||||
: `file://${__dirname}/index.html#newPage`
|
||||
let win = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 724,
|
||||
webPreferences: {
|
||||
webSecurity: false
|
||||
}
|
||||
})
|
||||
win.on('close', function() {
|
||||
win = null
|
||||
})
|
||||
win.loadURL(modalPath)
|
||||
|
||||
})
|
||||
|
||||
>>> router/index.js 定义路由
|
||||
// import 你的新页面 .vue 文件
|
||||
{
|
||||
path: '/newPage',
|
||||
name: 'newPage',
|
||||
component: newPage,
|
||||
}
|
||||
|
||||
》》》配置完成 任意进程调用ipc.send('newPage') 完美解决
|
||||
```
|
||||
3.
|
||||
```
|
||||
document.getElementById("youtube").onclick = function(){
|
||||
|
||||
youtubeWindow = new BrowserWindow ({width: 1000, height:800})
|
||||
|
||||
youtubeWindow.loadURL("https://youtube.com/");
|
||||
|
||||
youtubeWindow.on("close", function(){
|
||||
youtubeWindow = null;
|
||||
})
|
||||
}
|
||||
|
||||
document.getElementById("local-list").onclick = function(){
|
||||
|
||||
localListWindow = new BrowserWindow ({width: 1000, height:800})
|
||||
|
||||
localListWindow.loadURL(`file://${__dirname}/local-list.html`);
|
||||
|
||||
localListWindow.on("close", function(){
|
||||
localListWindow = null;
|
||||
})
|
||||
}
|
||||
```
|
112
07-Other/Node.js/Electron/Vue-cli项目移植Electron-vue的相关经验.md
Normal file
112
07-Other/Node.js/Electron/Vue-cli项目移植Electron-vue的相关经验.md
Normal file
@@ -0,0 +1,112 @@
|
||||
## 移植经验
|
||||
1. 使用npm下载所有在vue-cli项目所使用的模块,比如element-ui、NProgress之类的。
|
||||
2. 将vue-cli项目中src目录下所有文件复制到electron-vue中对应目录。(不要直接覆盖main.js,因为electron-vue中有一些写法不同了,对于这个文件需要自己仔细移植,比如 Vue.use(require('vue-electron'))就与vue-cli不同)
|
||||
3. 运行npm run dev,解决报错问题。(主要是因为路径失效造成的问题)
|
||||
|
||||
## this.$router.push()报错then() undefined
|
||||
这有可能是因为你使用了动态加载路由,也就是在vue-router中使用了vuex,同时夹杂了Electron-vue模板里的东西。在electron-vue模板使用的store/index.js文件中调用了vuex-electron插件,而这些插件貌似会与结构起冲突,把这些东西删除了就好了。
|
||||
```
|
||||
// import { createPersistedState, createSharedMutations } from 'vuex-electron'
|
||||
|
||||
// plugins: [
|
||||
// createPersistedState(),
|
||||
// createSharedMutations()
|
||||
// ],
|
||||
```
|
||||
我使用的动态路由是修改自
|
||||
[PanJiaChen/vue-admin-template](https://note.youdao.com/)。该作者也做了个Electron的项目,同样是用Electron-vue模板,所以相当值得参考。
|
||||
|
||||
## 跨域问题
|
||||
我一开始也很疑惑,为啥electron-vue中为啥没有proxytable。后来发现的确没有必要。如果有,只可能是因为你的需求有问题。
|
||||
|
||||
vue-cli项目是将打包好的js、html等文件一起放到服务端中,之后通过浏览器来浏览。服务端和前端文件是放在一起的。所以为了解决前后端分离的问题,才使用跨域转发的方式来解决(开发模式需要跨,而生成模式就不需要)。而Electron开发与生成都需要跨,毕竟前端和服务端不在一起。所以也就不存在跨域问题,也就不需要ProxyTable了。
|
||||
|
||||
我的解决方法是这样:
|
||||
设置一个config.js作为全局设置文件。在里面设置一个变量用于存储本地服务器ip,之后在使用axios编写get与post接口的文件,将服务器ip变量加到接口地址前就可以了。例如
|
||||
```
|
||||
export const requestLogin = params => {return axios.post(`/login`,params).then(res => res.data); };
|
||||
```
|
||||
=>
|
||||
```
|
||||
//在config.js中,base为127.0.0.1:3000
|
||||
import {base} from '../config';
|
||||
export const requestLogin = params => {return axios.post(`${base}/login`,params).then(res => res.data); };
|
||||
```
|
||||
如果需要导出web版本,则可以使用process.env.IS_WEB来判断是否为web模式。
|
||||
|
||||
## 静态文件与启动本地服务问题
|
||||
之前曾想过在electron里直接导入服务端代码来运行服务器,但后来发现不行。github上代码都是通过electron(Node.js)的child_process来启动外部服务器的。
|
||||
|
||||
这里我使用pm2来启动,这样就不需要在程序关闭时关闭服务器了。(child_process 老是无法关闭服务器进程,不知道为什么)
|
||||
```
|
||||
let exec = require('child_process').exec;
|
||||
let server = exec("pm2 start ./bin/www", {cwd: process.cwd()+'/server'});
|
||||
```
|
||||
静态文件可以放再electron目录下的static文件夹中(之后会打包成asar文件),不过因为本人写的app需要定时更新文件,且文件类型较杂,所以使用本地服务端来管理静态文件。
|
||||
|
||||
于是对于项目中的需要显示图片或者其他文件链接就需要加入本地服务器ip地址,就像上一节那样。
|
||||
|
||||
## 打包后静态文件失效问题
|
||||
__dirname是Electron-vue中在webpack中配置的变量,对应开发模式与
|
||||
产品模式时的electron静态文件目录。但如果你使用了一些webpack中没有设置的文件(比如字体)会出一些问题,请参考这个blog
|
||||
https://blog.csdn.net/aa661020/article/details/79757859
|
||||
|
||||
## 关于自定义协议
|
||||
本人写的app使用了类似QQ的自定义协议来启动自己写的外部程序并传参,在electron中,为了安全的问题,默认把这个功能屏蔽了。electron中应该可以使用protocol来定义协议来解决问题,不过我找到了另一种方法,那就是直接使用child_process的exec,直接用命令行的方式来启动程序。
|
||||
|
||||
直接在渲染进程中(xxx.vue文件)
|
||||
```
|
||||
<script>
|
||||
const exec = require('child_process').exec;
|
||||
export default {
|
||||
methods: {
|
||||
exec(row){
|
||||
//s为自定义协议的字符串
|
||||
let s='xxxxxx';
|
||||
let url="test://"+ s;
|
||||
|
||||
//test为程序名,注意后面有空格,第二个参数中提供程序的运行位置,具体的可以参考node.js文档。
|
||||
exec("test "+url, {cwd: process.cwd()+'/XXXX'});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
不过需要注意,在中文windows系统(没有修改过默认字符集的),浏览器里通过自定义协议来启动程序默认传的是gbk的字符集。而不同系统(win10,win7)字符集是不同的。所以就需要对启动程序进行修改。
|
||||
|
||||
## 关于多条命令启动
|
||||
因为本人写的App需要更新数据库(mysql数据库),所以本人使用mysql的命令进行恢复数据。
|
||||
奇怪的是child_process的exec竟然不支持多条语句。所以这里我就使用了child_process的spawn。
|
||||
|
||||
多条命令之间使用&&间隔,具体的请参照cmd指南(还有别的间隔符,比如| &)
|
||||
|
||||
```
|
||||
let cmd='cd /d C:/Program Files/MySQL/MySQL Server 5.7/bin && mysql -uXXXXX -pXXXXX abcde </managersystem_user.sql && mysql -uXXXXX -pXXXXX abcde </abcde_xxxx.sql';
|
||||
|
||||
cmd+=" && mysql -uXXXXX -pXXXXX abcde < /abcde_routines.sql";
|
||||
|
||||
let child = spawn(cmd, {
|
||||
shell: true
|
||||
});
|
||||
//这里还可以输入新的命令与显示cmd返回内容,具体的请参考node.js文档
|
||||
child.on('exit', ()=> {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
## 关于启动参数
|
||||
```
|
||||
//在主进程中
|
||||
global.sharedObject = {prop1: process.argv}
|
||||
```
|
||||
```
|
||||
//渲染进程中
|
||||
var remote = require('electron').remote,
|
||||
arguments = remote.getGlobal('sharedObject').prop1;
|
||||
|
||||
console.log(arguments);
|
||||
```
|
||||
|
||||
## 最后推荐一下苏南大叔的blog
|
||||
里面有关electron的文章很不错
|
||||
https://newsn.net
|
@@ -0,0 +1,61 @@
|
||||
## 修改electron-packager代码
|
||||
首先确认electron-packager安装位置(全局或者局部),之后进入`node_modules\electron-packager\`路径。使用VSCode搜索`packageForPlatformAndArch`。代码如下:
|
||||
```c#
|
||||
packageForPlatformAndArch (downloadOpts) {
|
||||
return download.downloadElectronZip(downloadOpts)
|
||||
.then(zipPath => {
|
||||
// Create delegated options object with specific platform and arch, for output directory naming
|
||||
const comboOpts = Object.assign({}, this.opts, {
|
||||
arch: downloadOpts.arch,
|
||||
platform: downloadOpts.platform,
|
||||
electronVersion: downloadOpts.version
|
||||
})
|
||||
|
||||
if (!this.useTempDir) {
|
||||
return this.createApp(comboOpts, zipPath)
|
||||
}
|
||||
|
||||
if (common.isPlatformMac(comboOpts.platform)) {
|
||||
/* istanbul ignore else */
|
||||
if (this.canCreateSymlinks === undefined) {
|
||||
return this.testSymlink(comboOpts, zipPath)
|
||||
} else if (!this.canCreateSymlinks) {
|
||||
return this.skipHostPlatformSansSymlinkSupport(comboOpts)
|
||||
}
|
||||
}
|
||||
|
||||
return this.checkOverwrite(comboOpts, zipPath)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
添加逻辑:
|
||||
```
|
||||
let zipFile="electron-v11.2.1-win32-x64.zip";
|
||||
console.log(__dirname);
|
||||
let HasFile=fs.existsSync(zipFile);
|
||||
if(HasFile)
|
||||
{
|
||||
const comboOpts = Object.assign({}, this.opts, {
|
||||
arch: downloadOpts.arch,
|
||||
platform: downloadOpts.platform,
|
||||
electronVersion: downloadOpts.version
|
||||
})
|
||||
|
||||
if (!this.useTempDir) {
|
||||
return this.createApp(comboOpts, zipFile)
|
||||
}
|
||||
|
||||
if (common.isPlatformMac(comboOpts.platform)) {
|
||||
/* istanbul ignore else */
|
||||
if (this.canCreateSymlinks === undefined) {
|
||||
return this.testSymlink(comboOpts, zipFile)
|
||||
} else if (!this.canCreateSymlinks) {
|
||||
return this.skipHostPlatformSansSymlinkSupport(comboOpts)
|
||||
}
|
||||
}
|
||||
|
||||
return this.checkOverwrite(comboOpts, zipFile)
|
||||
}
|
||||
```
|
||||
其中执行路径即为项目根目录,直接将zip放在项目目录中即可。
|
Reference in New Issue
Block a user