基于vue3+electron11实现QQ登录切换|自定义导航栏|托盘|打包
上一篇有给大家分享过使用vue3和electron快速搭建项目、创建多窗口/父子modal窗口的一些方法。今天继续给大家分享一些vue3.x+electron11项目开发中的一些知识点/踩坑记录,希望对你有帮助~~


1、vue3+electron实现QQ登录界面
<template>
<div>
<div class="ntMain__cont flex1 flexbox flex-col">
<div class="nt__lgregWrapper flex1 flexbox flex-col vui__drag">
<NavBar title=" " transparent fixed>
<template #wbtn>
<a class="wbtn" title="设置"><i class="iconfont icon-shezhi1"></i></a>
</template>
</NavBar> <template v-if="!isShowQrcode">
<div class="nt__lgregBox flex1">
<div class="banner">
<h2 class="tit">Vue3-Electron</h2>
<img class="bg" src="@assets/img/skin/bg-banner.png" />
</div>
<div class="slogan">
<div class="avatar"><img src="@assets/logo-white.png" /></div>
</div>
<div class="forms">
<form @submit.prevent="handleSubmit">
<ul class="clearfix">
<li class="flexbox flex-alignc"><input class="iptxt flex1" type="text" v-model="formObj.tel" placeholder="请输入手机号" autocomplete="off" maxLength="11" /><em class="borLine"></em></li>
<li class="flexbox flex-alignc"><input class="iptxt flex1" type="password" v-model="formObj.pwd" placeholder="请输入密码" autocomplete="off" /><em class="borLine"></em></li>
</ul> <div class="btns"><button class="vui__btn vui__btn-primary btn__submit" type="submit">登录</button></div>
<div class="lgregLink align-c clearfix">
<router-link class="navigator" to="#">忘记密码</router-link>
<router-link class="navigator" to="/register">注册账号</router-link>
</div>
</form>
</div>
</div>
<div class="nt__lgregFoot vui__nodrag" @click="handleShowQR">
<i class="iconfont icon-saoma"></i>
</div>
</template>
<template v-else>
<div class="nt__lgregBox flex1">
<div class="banner">
<h2 class="tit">Vue3-Electron</h2>
<img class="bg" src="@assets/img/skin/bg-banner.png" />
</div>
<div class="slogan">
<div class="qrcode"><img src="@assets/img/placeholder/wx-qrcode.jpg" /></div>
</div>
<div class="forms">
<div style="text-align:center;margin:20px 0 25px;">使用手机扫一扫快速登录。</div>
<div class="btns"><button class="vui__btn vui__btn-primary btn__submit" @click="handleShowQR">返回</button></div>
</div>
</div>
</template>
</div>
</div>
</div>
</template> <script>
import { ref, reactive, inject } from 'vue'
import { useStore } from 'vuex'
import { createWin } from '@module/actions' export default {
components: {},
setup() {
const store = useStore() const v3layer = inject('v3layer') const utils = inject('utils') const isShowQrcode = ref(false) const formObj = reactive({}) const handleShowQR = () => {
isShowQrcode.value = !isShowQrcode.value
} const VTips = (content) => {
v3layer({content: content, time: 2})
} const handleSubmit = () => {
if(!formObj.tel){
VTips('手机号不能为空!')
}else if(!utils.checkTel(formObj.tel)){
VTips('手机号格式不正确!')
}else if(!formObj.pwd){
VTips('密码不能为空!')
}else{
store.commit('SET_ISLOGIN', true);
store.commit('SET_TOKEN', utils.setToken());
store.commit('SET_USER', formObj.tel); // ...
}
} return {
isShowQrcode,
handleShowQR,
formObj,
handleSubmit
}
}
}
</script>
全局路由钩子判断登录状态,没有登录就跳转到上面的登录页面。
// 全局钩子拦截登录状态
router.beforeEach((to, from, next) => {
const hasLogined = store.state.isLogin // 判断当前路由地址是否需要登录权限
if(to.meta.requireAuth) {
if(hasLogined) {
next()
}else {
// 跳转登录页面
loginWin()
}
}else {
next()
}
})
/**
* @desc 登录窗口
*/
export function loginWin() {
createWin({
isMainWin: true,
title: '登录',
route: '/login',
width: 430,
height: 330,
resize: false,
alwaysOnTop: true,
})
}
注意1:当自定义可拖拽区域,必须是兄弟节点或是父子节点,如果是通过position:absolute/fixed定位,则定位的那个区域设置 -webkit-app-region: no-drag 会无效/无法点击。这时需要把定位的元素放在可拖拽节点的子节点里面。

