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, // 调试桌面应用时务必置为falsehost: '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 mainWindowasync 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,打包后产物如下图: