如何初始化项目在这里不再赘述,请看我的掘金处女作 基于Electron+vue的跨平台实践初探
此处假设你已经读完我的初探文章或本身已具备初始化electron项目的能力
托盘如何生成?如何设置托盘标题和通知
先看官方文档
let tray
app.whenReady().then(() => {
const icon = nativeImage.createFromPath('path/to/asset.png')
tray = new Tray(icon)
// 注意: 你的 contextMenu, Tooltip 和 Title 代码需要写在这里!
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', checked: true },
{ label: 'Item4', type: 'radio' }
])
tray.setContextMenu(contextMenu)
})
// 鼠标移上去的横幅
tray.setToolTip('This is my application')
// 这个玩意儿不知道是哪块
tray.setTitle('This is my title')
技术要点
- 注意看托盘的变量 tray 声明在了顶层作用域内,这样做的原因是 如果声明在函数作用域内,因为electron的垃圾收集机制,会被回收掉,表现效果为 托盘图标出现在任务栏,然后很快就消失了。
- 托盘图标是用nativeImage模块创建的。
- 托盘图标的图片文件路径: 可以使用内置变量
__static
如果是按我的教程进行的初始化,__static
对应的是public
文件夹
通知
通知分为两种 一种是electron提供的模块 Notification
一种是HTML5的notificationAPI
HTML5的文档很多了,不再赘述,这里主要讲electron提供的 Notification
模块
先看我代码内的实现
const notify = new Notification({
icon,
title: `您有来自${options.from_id}的新消息通知`,
subtitle: '苍穹·IM',
body: body
})
notify.show()
notify.on('click', () => {
if (timer) {
clearInterval(timer)
timer = null
const icon = nativeImage.createFromPath(__static + '/app.png')
tray.setImage(icon)
}
win.setAlwaysOnTop(true)
win.restore()
win.setAlwaysOnTop(false)
})
有关timer那部分代码可以忽略,我们下一部分再讲。这里重点关注notify实例点击事件内的两行代码
// 使用该方法将窗口置顶
win.setAlwaysOnTop(true) //避免窗口被最小化的情况,恢复至原状 win.restore() //使用该方法将窗口取消总是置顶 win.setAlwaysOnTop(false)
为什么这样设计呢?因为win.show()
或者win.focus()
均不能完成将窗口置于顶层的重任。也就无法实现点击消息弹出窗口的效果
进程间通信
低版本的electron默认是可以在渲染进程内直接通过es2015模块引入ipcRender
的,高版本则默认关闭了该选项,请自行查看官方文档描述
文档1 上下文隔离
文档2 进程间通信
看完文档后也就基本明白了,为什么进程间通信需要设计为通过预加载脚本的方式声明方法了,此处注意的点
- preloadjs引入的时机,官方文档是在
new BrowserWindow
的时候,在传入的option
内的webPreferences
内通过preload
属性 声明文件路径, 此处依然可以借助__static
进行引入,为什么不在index.html内直接引入呢? 因为preload内的contextBridge
需要在创建窗口完成后才会存在。
消息音效与任务栏图标闪烁
消息音效很简单,app.vue
内写一个display:none
的 audio
标签
// template
<video :src="src" id="beep" style="display:none"></video>
// script
src: 'http://localhost:56566/video?name=beep.mp3'
// 此处音效地址在一节进行说明
将audio实例注册在vuex上
state: {
...
beep: null,
...
},
getters: {
...
beep: state => state.beep,
...
},
mutations: {
...
PLAY_BEEP (state) {
if (state.beep) {
state.beep.play()
}
},
SET_BEEP (state, beep) {
state.beep = beep
},
...
}
this.SET_BEEP(document.getElementById('beep'))
在收到新消息时调用PLAY_BEEP()
即可
任务栏图标闪烁,本质上是两个托盘图片,通过setInterval
不断切换实现的【还记得上面的timer吗?】
// background.js 主进程内
let timer
// 托盘这里需要注册点击事件,点击将窗口还原,此时需要判断窗口是否为闪烁状态,如果是,清除定时器,设置托盘图标
tray.on('click', () => {
if (timer) {
clearInterval(timer)
timer = null
const icon = nativeImage.createFromPath(__static + '/app.png')
tray.setImage(icon)
}
if (win.isMinimized()) {
win.restore()
win.focus()
} else {
win.minimize()
}
})
// 主进程注册消息事件
ipcMain.on('newMsg', (event, options) => {
win.flashFrame(true)
// 实现托盘图标闪烁
const icon = nativeImage.createFromPath(__static + '/app.png')
const icon2 = nativeImage.createFromPath(__static + '/transparent.png')
let flag = true
timer = setInterval(() => {
if (flag) {
tray.setImage(icon2)
flag = false
} else {
tray.setImage(icon)
flag = true
}
}, 400)
let body = options.msg_body.text
if (options.msg_type === 'image') {
body = '[图片]'
} else if (options.msg_type === 'file') {
body = '[文件]'
}
const notify = new Notification({
icon,
title: `您有来自${options.from_id}的新消息通知`,
subtitle: '苍穹·IM',
body: body
})
notify.show()
notify.on('click', () => {
// 点击消息打开弹窗,设置托盘图标
if (timer) {
clearInterval(timer)
timer = null
const icon = nativeImage.createFromPath(__static + '/app.png')
tray.setImage(icon)
}
win.setAlwaysOnTop(true)
win.restore()
win.setAlwaysOnTop(false)
})
})
托盘图标闪烁与音效是之前章节的有机结合。按部就班即可实现
css内背景图片路径与渲染进程内资源路径开发环境和打包后一致化
这里是为了解决开发环境可以看到图片,打包后图片路径找不到的问题
开发环境是通过dev-server提供的http协议的localhost地址 打包后是自定义的app协议,这个是问题出现的关键原因
关于css内的静态资源,vue-cli官方文档有详细描述,这里不再赘述,上链接自行查看
此处我查阅了大量文档,包括修改css引用的publicPath 等,均不生效,如果有人有过成功经验,可以评论区留言探讨
我的解决方案: 在主进程通过引入的方式,跑一个简易的文件服务器,来代理项目内资源的访问
// 基于express
// background.js
import './express/index'
// express/index
const express = require('express')
const app = express()
const port = 56566
const path = require('path')
app.get('/img', (req, res) => {
if (req.query.name) {
res.sendFile(path.join(__static, 'static/img/' + req.query.name))
} else {
res.sendFile(path.join(__static, 'static/img/default.png'))
}
})
app.get('/video', (req, res) => {
if (req.query.name) {
res.sendFile(path.join(__static, 'static/video/' + req.query.name))
}
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
// css
background: url('http://localhost:56566/img?name=login_bg.jpg');
这样就实现了开发与打包的一致,唯一的缺点就是服务端口被占用还没做处理,毕竟是小概率事件
多图预警
登录页是时空隧道穿梭特效,gif图就不放了
这是我写过的最长的一篇了。。如果有不同意见或者我写的有不清楚的地方,欢迎评论区进行留言探讨。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。