如上图:如果把NavBar组件放在箭头位置,设置/最小化/关闭按钮会无法点击。
<script>
import { getCurrentInstance } from 'vue' export default {
setup() {
const { ctx } = getCurrentInstance() ctx.$store.commit(...)
ctx.$router.push(...)
}
}
</script>
注意2:最好不要使用上面的getCurrentInstance来获取上下文操作,打包的时候会报错+报错+报错。
2、vue3+electron实现无边框窗体自定义导航条
创建窗体的时候,设置 frame: false 窗口会无顶部栏。这时就需要自定义拖拽区域和最小/大化及关闭按钮了。

新建navbar.vue和winbar.vue两个模板,分别是导航栏/右上角按钮组。
- navbar模板
<template>
<div class="nt__navbar" :class="{'fixed': fixed, 'transparent fixed': transparent}">
<div class="nt__navbar-wrap flexbox flex-alignc vui__drag" :style="{'background': bgcolor, 'color': color, 'z-index': zIndex}">
<!-- 标题 -->
<div class="nt__navbar-title" :class="{'center': center}">
<template v-if="$slots.title"><slot name="title" /></template>
<template v-else>{{title || winCfg.window.title}}</template>
</div>
</div>
<WinBar :minimizable="minimizable" :maximizable="maximizable" :closable="closable">
<slot name="wbtn" />
</WinBar>
</div>
</template> <script>
import { winCfg } from '@module/actions' export default {
props: {
// 标题
title: { type: String, default: '' },
// 标题颜色
color: { type: String, default: '#fff' },
// 背景颜色
bgcolor: String,
// 标题是否居中
center: { type: [Boolean, String], default: false },
// 是否固定
fixed: { type: [Boolean, String], default: false },
// 背景透明
transparent: { type: [Boolean, String], default: false },
// 设置层级
zIndex: { type: [Number, String], default: '2021' }, /**
* WinBar组件参数
*/
// 窗口是否可以最小化
minimizable: { type: [Boolean, String], default: true },
// 窗口是否可以最大化
maximizable: { type: [Boolean, String], default: true },
// 窗口是否可以关闭
closable: { type: [Boolean, String], default: true },
},
setup() {
return {
winCfg,
}
}
}
</script>
支持自定义标题/居中、颜色/背景色、是否固定、透明背景等功能。

<NavBar bgcolor="#00d2ff" minimizable="false">
<template #title><i class="iconfont icon-about"></i> 关于</template>
</NavBar>

<NavBar bgcolor="#15ff95" color="#f00" center>
<template #title><i class="iconfont icon-huanfu"></i> 个性装扮</template>
<template #wbtn>
<a class="wbtn" title="我的装扮"><i class="iconfont icon-tabbar3"></i></a>
</template>
</NavBar>

