1、安装 (过程有点慢,耐心等待;建议替换yarn源或翻墙)
yarn add electron --dev
yarn add electron-devtools-installer --dev
yarn add vue-cli-plugin-electron-builder
2、在src目录下新建 background.ts,作为electron入口
import { app, protocol, BrowserWindow, ipcMain } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'
//import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
let win: any = null
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
useContentSize: true,
width: 1220,
height: 640,
minWidth: 1220,
minHeight: 640,
//transparent: true, //窗口透明 设置后还原窗口win.restore()无效
//backgroundColor: '#000', //背景颜色
title: '知乎者也', //标题
movable: true,
webPreferences: {
// Required for Spectron testing
// enableRemoteModule: !!process.env.IS_TEST,
// 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 as unknown as boolean,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
}
})
if (process.env.NODE_ENV === 'production') {
//正式
createProtocol('app')
win.loadURL('app://./index.html') // Load the index.html when not in development
//win.webContents.openDevTools()
} else {
//开发
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
if (!process.env.IS_TEST) win.webContents.openDevTools()
}
// 当应用所有窗口关闭要做的事情
win.on('closed', () => {
win = null
})
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
//外网 加载慢 去掉
// if (isDevelopment && !process.env.IS_TEST) {
// // Install Vue Devtools
// try {
// await installExtension(VUEJS3_DEVTOOLS)
// } catch (e) {
// console.error('Vue Devtools failed to install:', e.toString())
// }
// }
if (win === null) createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
3、修改package.json文件,新增入口属性以及运行命令
"scripts": {
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"web:serve": "vue-cli-service serve --open",
"web:build": "vue-cli-service build",
"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"
},
4、根目录下新建vue.config.js 用与保存打包配置
module.exports = {
runtimeCompiler: true,
publicPath: './',
//electron 13 把"build":{}从package.json移除,在vue.config.js里写
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
builderOptions: {
productName: 'zhzy', //打包名称
appId: 'cn.zhzy.app',
copyright: 'Copyright © 2022',
publish: [
{
provider: 'generic',
url: ''
}
],
nsis: {
allowToChangeInstallationDirectory: true,
createDesktopShortcut: true,
createStartMenuShortcut: true,
shortcutName: 'zhzy',
perMachine: true,
oneClick: false
},
dmg: {
contents: [
{
x: 410,
y: 150,
type: 'link',
path: '/Applications'
},
{
x: 130,
y: 150,
type: 'file'
}
]
},
//productName 包名称 version 包版本(package.json) ext后缀
mac: {
icon: 'public/icons/icon.icns',
artifactName: '${productName}_setup_${version}.${ext}'
},
win: {
icon: 'public/icons/icon.ico',
artifactName: '${productName}_setup_${version}.${ext}'
},
linux: {
icon: 'public/icons',
artifactName: '${productName}_setup_${version}.${ext}'
}
}
}
}
}
隐藏菜单栏
在background.ts增加
autoHideMenuBar: true, //是否隐藏菜单栏
无边框模式
1、在background.ts增加
frame: false,
无边框
不使用无边框模式
需要屏蔽掉
并将router/index.ts 路由中的替换
原始边框路由
background.ts增加
//登录窗口最小化
ipcMain.on('app-min', function () {
win.minimize()
})
//登录窗口最大化、还原窗口
ipcMain.on('app-max', function () {
if (win.isMaximized()) {
win.restore()
} else {
win.maximize()
}
})
ipcMain.on('app-close', function () {
//win.close() //在设置无边框后失效
win = null //主窗口设置为null 防止内存溢出
app.exit() //直接退出应用程序
})
在src目录下新建proload.ts
import { ipcRenderer } from 'electron'
(window as any).ipcRenderer = ipcRenderer
在background.ts webPreferences新增
preload: path.join(__dirname, 'preload.ts'), //预加载
版本升级
yarn add electron-updater
src目录下新建upgrade.ts
import { ipcMain } from 'electron'
import { autoUpdater } from 'electron-updater'
let mainWindow: any = null
export function upgradeHandle(window: any, feedUrl: any) {
const msg = {
error: '检查更新出错 ...',
checking: '正在检查更 ...',
updateAva: '检测到新版本 ...',
updateNotAva: '已经是最新版本 ...',
downloadProgress: '正在下载新版本 ...',
downloaded: '下载完成,开始更新 ...'
}
mainWindow = window
autoUpdater.autoDownload =
process.env.VUE_APP_UPGRADE === 'automatic' ? true : false //true 自动升级 false 手动升级
//设置更新包的地址
autoUpdater.setFeedURL(feedUrl)
//监听升级失败事件
autoUpdater.on('error', function (message: any) {
sendUpdateMessage({
cmd: 'error',
title: msg.error,
message: message
})
})
//监听开始检测更新事件
autoUpdater.on('checking-for-update', function (message: any) {
sendUpdateMessage({
cmd: 'checking-for-update',
title: msg.checking,
message: message
})
})
//监听发现可用更新事件
autoUpdater.on('update-available', function (message: any) {
sendUpdateMessage({
cmd: 'update-available',
title: msg.updateAva,
message: message
})
})
//监听没有可用更新事件
autoUpdater.on('update-not-available', function (message: any) {
sendUpdateMessage({
cmd: 'update-not-available',
title: msg.updateNotAva,
message: message
})
})
// 更新下载进度事件
autoUpdater.on('download-progress', function (message: any) {
sendUpdateMessage({
cmd: 'download-progress',
title: msg.downloadProgress,
message: message
})
})
//监听下载完成事件
autoUpdater.on(
'update-downloaded',
function (
event: any,
releaseNotes: any,
releaseName: any,
releaseDate: any,
updateUrl: any
) {
sendUpdateMessage({
cmd: 'update-downloaded',
title: msg.downloaded,
message: {
releaseNotes,
releaseName,
releaseDate,
updateUrl
}
})
//退出并安装更新包
autoUpdater.quitAndInstall()
}
)
//接收渲染进程消息,开始检查更新
ipcMain.on('checkForUpdate', () => {
//执行自动更新检查
autoUpdater.checkForUpdates()
})
ipcMain.on('downloadUpdate', () => {
// 下载
autoUpdater.downloadUpdate()
})
}
//给渲染进程发送消息
function sendUpdateMessage(text: any) {
mainWindow.webContents.send('message', text)
}
background.ts增加
import { upgradeHandle } from '@/upgrade'
if (process.env.NODE_ENV === 'production') {
//正式
createProtocol('app')
win.loadURL('app://./index.html') // Load the index.html when not in development
if (process.env.VUE_APP_UPLOAD !== '')
upgradeHandle(win, process.env.VUE_APP_UPLOAD) //检测版本更新
//win.webContents.openDevTools()
通过.env.development/.env.production设置 开发与正式环境
# 配置生产环境 系统变量 建议配置一些 常用 或者 数据较单一的配置
NODE_ENV=production
# 版本号 应与package.json version 保持统一
VUE_APP_VERSION=0.1.0
# 升级地址
VUE_APP_UPLOAD= http://192.168.1.51:8011/upload/
# 升级方式 automatic 自动升级 manual 手动升级
VUE_APP_UPGRADE=manual
APP.vue实现
<!--
* @Description:
* @Author: lanchao
* @Date: 2022-04-14 11:21:23
* @LastEditTime: 2022-05-28 17:38:12
* @LastEditors: lanchao
* @Reference:
-->
<template>
<div id="app">
<router-view />
<!--异步组件-->
<Suspense v-if="isUpgrade">
<template #default>
<UpgradeComponent />
</template>
<template #fallback>
<h1>检查更新 ...</h1>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { defineAsyncComponent } from 'vue'
@Options({
components: {
//异步加载组件
UpgradeComponent: defineAsyncComponent(
() => import('@/components/Upgrade.vue')
)
}
})
export default class AppComponent extends Vue {
isUpgrade = process.env.IS_ELECTRON
? process.env.IS_ELECTRON &&
process.env.NODE_ENV === 'production' &&
process.env.VUE_APP_UPLOAD !== ''
: false //判断是否可更新 true是 false否
}
</script>
<style lang="scss">
body {
margin: 0;
padding: 0;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
font-size: 16px;
}
</style>
更新对话框
<!--手动升级-->
<template>
<div id="update">
<el-dialog
:title="title"
v-model="dialogVisible"
width="60%"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
:show-close="showClose"
center
>
<div class="percentages">
<el-progress
status="success"
:text-inside="true"
:stroke-width="20"
:percentage="percentage"
:width="strokeWidth"
:show-text="true"
></el-progress>
</div>
<div class="footer" v-if="btnStatus && !isBtn">
<el-button @click="closeDialog">暂不升级</el-button>
<el-button @click="upgrade" type="primary">升级</el-button>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { ipcRenderer } from 'electron'
@Options({
components: {}
})
export default class UpgradeComponent extends Vue {
title = ''
dialogVisible = false
closeOnClickModal = false
closeOnPressEscape = false
showClose = false
percentage = 0
strokeWidth = 200
timeOut = 0
btnStatus = false //是否显示升级按钮
isBtn = process.env.VUE_APP_UPGRADE === 'automatic' ? true : false //true 自动升级 false 手动升级
/**
* 关闭
*/
closeDialog() {
this.title = ''
this.dialogVisible = false
}
/**
* 开始更新
*/
upgrade() {
ipcRenderer.send('downloadUpdate')
this.btnStatus = false
}
mounted() {
ipcRenderer.send('checkForUpdate') //检查版本更新
// eslint-disable-next-line @typescript-eslint/no-this-alias
let _this = this
//接收主进程版本更新消息
ipcRenderer.on('message', (event: any, arg: any) => {
_this.title = arg.title
if ('update-available' === arg.cmd) {
_this.btnStatus = true
_this.dialogVisible = true //显示升级对话框
} else if ('download-progress' === arg.cmd) {
const percent = Math.round(parseFloat(arg.message.percent))
_this.percentage = percent
} else if ('update-downloaded' === arg.cmd) {
_this.dialogVisible = false
} else if ('error' === arg.cmd) {
_this.dialogVisible = false
}
})
}
}
</script>
<style lang="scss">
#update {
background: #f8f8f8;
}
.percentages {
width: 100%;
height: 5vh;
line-height: 5vh;
text-align: center;
}
body {
margin: 0px;
}
.v-modal {
opacity: 1 !important;
background: rgba(0, 0, 0, 0.5) !important;
}
.el-tooltip {
display: flex;
align-items: center;
}
.footer {
display: flex;
flex-flow: row-reverse;
}
</style>
<style lang="scss" scoped>
.tableList {
widows: 150px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.el-tooltip__popper {
font-size: 14px;
max-width: 300px !important;
text-align: justify;
text-justify: newspaper;
word-break: break-all;
line-height: 20px;
}
</style>
将打包后的dist_electron 目录下的 exe文件 和 latest.yml上传到 服务器VUE_APP_UPLOAD
解决打包后白屏
const router = createRouter({
// history: createWebHistory(process.env.BASE_URL),
history: process.env.IS_ELECTRON
? createWebHashHistory(process.env.BASE_URL)
: createWebHistory(process.env.BASE_URL), //解决打包后白屏
routes
})
更换图标
window
如下方式可解决打包后窗口左上角图标不显示问题
修改vue.config.js
"win": {
"icon": "build/icons/icon.ico" (为256*256图片)
}
mac
图标格式icns
推荐在线转换格式
demo地址 zhzy-tveep/src/components at main · Lateautumn00/zhzy-tveep · GitHub