之前因为项目需求 需要做一个桌面应用 所以在bilibili上学习了一部分基础知识后 又参考[https://zhuanlan.zhihu.com/p/75764907]这篇博客搭建了一个electron+vue 的桌面价格查询应用,此文章记录一下搭建流程和遇到的一些问题
一.electron基础知识点
electron 我认为就是个浏览器 ,里面显示什么.怎么写还是跟平时的页面怎么写一样,只不过一个是用浏览器直接打开,一个是以桌面应用的方法打开
准备工作
1.cnpm init -y 创建package.json
2. cnpm i electron -D 安装electron
3.新建一个main.js 入口文件 这里写入electron 主进程代码
4.修改package.json 在script里添加
“package”:“electron-packager ./ Helloword --platform=win32 --arch=x64 --icon=favicon.ico --out=./out --asar --app-version=0.0.1 --overwrite --ignore=node_modules” 使用cnpm run package 打包
5.新建一个html 让html显示到electron窗口上
创建electron窗口 开始正式学习
main.js里直接引用的js是主进程 在html中引用的是渲染进程 渲染进程不能直接使用主进程的一些属性 需要用remote
// main.js
var electron = require('electron')
var app = electron.app // 引用app
var globalShortcut = electron.globalShortcut // 全局快捷键
var BrowserWindow = electron.BrowserWindow // 窗口引用
var mainWindow = null // 声明要打开的主窗口
app.on('ready', () => {
// 把窗口设置好
mainWindow = new BrowserWindow({
width: 900, height: 800,
webPreferences: {
nodeIntegration: true,// 让所有的node都能在我们渲染进程中使用
contextIsolation: false,
enableRemoteModule: true // 配置才能用remote
}
})
// 注册全局函数
globalShortcut.register('ctrl+e',()=>{
mainWindow.loadURL('https://jspang.com')
})
// 判断这个快捷键是否添加成功
let isRegister = globalShortcut.isRegistered('ctrl+e')
console.log('-----',isRegister,'-------')
// 打开调试面板 如果修改了Menu 不能在view下面的toggle中打开调试模式的话
// mainWindow.webContents.openDevTools()
// 引入自定义菜单
// require('./main/menu')
// 把网页加进来
mainWindow.loadFile('demo7.html') //加载html页面
// BrowserView
// 在页面中嵌套子页面
/* var BrowserView = electron.BrowserView
var view = new BrowserView()
mainWindow.setBrowserView(view) //view放在主窗口下
view.setBounds({//设置样式 位置
x:0,
y:120,
width:900,
height:600
})
// 页面里放入什么
view.webContents.loadURL('https://www.baidu.com') */
// window.open 子窗口 BrowserWindow // 打开一个窗口
// 关闭窗口时 要把mainwindow设置null 不然内存会越存越多
mainWindow.on('closed', () => {
mainWindow = null
})
})
app.on('will-quit',function(){
// 将要退出的这个应用之前 注销快捷键方法
globalShortcut.unregister('ctrl+e') //取消单个快捷键
// globalShortcut.unregisterAll() //取消所有的快捷键
})
// menu.js
const { Menu, BrowserWindow } = require('electron')
var template = [
{
label: "凤来仪",
submenu: [
{
label: '精品spa',
accelerator:'ctrl+n',
click: () => {
var win = new BrowserWindow({
width: 500,
height: 500,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
}
})
win.loadFile('yellow.html')
win.on('closed', () => {
win = null
})
}
},
{ label: "泰式按摩" }
]
},
{
label: "大浪淘沙",
submenu: [
{ label: '牛奶玫瑰浴' },
{ label: "脚底按摩" }
]
}
]
var m = Menu.buildFromTemplate(template) // 构建一下菜单才能使用
Menu.setApplicationMenu(m) //设置菜单
1.测试文字进来
...
<body>
<button id="btn">测试文字进来</button>
<div id="myTest"></div>
</body>
<script>
var fs = require('fs')
window.onload = function(){
var btn = this.document.querySelector('#btn')
var myTest = this.document.querySelector('#myTest')
btn.onclick = function(){
fs.readFile('test.txt',(err,data) => {
myTest.innerHTML = data
})
}
}
</script>
2.打开新的窗口
...
<body>
<button id="btn">打开新的窗口</button>
<div id="myTest"></div>
</body>
<script>
const btn = this.document.querySelector('#btn')
// 这样可以共用main.js中的BrowserWindow
// main.js里直接引用的js是主进程 在html中引用的是渲染进程 渲染进程不能直接使用主进程的一些属性 需要用remote
const BrowserWindow = require('electron').remote.BrowserWindow
window.onload = function(){
btn.onclick = function(){
newWin = new BrowserWindow({
width:500,
height:500
})
newWin.loadFile('yellow.html')
newWin.on('closed',()=>{
newWin = null
})
}
}
const {remote} = require('electron')
var rightTemplate = [
{label:"粘贴",accelerator:'ctrl+c',},
{label:"复制",accelerator:'ctrl+v',}
]
var m = remote.Menu.buildFromTemplate(rightTemplate)
// 右键菜单
window.addEventListener('contextmenu',(e)=>{
// alert(1)
e.preventDefault()
m.popup({window:remote.getCurrentWindow()})
})
</script>
3.打开子窗口
<body>
<h1>
<a id="aHref" href="https://www.baidu.com">百度</a>
</h1>
<button id="mybtn">打开子窗口</button>
<div id="mytext"></div>
</body>
<script>
var {shell} = require('electron')
var aHref = this.document.querySelector('#aHref')
var mybtn = document.querySelector('#mybtn')
var mytext = document.querySelector('#mytext')
aHref.onclick = function(e){
e.preventDefault() // 默认链接会打开到当前应用里面的
var href = this.getAttribute('href')
// 这样可以在浏览器中打开 在谷歌浏览器中打开
shell.openExternal(href)
}
mybtn.onclick = function(){
window.open('./popup_page.html')
}
window.addEventListener('message',(msg)=>{
// console.log(msg)
mytext.innerHTML = JSON.stringify(msg.data)
})
</script>
...
<body>
<h2>我是弹出的子窗口</h2>
<button id="popbtn">向父窗口传递信息</button>
</body>
<script>
var popbtn = document.querySelector('#popbtn')
popbtn.onclick = (e)=>{
// alert(1)
window.opener.postMessage('我是子窗口传递的信息')
}
</script>
4.打开图片 保存文件 弹出对话框
<body>
<button id="openBtn">打开图片</button>
<button id="saveBtn">保存文件</button>
<button id="messageBtn">弹出对话框</button>
<img style="width:100%" id="images">
<script>
const {dialog} = require('electron').remote
const fs = require('fs')
// 上传文件
const openBtn = document.querySelector('#openBtn')
// 也可以用input file js的方式 这个是electron 自己的方式
openBtn.onclick = function(){
dialog.showOpenDialog({
title:"请选择你喜欢的照片",
defaultPath:'./2.png',//可以选择填写默认照片路径
filters: [{name:'img',extensions:['png']}],
buttonLabel:'打开我喜欢的图片',// 设置图片打开按钮文字
}).then(res => {
console.log(res)
let images = document.querySelector('#images')
images.setAttribute('src',res.filePaths[0])
}).catch(err=>{
console.log(err)
})
}
// 保存文件 相当于新建一个文件 然后新建成功后 可以在里面写入内容
const saveBtn = document.querySelector('#saveBtn')
saveBtn.onclick = function(){
dialog.showSaveDialog({
title:"保存文件"
}).then(res => {
fs.writeFileSync(res.filePath,'jspang.com')
}).catch(err=>{
console.log(err)
})
}
// 弹出对话框
const messageBtn = document.querySelector('#messageBtn')
messageBtn.onclick = function(){
dialog.showMessageBox({
type:"warning",
title:'去不去由你',
message:"是不是要去",
buttons:['我要去','不去了' ]
}).then(res=>{
console.log(res)
// 返回的是buttons的下标
})
}
</script>
5.断网提醒功能
<body>
<h2>JSPANG.com 断网提醒测试</h2>
</body>
<script>
// 可以在F12调试工具上 断网试一下
// online 在线 offline断网
window.addEventListener('online',function(){
// 断网的时候再次连接才会触发
alert('来网啦')
})
window.addEventListener('offline',function(){
alert('断网啦,先行离开一会儿~~')
})
</script>
6.底部消息通知
<body>
<button id="notifyBtn">通知消息</button>
</body>
<script>
var notifyBtn = document.querySelector('#notifyBtn')
var option = {
title:'小二,来订单了,出来接客!',
body:'有大官人翻你牌子啦'
}
notifyBtn.onclick = function(){
new window.Notification(option.title,option)
}
</script>
7.剪切板功能
<body>
<div>
激活码: <span id="code">jasdfdlfjdsklfjkldsfj</span>
<button id='btn'>复制激活码</button>
</div>
<script>
// clipboard 这个在渲染进程和主进程中都能使用
const {clipboard} = require('electron')
var btn = document.querySelector('#btn')
var code = document.querySelector('#code')
btn.onclick = function(){
clipboard.writeText(code.innerHTML)
alert('复制成功')
}
</script>
8.除了使用remote,我们还可以用另一种方式让渲染进程与主进程进行通信
// 主进程 main.js
import { ipcMain} from 'electron'
ipcMain.on('message',function(event,arg){
// arg 是从渲染进程返回来的数据
console.log(arg)
// 这里是传给渲染进程的数据
// event.sender.send 给渲染进程传递数据
fs.rendFile(path.join(__dirname,"/test.txt"),'utf8',(err,data) => {
if(err){
event.sender.send('replay','读取失败')
}else{
event.sender.send('replay',data)
}
})
})
// 渲染进程 html
import { ipcRenderer } from 'electron'
// 这里接收主进程传递过来的参数 这里的on要对应主进程send过来的名字
ipcRenderer.on("replay",function(event,arg){
// 这里的arg 是从主线程请求的数据
console.log('render+'+arg)
})
// 这里的会传递回主进程,这里的第一个参数需要对应主进程里on注册事件的名字一致
ipcRenderer.send('message','传递回去arg')
二.搭建vue+electron项目
创建vue项目
vue create electron-vue-demo
自定义安装
自动安装electron
vue add electron-builder
在安装过程中可能会因为网络问题卡着 我们可以强制结束掉然后再重新执行
安装完后会自动在src目录下生成background.js(electron代码)并修改package.json
修改package.json
在package.json里添加
“main”: “background.js”,
在scripts里添加
“electron:build”: “vue-cli-service electron:build”,
“electron:serve”: “vue-cli-service electron:serve”,
“postinstall”: “electron-builder install-app-deps”,
“postuninstall”: “electron-builder install-app-deps”
devDependencies里添加
“electron”: “^5.0.6”,
“vue-cli-plugin-electron-builder”: “^1.3.5”,
在安装一下依赖包
cnpm install
编译并启动APP
npm run electron:serve
编译打包
npm run electron:build
vue 单独打包
npm run build
修改electron配置文件
electron配置文件和vue的配置文件放在一起的 都是在vue.config.js里配置
也可以在package.json下面添加一个build:{}配置
在vue.config.js里添加配置 更多其他配置可以到官网上查询
pluginOptions: {
electronBuilder: {
builderOptions: {
"appId": "com.example.app",
"productName": "Price-look-up",//项目名,也是生成的安装文件名,即aDemo.exe
"copyright": "Copyright © 2021",//版权信息
"directories": {
"output": "./dist_out"//输出文件路径
},
"extraResources": {
"from": "./public/config.js", // 把config.js文件复制到resources文件夹下面 让它不被打包进去
"to": "./config.js"
},
"win": {//win相关配置
"icon": "./favicon.ico",//图标,当前图标在根目录下,注意这里有两个坑
"target": [
{
"target": "nsis",//利用nsis制作安装程序
"arch": [
"x64"//64位
]
}
]
},
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./favicon.ico",// 安装图标
"uninstallerIcon": "./favicon.ico",//卸载图标
"installerHeaderIcon": "./favicon.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "Price-look-up", // 图标名称
}
}
}
}
在background.js可以修改webPreFerences配置 自定义头部 如
...
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 1000,
height: 800,
// frame: false,// 取消默认头部 自定义头部
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
// nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegration: true, // 在渲染模板上可以使用node
contextIsolation: false, // 以前electron版本contextIsolation默认为false 现在已经默认为true了 所以要想用node还得改contextIsolation为false
enableRemoteModule: true,// 可以使用remote
},
icon: `${__static}/favicon.ico`
})
// 注册全局快捷键 Ctrl+ F12打开开发工具调试
globalShortcut.register('ctrl+F12', () => {
win.webContents.openDevTools()
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
createMenu()
}
...
遇到的问题:
1.在渲染模板上使用require(electron)显示 __dirname is undefined 或者是 fs.existsSync is not a function
解决办法:
把
const {getCurrentWindow} = require('electron').remote
改为
const {getCurrentWindow} = window.require('electron').remote
webpack会自动编译js文件 改为window.require时 webpack不会编译 会等到node去编译
这个用浏览器打开 还是会提示window.require is not a function错误, 但是用electron桌面应用的方式打开就会显示正常
window.require is not a function
at eval (Header_menu.vue?a063:19)
at Module…/node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/views/Header_menu.vue?vue&type=script&lang=js
2.之前在运行npm run electron:build打包过程中经常失败,查了一下,大概率是因为网络问题,下载electron打包所需的一些包下载不下来,我们可以手动下载下来再打包,也可以改为淘宝镜像打包,我改为淘宝镜像打包后一下就打包成功了