<NavBar :bgcolor="headerBg" transparent>
<template #title><i class="iconfont icon-pyq"></i> 朋友圈</template>
<template #wbtn>
<a class="wbtn" title="更换封面"><i class="iconfont icon-dianzan"></i></a>
<a class="wbtn" title="发布" @click="isShowPublish=true"><i class="iconfont icon-paizhao"></i></a>
</template>
</NavBar>
- winbar.vue模板
<template>
<div class="vui__winbtn flexbox flex-alignc">
<div class="vui__winbtn-groups" :style="{'color': color}">
<slot />
<a v-if="JSON.parse(minimizable)" class="wbtn" title="最小化" @click="handleWinMin"><i class="iconfont icon-min"></i></a>
<a v-if="JSON.parse(maximizable)&&winCfg.window.resize" class="wbtn" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinMax2Min"><i class="iconfont" :class="hasMaximized ? 'icon-restore' : 'icon-max'"></i></a>
<a v-if="JSON.parse(closable)" class="wbtn close" title="关闭" @click="handleWinClose"><i class="iconfont icon-quit"></i></a>
</div>
</div>
</template> <script>
import { remote } from 'electron'
import { onMounted, reactive, inject, toRefs } from 'vue'
import { useStore } from 'vuex'
import { winCfg, setWin } from '@module/actions' export default {
props: {
color: { type: String, default: '#fff' },
// 窗口是否可以最小化
minimizable: { type: [Boolean, String], default: true },
// 窗口是否可以最大化
maximizable: { type: [Boolean, String], default: true },
// 窗口是否可以关闭
closable: { type: [Boolean, String], default: true },
},
setup() {
let win = remote.getCurrentWindow() const store = useStore() const v3layer = inject('v3layer') const data = reactive({
hasMaximized: false
}) onMounted(() => {
if(win.isMaximized()) {
data.hasMaximized = true
}
win.on('maximize', () => {
data.hasMaximized = true
})
win.on('unmaximize', () => {
data.hasMaximized = false
})
}) // 最小化
const handleWinMin = () => {
setWin('min', winCfg.window.id)
}
// 最大化/还原
const handleWinMax2Min = () => {
setWin('max2min', winCfg.window.id)
}
// 关闭
const handleWinClose = () => {
setWin('close', winCfg.window.id)
} return {
...toRefs(data),
winCfg, handleWinMin,
handleWinMax2Min,
handleWinClose
}
}
}
</script>
支持自定义颜色、是否可以最大/小化及关闭等功能。

<!-- //网址链接模板 -->
<template>
<div>
<NavBar></NavBar> <!-- 内容区 -->
<div class="ntMain__cont flex1 flexbox flex-col">
<div class="vChat__lkview">
<iframe scrolling="auto" allowtransparency="true" frameborder="0" :src="data.url"></iframe>
</div>
</div>
</div>
</template>

<WinBar color="#ff0">
<a class="wbtn" title="关于" @click="handleAboutWin"><i class="iconfont icon-about"></i></a>
<a class="wbtn" title="个性装扮" @click="handleSkinWin"><i class="iconfont icon-huanfu"></i></a>
<a class="wbtn" title="朋友圈" @click="handleFZoneWin"><i class="iconfont icon-pyq2"></i><em class="vui__badge-dot"></em></a>
<a class="wbtn" title="设置" @click="isShowSettingLayer=true"><i class="iconfont icon-peizhi"></i></a>
<a class="wbtn" title="界面管理器" @click="handleUIManager"><i class="iconfont icon-tianjia"></i></a>
<a class="wbtn" :class="{'on': isAlwaysOnTop}" :title="isAlwaysOnTop ? '取消置顶' : '置顶'" @click="handleAlwaysTop"><i class="iconfont icon-ding"></i></a>
</WinBar>
实现起来非常简单,只是需要注意一些细节即可。另外设置-webkit-app-region: drag之后,下面的子元素记得要设置-webkit-app-region: no-drag,否则无法响应点击事件。
另外拖拽区域会出现如下系统右键菜单,为了让拖拽区更加逼真,需要屏蔽系统菜单。

win.hookWindowMessage(278, function(e){
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 150)
return true
})
3、vue3+electron模仿QQ托盘图标/托盘闪烁


在项目根目录下放置两个大小一致的tray.ico文件,其中一个透明即可。


