Electron + VUE3 + 实现桌面级应用程序前端模板
前言
electron 用的版本是25.6.0
打包工具使用:vite
vue版本:3.3.4
虽然是vue3.x版本,但是大部分的代码还是采用了vue2.x的语法去编写,这样让没有学过vue3的同学们,看起来更直观一点
Electron 是一个用于构建跨平台桌面应用程序的开源框架,由 GitHub 开发和维护。它允许开发者使用 HTML、CSS 和 JavaScript 等前端技术,结合 Node.js 和 Chromium,创建可以在 Windows、macOS 和 Linux 上运行的桌面应用程序。
- 跨平台支持
- 同一份代码可以在主流桌面平台上运行(Windows、macOS、Linux)。
- 基于 Web 技术
- 使用前端技术(HTML/CSS/JavaScript)来开发界面。
- Node.js 集成
- 支持 Node.js 的完整功能,允许直接调用文件系统、进程、网络等底层功能。
- Chromium 渲染引擎
- 使用 Chromium 提供的高性能 Web 渲染能力,使应用界面与现代浏览器一致。
- 强大的生态系统
- Electron 拥有丰富的第三方插件和库支持,例如文件系统操作、通知、菜单管理等。
页面展示
登录页面

导航栏自定义红绿灯,关闭了原始的
核心代码
主进程代码
import { app, shell, BrowserWindow ,ipcMain} from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
const login_width = 530;
const register_height = 635;
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
icon:icon,
width: login_width,
height: register_height,
show: false,
transparent:true,
autoHideMenuBar: true,
titleBarStyle:'hidden',
resizable:false,
frame: true,
mediaAccess:true,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
webSecurity: false,
}
})
ipcMain.on('toMain',(event, args)=>{
mainWindow.setResizable(true)
mainWindow.setSize(((args.screenWidth / 5) * 3) + 150,600)
mainWindow.setMinimumSize(((args.screenWidth / 5) * 3) + 150,600)
mainWindow.center();
mainWindow.setMaximizable(true)
mainWindow.setMaximumSize(((args.screenWidth / 5) * 3) + 200,700)
})
ipcMain.on('minimizing',(event,args)=>{
event.preventDefault(); // 阻止默认最小化行为
mainWindow.minimize(); // 最小化到任务栏
})
ipcMain.on('expandWindow',(event,args)=>{
let defaultSize = mainWindow.getSize();
let maxSize = mainWindow.getMaximumSize();
if (defaultSize[0] === (((args.screenWidth / 5) * 3) + 150) && defaultSize[1] === 600){
mainWindow.setResizable(true)
mainWindow.setSize(((args.screenWidth / 5) * 3) + 200,700)
}else if (maxSize[0] === (((args.screenWidth / 5) * 3) + 200) && maxSize[1] === 700){
mainWindow.setResizable(true)
mainWindow.setSize(((args.screenWidth / 5) * 3) + 150,600)
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
// 窗口调试 mainWindow.webContents.openDevTools()
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
自定义导航栏组件
<template>
<div class="title-bar" :style="{width:expandState?'':'510px'}">
<div class="title-bar-left" :style="{backgroundColor:expandState?'rgba(221, 228, 234,0.9)':'#fff'}">
<img src="~@/assets/bar/logo.svg" alt="图标" class="icon" />
<div class="title">流量监控系统</div>
</div>
<div class="title-bar-right">
<div class="operating-button close-button" @click="closeWindow">
<img src="~@/assets/bar/close.svg" />
</div>
<div class="operating-button minimum-button" @click="minimumWindow">
<img src="~@/assets/bar/minimum.svg" />
</div>
<div v-if="expandState" class="operating-button expand-button" @click="expandWindow">
<img src="~@/assets/bar/expand.svg" />
</div>
<div style="width: 10px;"></div>
</div>
</div>
</template>
<script>
export default {
name: "TitleBar",
data(){
return{
expandState:false,
}
},
mounted() {
this.expandState = !(this.$route.path === "/" || this.$route.path === '/login');
},
methods: {
closeWindow() {
window.close(); // 关闭窗口
},
minimumWindow() {
window.ipcRenderer.send('minimizing')
},
expandWindow() {
window.ipcRenderer.send('expandWindow',{
screenWidth:window.screen.width,
screenHeight:window.screen.height,
})
},
},
};
</script>
<style scoped>
@import "./index.css";
</style>
注册页面

其他登录方式
人脸登录
效果展示图:

人脸登录采用的是effet.js进行登录的
官方文档:faceeffet.com/
首先安装人脸动画插件
npm install face-effet
具体实现代码 face.vue:
<template>
<div class="face-main">
<div class="faceMain">
</div>
<button v-loading="backLoading" class="btn" @click="backReturn"> 返回登录
</button>
</div>
</template>
<script>
// 初次加载会很慢,请耐心等待,
// face-effet插件官网:https://faceeffet.com/
import 'face-effet/effet/effet.css'
import effet from 'face-effet/effet/effet'
export default {
name: "index",
data(){
return{
faceEffet:effet,
backLoading:false,
}
},
beforeDestroy(){
if (this.faceEffet){
this.faceEffet.close();
}
},
mounted() {
try {
this.faceEffet.restart();
}catch (e) {
this.faceEffet.init({
el:'faceMain',
callBack:this.callBack
})
}
},
methods:{
callBack(data){
if (data.progressMessage === 'success'){ // 判断阶段是否成功
if (!data.base64Array || data.base64Array.length === 0){
return;
}
Promise.all(data.base64Array).then((base64Strings) => {
// 人脸数据:base64Strings
// 这里调用后端
if (base64Strings){
this.faceEffet.close();
setTimeout(()=>{
this.$router.push({
path:'/main',
query:{
base64:base64Strings[0]
}
});
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
window.ipcRenderer.send('toMain',{
username:'xxxxx',
token:'',
screenWidth:screenWidth,
screenHeight:screenHeight
})
},1000)
}
}).catch((error) => {
console.error("Error resolving promises:", error);
});
}
},
backReturn(){
if (this.backLoading){
return
}
// 先销毁人脸容器
this.faceEffet.close();
this.backLoading = true
// 等待1s后跳转
setTimeout(()=>{
this.backLoading = false
this.$router.push('/login');
},1000)
}
}
}
</script>
<style>
@import "./index.css";
</style>
注意index.html相关协议需要这样配置
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self' 'unsafe-eval' blob: https://unpkg.com;
style-src 'self' 'unsafe-inline';
img-src * data:;
connect-src 'self' blob: https://unpkg.com data:;
worker-src 'self' blob:;
"
/>
主进程代码
为了开发阶段便捷性,临时关闭了webSecurity,生产环境需要开启确保安全。
const login_width = 530;
const register_height = 635;
const mainWindow = new BrowserWindow({
icon:icon,
width: login_width,
height: register_height,
show: false,
transparent:true,
autoHideMenuBar: true,
titleBarStyle:'hidden',
resizable:false,
frame: true,
mediaAccess:true,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
webSecurity: false,
}
})
网页SSO登录
功能描述
- 使用系统默认浏览器打开 SSO 登录页面。
- 利用自定义协议捕获登录后的回调信息。
- 关闭监听后返回授权信息。
代码示例
javascript复制代码const { app, shell, protocol } = require('electron');
const { URL } = require('url');
function openSSOLogin(loginUrl, callback) {
// Register a custom protocol to handle the SSO callback
const customProtocol = 'custom-app';
protocol.registerHttpProtocol(customProtocol, (req) => {
try {
const url = new URL(req.url);
const params = Object.fromEntries(url.searchParams.entries());
callback(null, params); // Send back the captured parameters
} catch (err) {
callback(err);
}
});
// Open the SSO login URL in the default browser
shell.openExternal(loginUrl);
}
// Example Usage
app.whenReady().then(() => {
const ssoLoginUrl = 'https://example-sso.com/login?client_id=APP_ID&redirect_uri=custom-app://callback';
openSSOLogin(ssoLoginUrl, (error, data) => {
if (error) {
console.error('SSO Login Error:', error);
} else {
console.log('SSO Login Successful, Data:', data);
}
});
});
步骤说明
- 自定义协议
- 在
redirect_uri设置中配置一个自定义协议。 - Electron 的
protocol.registerHttpProtocol用于捕获这个自定义协议的回调。
- 在
- 回调解析
- SSO 登录成功后,服务器会将授权信息(如
code或token)附加到回调 URL 的查询参数中。 - 回调数据通过
callback参数返回给调用方。
- SSO 登录成功后,服务器会将授权信息(如
- 打开浏览器
- 使用
shell.openExternal()打开系统浏览器登录。
- 使用
注意事项
- 回调协议注册
确保在 SSO 平台配置
redirect_uri时,注册的自定义协议是允许的。 - 安全性 在生产环境中,验证返回的授权数据的真实性,确保不会被中间人打入。
主页面
桌面页面

充值页面

其他页面

退出登录

关于渲染进程跟主进程的交互
我们已自定义导航栏为列子
在我们的preload下面的index.js
核心的:window.ipcRenderer = ipcRenderer
import { contextBridge,ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
window.ipcRenderer = ipcRenderer // 将ipcRenderer定义到window上面
const api = {}
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
关闭窗口
直接调用close就行了
关闭窗口
closeWindow() {
window.close(); // 关闭窗口
},
最小化
minimumWindow() {
window.ipcRenderer.send('minimizing')
},
发送一个 minimizing 消息
在主进程中接收消息,然后响应
import { app, shell, BrowserWindow ,ipcMain} from 'electron'
ipcMain.on('minimizing',(event,args)=>{
event.preventDefault(); // 阻止默认最小化行为
mainWindow.minimize(); // 最小化到任务栏
})
扩大或者缩小
expandWindow() {
window.ipcRenderer.send('expandWindow',{
screenWidth:window.screen.width,
screenHeight:window.screen.height,
})
},
主进程的处理
主要判断是否为原始窗口,如果是原始窗口大小,则放大,否则则缩小
ipcMain.on('expandWindow',(event,args)=>{
let defaultSize = mainWindow.getSize();
let maxSize = mainWindow.getMaximumSize();
if (defaultSize[0] === (((args.screenWidth / 5) * 3) + 150) && defaultSize[1] === 600){
mainWindow.setResizable(true)
mainWindow.setSize(((args.screenWidth / 5) * 3) + 200,700)
}else if (maxSize[0] === (((args.screenWidth / 5) * 3) + 200) && maxSize[1] === 700){
mainWindow.setResizable(true)
mainWindow.setSize(((args.screenWidth / 5) * 3) + 150,600)
}
})
项目开源地址
gitee:gitee.com/susantyp/fl…
github:github.com/typsusan/fl…