我正在参加跨端技术专题征文活动,详情查看:juejin.cn/post/710123…
源码已开源,gitee开源地址:https://gitee.com/ihope_top/ten-time
前言
这个文章我也是边实践边写的,所以可能并不是特别的准确,大家仅供参考哈,文章主要描述electron的使用知识点,关于vue项目部分的介绍基本一笔带过,不会详细说,由于时间仓促(主要是征文快结束了),所以很多内容还来不及写和优化,大家按照文章教程操作过程如有任何问题欢迎贴在评论区一起探讨,如果有任何的优化建议也欢迎随时提出,我也会持续对文章内容进行更新和优化。虽然内容简陋,但也包含了我摸索很久找到的一些解决方案,建议有需求的朋友进行收藏
初始化
本文采用的方案为使用vue-cli-plugin-electron-builder
插件直接构建
首先使用vue-cli初始化一个项目
代码语言:javascript复制vue create project-name
这一步就是常规的新建vue项目,就不多讲了。
然后是进入新建的vue项目里面引入electron
代码语言:javascript复制cd project-name
vue add vue-cli-plugin-electron-builder
之后会让我们选择版本,插件提供的最新版本为13,我们可以在安装完成后手动安装最新版本
出现这样的画面就说明我们已经安装好了,现在我们手动升级一下electron的版本
代码语言:javascript复制yarn add electron --save-dev
安裝完成之后就可以启动项目了
代码语言:javascript复制yarn run electron:serve
之后会出现这个画面,这个主要是因为vue-devtools插件需要从外网下载,如果需要的话可以科学上网一下,如果你平时开发vue就用不到vue-devtools,那直接忽略就好了,稍等数秒,就可以看到我们的项目启动成功了。
适配主题
上面看到的是vue预置的启动画面,我们先把无用的东西都给删了,就留下一个helo world。
可以看到,画面是黑的,字体也是黑的,就导致我们看不清,这是因为我电脑主题选择的是暗黑模式,应用自动显示的黑色背景,如果你不想对系统主题做适配的话,直接设置白色背景即可,这里我们选择对系统主题进行适配。
对于系统主题的获取,我们可以使用媒体查询prefers-color-scheme
,具体用法如下
@media (prefers-color-scheme: dark) {
body {
color: #fff;
background: #000;
}
}
@media (prefers-color-scheme: light) {
body {
color: #000;
background: #fff;
}
}
可以看到暗黑模式下字体颜色已经变成了白色。
我们在把应用模式切换到亮
可以看到页面页自动显示了对应的颜色。
但是如果这样子的话,我们就需要在每个页面都写两份css样式,如果后期我们增加其他主题的话,就需要写多份样式,这样肯定是不行的,这时候有的同学就想到了,我们使用scss变量不就可以了,这个思路很对,但是在这里行不通,因为这个媒体查询不支持scss变量。
代码语言:javascript复制@media (prefers-color-scheme: dark) {
$--color-1: #fff;
}
@media (prefers-color-scheme: light) {
$--color-1: #000;
}
body {
color: $--color-1;
}
可以看到,运行直接就报错了,那我们还有没有别的方法?当然有,那就是使用css变量
代码语言:javascript复制/* theme-dark.scss */
@mixin dark {
--text-color-1: #fff;
--bg-color-1: #000;
}
代码语言:javascript复制/* theme-light.scss */
@mixin light {
--text-color-1: #000;
--bg-color-1: #fff;
}
代码语言:javascript复制@media (prefers-color-scheme: dark) {
@import './assets/scss/theme-dark.scss';
:root {
@include dark()
}
}
@media (prefers-color-scheme: light) {
@import './assets/scss/theme-light.scss';
:root {
@include light()
}
}
body {
color: var(--text-color-1);
background: var(--bg-color-1);
}
这样就可以使用变量的方式控制不同的主题显示了。
开发
基础搭建好了之后,我们就来开发主要功能及界面了。
首先分析一下我们都需要几个页面,由于这个就是个小demo,我们就实现最基础的功能,所以大概就是一个设置页面,一个倒计时管理页面,一个桌面上的小部件。所以总共就3个页面。
管理页面开发
为了让懒进行到底,我们先安装一下element的ui框架,这里我们采用的是按需加载的方式,大家可以参考官网教程,这里就不重复了。
页面方面,由于时间紧张,我们也按最简单的来,开发出一个大概的功能结构,后面会进行功能扩展和优化。
本地化存储
这个地方为了可以让数据长久保存,我选择了Node.js嵌入式数据库Nedb,你可以把他理解成一个简化版的MongoDB。由于nedb不支持promise调用,我们选择使用nedb-promises(后简称nedb),直接安装即可
代码语言:javascript复制yarn add nedb-promises
由于nedb需要借助node的能力,所以我们需要开启electron的node支持
代码语言:javascript复制const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
pluginOptions: {
electronBuilder: {
nodeIntegration: true
}
}
})
使用方法也很简单,这里我们先对nedb进行一个简单的封装
代码语言:javascript复制// datastore.js
import Vue from 'vue'
import Datastore from 'nedb-promises'
const { app } = require('@electron/remote')
const basePath = app.getPath('userData')
// 创建一个表
const db = {
todoItem: new Datastore({
autoload: true,
filename: basePath '/nedb/todo-item'
})
}
// 挂载到vue上
Vue.prototype.$db = db
然后在main.js进行引入
代码语言:javascript复制//main.js
import './plugins/datastore.js'
注意,这里还使用了electron的remote模块,需要先进行安装
代码语言:javascript复制yarn add @electron/remote
之后在background.js加入以下代码
代码语言:javascript复制require('@electron/remote/main').initialize()
代码语言:javascript复制require('@electron/remote/main').enable(win.webContents)
之后就可以使用了,使用也很简单
代码语言:javascript复制await datastore.find(...)
.sort(...)
.limit(...)
具体使用方法可参考官方文档https://github.com/bajankristof/nedb-promises/blob/master/docs.md
也可以阅读文章源代码查看更多用法
桌面悬浮窗(创建新窗口)
接下来我们需要一个在桌面常驻的日期倒计时,那么第一步,我们就需要先创建一个新窗口
创建新窗口
新窗口需要几个特点
- 全屏,因为我们需要让主要内容可以拖动到屏幕任意地方,并且放大缩小,然后配置项还可以展示(由于时间问题,本期暂不实现)
- 透明,因为我们不能挡住桌面图标
- 透明区域可以穿透点击,因为我们需要可以进行正常的桌面操作。
- 不会在下方出现缩略图标(如下)
- win d时候窗口不能隐藏
具体代码如下,实现需求的关键代码都给了注释
代码语言:javascript复制// background.js
// 创建桌面悬浮窗口
const winURL = process.env.NODE_ENV === 'development'
? 'http://localhost:8080'
: 'app://./index.html'
let desktopWindow = null
function createDesktopWindow () {
desktopWindow = new BrowserWindow({
width: 500,
height: 500,
frame: false, // 是否显示边缘框
fullscreen: true, // 是否全屏显示
transparent: true, // 是否透明
resizable: false, // 是否可以改变窗口大小
type: 'toolbar', // 设置窗口类型为工具窗口,则不会在任务栏出现缩略图
webPreferences: {
nodeIntegration: true,
// 官网似乎说是默认false,但是这里必须设置contextIsolation
contextIsolation: false
}
})
desktopWindow.loadURL(winURL '#/desktop')
desktopWindow.setIgnoreMouseEvents(true) // 设置窗口不接收鼠标事件
desktopWindow.on('closed', () => {
desktopWindow = null
})
require('@electron/remote/main').enable(desktopWindow.webContents)
}
app.on('ready', async () => {
/**其他代码*/
createDesktopWindow()
}}
我们还要再vue的路由里加上对应的页面
代码语言:javascript复制// router.js
{
path: '/desktop',
name: 'desktop',
component: () => import('../views/DesktopView.vue')
}
注意,在这里我们还需要把router的路由模式改为hash,否则打包后会遇到白屏的情况
代码语言:javascript复制const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
然后在对应的目录创建对应的vue文件就可以了。 此时可能看到窗口不是透明的,这是因为我们最开始演示适配主题的时候,给body设置了默认颜色,直接去掉就行了。
由于这篇文章主要是走流程,所以咱们这里不做的特别完善,比如拖动啊,手动固定啊之类的暂时都没有,主要是交大家一个思路,后续有时间也会对软件进行完善。
这里我们再采用一个最简单的方式去实现时间显示,就是每次启动的时候去获取最近的一个日期进行倒计时,当前倒计时结束,再去获取下一个。代码就不贴了,这里就说一下实现思路,感兴趣可以直接查看源码。后续有时间也会进行优化。
暂时的实现成果就这样
注意,目前发现一个问题,当点击在windows上使用win d显示桌面时,倒计时也会消失,目前尚未找到完美的解决办法,后续也会继续探索可能的解决方案。也尝试了窗口置顶,但在测试过程中有时候有效,有时候无效,效果不稳定,后续也会持续进行测试
主进程与渲染进程通信
实现上面的效果时需要考虑一个问题,就是当数据更新的时候,桌面的倒计时也要跟着更新,尝试了很多方法都无效,比如使用bus、或者定时请求nedb中最新的数据(可能存在缓存的情况)都无效,后来只能每次修改都销毁桌面窗口,然后重新打开。那么我们怎么实现这一功能呢?
首先我们需要弄明白一个概念,就是主进程和渲染进程,这一块我也理解的不太深,就简单讲一下,大家可以自行学习一下。
我们项目里有一个background.js
,这个是electron的入口文件,我们可以把它就理解为主进程,而除了这个文件外的其他页面里写的方法,我们就把它当成渲染进程。我们对于窗口的操作最好都放在主进程中进行,比如我们的倒计时窗口就是在主进程创建的,那么我们如何在vue文件(渲染进程)告诉主进程我们要重启倒计时窗口呢,这时候我们就需要用到ipc进行进程间的通信,使用方法如下
// vue文件
// 引入
import { ipcRenderer as ipc } from 'electron'
// 使用
ipc.send('desktopRestar')
我们使用ipc的send方法即可触发相应的事件,然后我们需要在主进程进行事件接收
代码语言:javascript复制// background.js
import { ipcMain} from 'electron' // 通常页面初始化时已引入本模块,无需重复引入
// 使用
ipcMain.on('desktopRestar', (e) => {
desktopWindow.destroy() // 销毁倒计时窗口
createDesktopWindow() //重新调用创建窗口
})
到这里其实最简单的一个应用已经完成了,我们可以实现事件的添加、编辑与删除,并且还会在桌面展示距离最近一个事件的倒计时。
修改图标
有个细节我们一直没有修改,就是应用的图标一直是用的默认的,我们需要改成自己的。
这个地方我直接用的png就行,用ico文件反而在打包后显示不出来,还没弄清楚怎么回事
注意看,第二个红框里的__static可能会报错,忽略即可,这个是electron的全局变量。
设置任务栏图标并增加退出菜单
现在还有一个问题就是我们的应用在右下角没有图标,这样我们点击右上角的时候只是暂时关闭窗口,其实没有退出,而我们也没有办法让它再显示,所以我们需要在右下角可以重新显示窗口并且退出。
这个功能我们需要借助electron的tray模块和Menu模块,所以我们需要先进行引入
代码语言:javascript复制import { Tray, Menu } from 'electron'
之后就是在app准备好之后设置菜单
代码语言:javascript复制app.on('ready', async () => {
/**省略已有代码*/
// 设置图标及菜单
const tray = new Tray(path.join(__static, './favicon.png')) // sets tray icon image
const contextMenu = Menu.buildFromTemplate([ // define menu items
{
label: '退出',
click: () => app.quit()
}
])
tray.on('click', () => {
if (mainWindow) {
mainWindow.show()
}
})
tray.setContextMenu(contextMenu)
})
现在右下角图标就出现了,也可以正常退出了
打包
我们直接执行下面的打包命令即可
代码语言:javascript复制yarn run electron:build
打包之后项目根目录会出现dist_electron目录,里面会有一个可执行的exe文件,双击即可安装。如果是mac的话应该是mac的可执行文件,可惜我没有。
禁止多开
安装完成之后,会发现重复打开竟然会出现多个窗口,这怎么能行,我们需要禁止多开,官方也提供给了我们方法。(使用此方法,如果该软件正在运行的话,则无法重新打开,所以你如果一直打开失败的话,不妨去任务管理器看看该软件是否已经正在运行)
代码语言:javascript复制// background.js
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
}
更新日志
6.23 更新
新建窗口时的加载路径错误,造成打包后无法访问同一个nedb数据库
代码语言:javascript复制const winURL = process.env.NODE_ENV === 'development'
? 'http://localhost:8080'
: path.join('file://', __dirname, '/index.html')
改为
代码语言:javascript复制const winURL = process.env.NODE_ENV === 'development'
? 'http://localhost:8080'
: 'app://./index.html'
由于偶然会出现桌面倒计时不出现的情况,所以增加临时解决办法,在主页面增加显示倒计时按钮,后期会进行优化
尚未解决问题
打包后element图标字体丢失的问题 打包后桌面窗口不展示倒计时的问题(可能是无法访问nedb数据库,待排查)
结束语
这样一个简单的不完善的项目就做好了,当然这篇文章的目的也不是为了做一个完美的项目,只是为了把一些常用的知识点介绍给大家而已。
由于完整的项目内容确实太多,所以这里省略了很多的内容,后期也会逐渐的补全,谢谢理解。
顺便推荐一下我写的另一篇文章,使用js写一个跑酷游戏,欢迎体验 https://cloud.tencent.com/developer/article/2078695