let tray = null
let flashTimer = null
let trayIco1 = path.join(__dirname, '../static/tray.ico')
let trayIco2 = path.join(__dirname, '../static/tray-empty.ico') // 创建系统托盘图标
createTray() {
const trayMenu = Menu.buildFromTemplate([
{
label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
click: () => {...}
},
{
label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
click: () => {...}
},
{
label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
click: () => {...}
},
{
label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
click: () => {...}
},
{type: 'separator'},
{
label: '关闭所有声音', click: () => {...},
},
{
label: '关闭头像闪动', click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
{
label: '打开主窗口', click: () => {
// ...
}
},
{
label: '退出', click: () => {
// ...
}
},
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
// ...
})
} // 托盘图标闪烁
flashTray(flash) {
let hasIco = false if(flash) {
if(this.flashTimer) return
this.flashTimer = setInterval(() => {
this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco = !hasIco
}, 500)
}else {
if(this.flashTimer) {
clearInterval(this.flashTimer)
this.flashTimer = null
}
this.tray.setImage(this.trayIco1)
}
} // 销毁托盘图标
destoryTray() {
this.flashTray(false)
this.tray.destroy()
this.tray = null
}
调用 flashTray(true) 和 flashTray(false) 分别开启/关闭托盘图标闪烁。
在一开始调试的时候托盘图标一直不显示,最后发现是路径错了。通过 console.log(__dirname) 指向了默认的 dist_electron 打包目录。
console.log('----------开始创建托盘')
console.log(__dirname)
console.log(path.join(__dirname, '../static/tray.ico'))


4、vue3+electron打包遇到的一些坑
项目使用的是electron-builder打包器来打包的,下面是常用的一些electron-builder打包配置。这里选择的是在vue.config.js里面配置。
/**
* 项目配置文件
*/ const path = require('path') module.exports = {
... // webpack配置
chainWebpack: config => {
...
}, pluginOptions: {
electronBuilder: {
nodeIntegration: true,
// 项目打包参数配置
builderOptions: {
"productName": "electron-qchat", //项目名称 打包生成exe的前缀名
"appId": "com.example.electronqchat", //包名
"compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}",
// "directories": {
// "output": "build", //输出文件夹(默认dist_electron)
// },
"asar": false, //asar打包
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
"nsis": {
"oneClick": false, //一键安装
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
"artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}",
"deleteAppDataOnUninstall": true, //卸载时删除数据
"createDesktopShortcut": true, //创建桌面图标
"createStartMenuShortcut": true, //创建开始菜单图标
"shortcutName": "ElectronQChat", //桌面快捷键图标名称
},
"win": {
"icon": "./static/shortcut.ico", //图标路径
}
}
}
}
}
1、大家如果在.vue页面中使用 ipcRenderer 或 remote 模块,出现如下报错:
Uncaught TypeError: fs.existsSync is not a function
出现这个问题的原因:
- 因为渲染进程属于浏览器端,没有集成Node的环境,像fs这样的Node包是不可以使用的。
- 没有Node环境,所以require关键词也是不可以使用的。

配置如上参数即可快速解决问题。
2、项目路径不能设置中文,否则打包会出错,一定要养成英文命名习惯。
3、最好不要使用getCurrentInstance函数来获取上下文,操作store或router,打包会出错。
4、创建的vue3项目默认会是 createWebHistory 模式,打包会出现白屏情况;改为 createWebHashHistory Hash模式。

5、打包静态资源缺失或截图dll无效。需在打包配置里设置 extraResources 参数。
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
from参数表示资源目录位置,to表示打包时移动资源目录到指定位置。如下图所示:

项目中的static目录用来存放一些ico图标或截图dll文件,即配置中的from目录名称。
打包后,会在resources目录下生成static文件夹,即配置中的to定义的目录名称。

好了,以上就是vue3+electron11开发跨平台项目的一些分享及踩坑记录,希望能帮助到大家哈!
最后贴上一个vue3.0+vant3开发移动端h5聊天实例
https://www.cnblogs.com/xiaoyan2017/p/14250798.html





基于vue3+electron11实现QQ登录切换|自定义导航栏|托盘|打包的更多相关文章
- 基于vite3+tauri模拟QQ登录切换窗体|Tauri自定义拖拽|最小/大/关闭
前两天有给大家分享tauri+vue3快速搭建项目.封装桌面端多开窗口.今天继续来分享tauri创建启动窗口.登录窗口切换到主窗口及自定义拖拽区域的一些知识.希望对想要学习或正在学习的小伙伴有些帮助. ...
- Taro多端自定义导航栏Navbar+Tabbar实例
运用Taro实现多端导航栏/tabbar实例 (H5 + 小程序 + React Native) 最近一直在捣鼓taro开发,虽说官网介绍支持编译到多端,但是网上大多数实例都是H5.小程序,很少有支持 ...
- uni-app自定义导航栏按钮|uniapp仿微信顶部导航条
最近一直在学习uni-app开发,由于uniapp是基于vue.js技术开发的,只要你熟悉vue,基本上很快就能上手了. 在开发中发现uni-app原生导航栏也能实现一些顶部自定义按钮+搜索框,只需在 ...
- React Native自定义导航栏
之前我们学习了可触摸组件和页面导航的使用的使用: 从零学React Native之09可触摸组件 - 从零学React Native之03页面导航 - 经过之前的学习, 我们可以完成一个自定义导航栏了 ...
- iOS个人中心渐变动画、微信对话框、标签选择器、自定义导航栏、短信验证输入框等源码
iOS精选源码 简单的个人中心页面-自定义导航栏并予以渐变动画 程序员取悦女票的正确姿势---Tip1(iOS美容篇) iOS 前台重启应用和清除角标的问题 微信原生提醒对话框3.0 JHLikeBu ...
- Nuxt/Vue自定义导航栏Topbar+标签栏Tabbar组件
基于Vue.js实现自定义Topbar+Tabbar组件|仿咸鱼底部凸起导航 最近一直在倒腾Nuxt项目,由于Nuxt.js是基于Vue.js的服务端渲染框架,只要是会vue,基本能很快上手了. 一般 ...
- 原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前
思路分析: 向后滚动鼠标滚轮,页面向下全屏切换:向前滚动滚轮,页面向上全屏切换.切换过程为动画效果. 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏.切换回第一屏时,导航栏向右滑动 ...
- 分别用ToolBar和自定义导航栏实现沉浸式状态栏
一.ToolBar 1.在build.gradle中添加依赖,例如: compile 'com.android.support:appcompat-v7:23.4.0' 2.去掉应用的ActionBa ...
- swift 自定义导航栏颜色
func setNavigationApperance(){ //自定义导航栏颜色 [self.navigationController?.navigationBar.barTintColor = U ...
随机推荐
- 2019牛客暑期多校训练营(第二场)E.MAZE(线段树+dp)
题意:给你一个n*m的矩阵 你只能向左向右相下走 有两种操作 q次询问 一种是把一个单位翻转(即可走变为不可走 不可走变为可走) 另一种是询问从(1,x) 走到 (n,y)有多少种方案 思路:题目n为 ...
- D-DP
必备知识 树链剖分,最大权独立集(即没有上司的舞会(树上DP)),矩阵乘法; D-DP 模版简述 模板 关于动态DP,其实是关于一类动态修改点权的问题,但是很难去处理; 我们平常的DP ...
- Bézout恒等式
写在前面: 记录了个人的学习过程,同时方便复习 整理自网络 非原创部分会标明出处 目录 结论 证明 拓展 n个整数间 拓展欧几里得算法 拓展欧几里得算法的多解 结论 (Bézout / 裴蜀 / 贝祖 ...
- poj3083 Children of the Candy Cor
Description The cornfield maze is a popular Halloween treat. Visitors are shown the entrance and mus ...
- Python 分析热卖年货,今年春节大家都在送啥?
今年不知道有多少小伙伴留在原地过年,虽然今年过年不能回老家,但这个年也得过,也得买年货,给家人长辈送礼.于是我出于好奇心的想法利用爬虫获取某宝数据,并结合 Python 数据分析和第三方可视化平台来分 ...
- 通过k8s部署dubbo微服务并接入ELK架构
需要这样一套日志收集.分析的系统: 收集 -- 能够采集多种来源的日志数据 (流式日志收集器) 传输 -- 能够稳定的把日志数据传输到中央系统 (消息队列) 存储 -- 可以将日志以结构化数据的形式存 ...
- ArcGIS处理栅格数据(一)
一.建立影像金字塔 ArcToolbox--数据管理工具--栅格--栅格属性--构建金字塔(pyramid) 说明:该方式一次只能为一张影像数据建立影像金字塔. ArcToolbox--数据管理工具- ...
- 问题记录 java.lang.NoClassDefFoundError: org/dom4j/DocumentException
客户端调webservice服务产生以下错误 AxisFault faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.genera ...
- 概率分析方法与推断统计(来自我写的python书)
在数据分析统计的场景里,常用的方法除了描述性统计方法外,还有推断统计方法,如果再从工作性质上来划分,推断统计包含了参数估计和假设验证这两方面的内容.而推断统计用到了很多概率统计方法,所以本小节在介绍推 ...
- 010. NET5_命令参数读取+配置多种读取
上节课遗留问题:上节脚本启动后,CSS样式丢失问题 解决办法:a.拷贝丢失的wwwroot目录:b. 给UesStaticFiles类指定读取wwwroot目录 静态文件读取 Nuget引入:Micr ...