1.准备工作
① 必要安装node.js、vue、vite、electron、pnpm
本人用的node版本v18.17.1、vue版本^3.4.19、vite版本^3.2.7、electron版本^35.1.4
② 开发调试打包安装
"devDependencies": {
"concurrently": "^9.1.2",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^4.0.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
"wait-on": "^8.0.3"
}
package.json结构:
{
"name": "okyi_admin",
"private": true,
"version": "0.0.1",
"main": "electron/main.js", // 改动点1
"scripts": {
"dev": "vite",
"preview": "vite preview",
"build": "vite build --mode production",
// 改动点2 注意此处端口为5173,在vite.config.js中server下的port启动端口务必保持一致
"electron": "wait-on tcp:5173 && electron .",
"electron:dev": "concurrently -k \"pnpm run dev\" \"pnpm run electron\"",
"electron:build": "pnpm run build && electron-builder",
"electron:buildAll": "pnpm run build && electron-builder -wml",
"postinstall": "electron-builder install-app-deps"
},
"dependencies": {
"@electron/remote": "^2.1.2",
"@element-plus/icons-vue": "^2.0.9",
"@types/node": "^18.6.5",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"add": "^2.0.6",
"animate.css": "^4.1.1",
"axios": "1.6.0",
"crypto-js": "^4.2.0",
"electron-reload": "2.0.0-alpha.1", // 改动点3
"element-plus": "2.2.21",
"js-cookie": "^3.0.1",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.17",
"pinia-plugin-persist": "^1.0.0",
"unplugin-element-plus": "^0.4.1",
"vue": "^3.4.19",
"vue-router": "^4.1.3",
"vue3-video-play": "^1.3.2",
"ws": "^8.14.2",
"yarn": "^1.22.19"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.0",
"babel-eslint": "^10.1.0",
"concurrently": "^9.1.2", // 改动点4
"consola": "^2.15.3",
"electron": "^35.1.4", // 改动点5
"electron-builder": "^26.0.12", // 改动点6
"electron-devtools-installer": "^4.0.0", // 改动点7
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.6.0",
"less": "^4.2.0",
"prettier": "^2.7.1",
"sass": "^1.55.0",
"sass-loader": "^13.1.0",
"unplugin-auto-import": "^0.11.1",
"unplugin-vue-components": "^0.22.3",
"vite": "^3.2.7",
"vite-plugin-electron": "^0.29.0", // 改动点8
"vite-plugin-electron-renderer": "^0.14.6", // 改动点9
"vite-plugin-style-import": "^2.0.0",
"wait-on": "^8.0.3" // 改动点10
},
// 改动点11
"build": {
"appId": "com.yourcompany.yourapp",
"productName": "Your App",
"copyright": "Copyright © 2025",
"directories": {
"output": "release/${version}", // 打包后产物路径
"buildResources": "build-electron"
},
"files": [
"dist/**/*",
"electron/**/*",
"!**/node_modules/**/*"
],
"win": {
"target": "nsis",
"icon": "public/icon.ico" // 应用logo路径
},
"mac": {
"target": "dmg",
"icon": "public/icon.icns", // 应用logo路径
"category": "public.app-category.productivity"
},
"linux": {
"target": "AppImage",
"icon": "public/icon.png" // 应用logo路径
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
}
2.vite.config.js中修改如下:
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import * as path from 'path';
... 其它导入 ...
import electron from 'vite-plugin-electron'
import electronRenderer from 'vite-plugin-electron-renderer'
export default defineConfig((env) => {
const evnMap = loadEnv(env.mode, process.cwd());
console.log(`当前运行环境配置信息 evnMap = ${JSON.stringify(evnMap)}`);
return {
base: './', // 必须设置为相对路径
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@a': path.resolve(__dirname, 'src/assets'),
'@u': path.resolve(__dirname, 'src/utils'),
'@c': path.resolve(__dirname, 'src/components'),
'@api': path.resolve(__dirname, 'src/api'),
},
},
... 其它配置 ...
build: {
... 其它配置 ...
emptyOutDir: false, // 避免electron构建被清空
},
plugins: [
... 其它配置 ...
electron({
entry: 'electron/main.js',
onstart(options) {
options.startup(['.', '--no-sandbox']).then(r => {})
},
vite: {
build: {
sourcemap: true,
outDir: 'dist-electron',
},
},
}),
electronRenderer({
nodeIntegration: true,
}),
... 其它配置 ...
],
... 其它配置 ...
server: {
open: false, // 调试桌面应用时务必置为false
host: '0.0.0.0', // ip地址
port: 5173, // 启动端口
... 其它配置 ...
},
};
});
3.项目根目录下创建electron目录,并新建main.js、preload.js文件,main.js对应的是package.json中main字段的值
① main.js内容如下:
const { app, BrowserWindow, ipcMain, shell } = require('electron')
const path = require('path')
const isDev = !app.isPackaged
// 安全设置
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
let mainWindow
async function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
sandbox: true,
contextIsolation: true,
nodeIntegration: false,
webSecurity: false // 启用web安全策略
}
})
// 优雅加载
mainWindow.once('ready-to-show', () => {
mainWindow.show()
if (isDev) {
mainWindow.webContents.openDevTools({ mode: 'detach' })
}
})
// 安全策略:阻止外部链接在应用内打开
mainWindow.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
console.log(`尝试打开: ${url}, 框架名: ${frameName}, 特性: ${features}`)
if (!url.startsWith('https://')) {
shell.openExternal(url) // 使用外部浏览器打开
return { action: 'deny' }
}
return { action: 'allow' } // 使用桌面应用新窗口形式打开
})
// 加载应用
if (isDev) {
require('electron-reload')/*(__dirname, {
electron: path.join(__dirname, 'node_modules', '.bin', 'electron'),
hardResetMethod: 'exit'
})*/
// 加载浏览器安全策略
// mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
// callback({
// responseHeaders: {
// ...details.responseHeaders,
// 'Content-Security-Policy': [
// `default-src 'self' 'unsafe-inline' data:;
// script-src 'self' 'unsafe-eval' 'unsafe-inline' http:;
// connect-src 'self' ws://admin-test-api.ok-yi.com:* http://8.217.215.97:* https://admin-test-api.ok-yi.com:* https://admin-test-api.ok-yi.com:*;
// img-src 'self' data: http:;
// style-src 'self' 'unsafe-inline';
// font-src 'self' data:;`
// ]
// }
// })
// })
await mainWindow.loadURL('http://localhost:5173') // 启动端口5173务必与vite.config.js中保持一致
} else {
// vue3+vite项目默认构建产物在根目录的dist下
await mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
// 开发工具
if (isDev) {
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer')
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
}
// 安全通信通道
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
app.whenReady().then(() => {
createWindow().then(r => {
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow().then(r => {
})
})
② preload.js内容如下:
const { contextBridge, ipcRenderer } = require('electron')
// 安全暴露有限的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
// openExternal: (url) => {
// console.log('openExternal = ', url)
// ipcRenderer.send('open-external', url)
// },
platform: process.platform
})
4.在App.vue中新增内容,获取electron主进程暴露给渲染进程的api,内容如下:
<template>
<!-- <el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>-->
<div>
<p>App Version: {{ appVersion }}</p>
<p>Platform: {{ platform }}</p>
<button @click="openDocs">Open Docs</button>
</div>
</template>
<script setup>
// import zhCn from 'element-plus/lib/locale/lang/zh-cn';
import { ref, onMounted } from 'vue'
const appVersion = ref('')
const platform = ref('')
onMounted(async () => {
if (window.electronAPI) {
appVersion.value = await window.electronAPI.getAppVersion()
platform.value = window.electronAPI.platform
}
})
const openDocs = () => {
if (window.electronAPI) {
// window.electronAPI.openExternal('https://electronjs.org/docs')
// window.open打开的外部链接会通过electron main.js中mainWindow.webContents.setWindowOpenHandler去过滤是使用窗口形式打开还是浏览器形式打开
window.open('https://electronjs.org/docs')
} else {
window.open('https://electronjs.org/docs', '_blank')
}
}
</script>
<style scoped></style>
5.一切准备就绪,接下来就可以运行跑起来了
确保已执行 pnpm install且成功安装所有依赖,执行命令:pnpm run electron:dev 桌面窗口正常弹出来了同时出现的还有调试工具,如下图:
打包构建使用命令:pnpm run electron:build,本人是用的mac,打包后产物如下图: