前言
此前有给大家分享一个vite2 vant3开发h5手机端小视频实例。今分享一个最新开发的electron跨端聊天应用。
基本实现了发送图文消息、图片/视频/链接预览、拖拽发送图片、截图及朋友圈
等功能。
vue3-electronChat 支持新开多个窗体、换肤
等功能。
框架技术
- vue3全家桶:vue3.0 vuex4 vue-router@4
- electron框架:electron11.2.3
- 打包工具:vue-cli-plugin-electron-builde
- UI组件库:ant-design-vue2.0 (蚂蚁金服桌面端vue3组件库)
- 弹框组件:v3layer (vue3桌面端弹窗组件)
- 滚动条组件:v3scroll (vue3自定义美化滚动条)
项目结构目录
ant-design-vue桌面端vue3组件库
antdv 蚂蚁金服团队推出的vue3桌面端pc组件库。目前star高达13.8K 。
安装组件
代码语言:javascript复制npm i ant-design-vue@next --save
引入组件
在main.js中全局引入组件。
代码语言:javascript复制import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import App from './App'
import 'ant-design-vue/dist/antd.css'
const app = createApp()
app.use(Antd)
按需引入
需要先安装插件npm i babel-plugin-import -D
然后在babel.config.js
中如下配置:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// 按需引入第三方插件
plugins: [
[
'import',
{
'libraryName': 'ant-design-vue',
'libraryDirectory': 'es',
'style': 'css',
}
]
]
}
则可以通过如下方法按需引入组件了。
代码语言:javascript复制// 按需引入ant-design-vue组件库
import {
Button, message, Tabs, Checkbox, Image, Upload
} from 'ant-design-vue'
...
const Plugins = (app) => {
app.use(Button)
app.use(Tabs)
app.use(Checkbox)
app.use(Image)
app.use(Upload)
...
app.config.globalProperties.$message = message
}
export default Plugins
如果大家在开发中只需要部分组件功能,建议按需引入,这样打包后体积不会过大。
electron自定义无边框导航菜单
为了让项目UI更加美观,大家可以选择在创建窗口的时候去掉系统边框,自定义拖拽区域。
配置frame: false
,即可去掉系统顶部菜单。
通过css3来控制可拖拽区域-webkit-app-region: drag
不过会随之而来出现一个问题,就是鼠标右键会弹出一个系统菜单,那有木有什么方法可以屏蔽掉呢?
后来经过反复调试,发现可以通过如下方法快速去掉系统右键菜单。
代码语言:javascript复制win.hookWindowMessage(278, () => {
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 100)
return true
})
如果小伙伴们有其它的方法,欢迎一起交流分享哈~~
winbar.vue
右上角菜单组
<template>
<div class="vui__winbtn flexbox flex-alignc">
<div class="vui__winbtn-groups" :style="{'color': color}">
<slot />
<a v-if="minimizable" class="wbtn" title="最小化" @click="handleWinMin"><i class="iconfont icon-min"></i></a>
<a v-if="maximizable" class="wbtn" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinMax2Min"><i class="iconfont" :class="hasMaximized ? 'icon-restore' : 'icon-max'"></i></a>
<a v-if="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 = () => {
...
}
return {
...toRefs(data),
winCfg,
handleWinMin,
handleWinMax2Min,
handleWinClose
}
}
}
</script>
navbar.vue
导航栏模板
<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>
electron创建托盘|托盘闪烁
关闭主窗口会有一个询问提示。
点击最小化到托盘后,会直接隐藏窗口到托盘。
大家需要准备两个大小一致的ico图标,其中一个透明即可,通过定时器来控制切换。
代码语言:javascript复制// 创建系统托盘图标
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: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(!win) return
if(win.isMinimized()) win.restore()
win.show()
}
} catch (error) {
console.log(error)
}
}
},
{
label: '退出', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(win) win.webContents.send('win-logout')
}
app.quit()
} catch (error) {
console.log(error)
}
}
},
])
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
}
注意:加载本地ico,主进程__dirname
会默认指向dist_electron
目录。
大家可以在根目录下新建一个静态资源目录,用来存放一些图片/dll等文件。
electron-builder打包配置
创建vue3项目的时候,根目录会有一个vue.config.js
配置文件。可用来进行一些项目配置和electron打包配置。
/**
* @Desc vue3 electron项目|打包配置文件
* @Time andy by 2021-02
* @About Q:282310962 wx:xy190310
*/
const path = require('path')
module.exports = {
// 基本路径
// publicPath: '/',
// 输出文件目录
// outputDir: 'dist',
// assetsDir: '',
// 环境配置
devServer: {
// host: 'localhost',
// port: 8080,
// 是否开启https
https: false,
// 编译完是否打开网页
open: false,
// 代理配置
// proxy: {
// '^/api': {
// target: '<url>',
// ws: true,
// changeOrigin: true
// },
// '^/foo': {
// target: '<other_url>'
// }
// }
},
// webpack配置
chainWebpack: config => {
// 配置路径别名
config.resolve.alias
.set('@', path.join(__dirname, 'src'))
.set('@assets', path.join(__dirname, 'src/assets'))
.set('@components', path.join(__dirname, 'src/components'))
.set('@module', path.join(__dirname, 'src/module'))
.set('@plugins', path.join(__dirname, 'src/plugins'))
.set('@layouts', path.join(__dirname, 'src/layouts'))
.set('@views', path.join(__dirname, 'src/views'))
},
// 插件配置
pluginOptions: {
electronBuilder: {
// 配置后可以在渲染进程使用ipcRenderer
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", //图标路径
}
}
}
}
}
大家在开发项目的时候,千万记得目录最好不要使用中文,否则会打包失败,巨坑!
另外在操作store和router,也最好不要使用getCurrentInstance来获取上下文,打包会报错!
大家如果出现打包后,截图dll无效,不能使用截图功能。记得配置extraResources
字段。
// 拷贝资源到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
from是原路径,如上图所示。to是目标目录,如下图所示位置。
ending,基于vue3.x electron开发聊天软件就分享到这里。希望对大家有些帮助!