vue3项目实现electron桌面应用-项目实战
1 安装环境
scss
npm install electron electron-builder concurrently wait-on cross-env --save-dev
2 维护项目结构
bash
项目根目录/
├── electron/
│ ├── ipc/ # 新增的IPC模块目录
│ │ ├── api/ # 具体功能API
│ │ │ ├── print.js # 打印相关API
│ │ │ ├── device.js # 设备相关API
│ │ │ └── ... # 其他功能模块
│ │ ├── index.js # IPC API聚合
│ │ └── types.js # 类型定义
│ ├── main/
│ │ └── index.js # electron主进程
│ └── preload/
│ └── preload.js # electron预加载进程
├── src/ # vue3项目
│ ├── utils/
│ │ ├── electron.js # 前端Electron API封装
│ │ └── ... # 其他功能模块
│ ├── ... # 其他vue3内容
├── vite.config.js
├── package.json
├── index.html
├── .env
├── .env.dev/product.... 其他环境配置
3 electron/main/index.js
javascript
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, '../preload/preload.js'),
nodeIntegration: false,
contextIsolation: true
}
})
// 加载Vue应用
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:8888')
mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(path.join(__dirname, '../../dist/index.html'))
}
mainWindow.on('closed', () => {
mainWindow = null
})
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
ipcMain.handle('ping', () => {
return 'pong'
})
4 electron/preload/preload.js
arduino
const { ipcRenderer, contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myElectronAPI', {
// 添加你希望暴露给渲染进程的安全方法
ping: () => ipcRenderer.invoke('ping'),
version: process.version,
})
5 修改package.json
主要维护打包命令 build部分 和
perl
{
"name": "ruoyi-electron",
"productName": "OkYun",
"version": "1.3.5", // 1 electron 必填,版本
"main": "electron/main/index.js",
"description": "OkYunFactura", // 2 electron 必填,项目描述
"author": "HUBIAO", // 3 electron 必填,作者
"license": "MIT",
"type": "commonjs", // 4 electron 使用的是node,所以此处不可以是 "module"
// 5 执行命令兼容 electron
"scripts": {
"dev": "cross-env NODE_ENV=development VITE_BUILD_TARGET=web vite",
"dev:electron": "concurrently -k "cross-env NODE_ENV=development VITE_BUILD_TARGET=electron vite" "wait-on http://localhost:8888 && cross-env NODE_ENV=development electron ."",
"build:web": "cross-env NODE_ENV=production VITE_BUILD_TARGET=web vite build",
// 5.1 必须通过 --mode production.electron 指向.env最终的配置文件
"build:electron": "vite build --mode production.electron && electron-builder",
"build:mac": "npm run build:electron -- --mac",
"build:win": "npm run build:electron -- --win",
"build:linux": "npm run build:electron -- --linux",
"preview": "vite preview --port 5000",
"preview:electron": "npm run force-clean && vite build && electron-builder",
"package": "electron-builder --dir",
"package:all": "electron-builder -mwl",
"clean": "rimraf dist dist-electron release",
"force-clean": "node clean.js",
"lint": "eslint --ext .js,.vue src",
"postinstall": "electron-builder install-app-deps",
"check-config": "electron-builder --dir --config"
},
// 6 打包配置
"build": {
"appId": "okyun",
"productName": "OKFactura",
"copyright": "Copyright © 2023 OkYun",
"directories": {
// 6.1 打包输出目录
"output": "release/${version}",
"buildResources": "build"
},
"files": [
"package.json",
{
// 6.2 将vue3项目打包到 dist-electron,再将dist-electron 放到打包后APP的/dist-electron目录中
"from": "dist-electron",
"to": "dist-electron",
"filter": [
"**/*"
]
},
{
// 6.3 将主进程文件打包到 electron/main,再将electron/mainn 放到打包后APP的/electron/main目录中
"from": "electron/main",
"to": "electron/main",
"filter": [
"**/*"
]
},
{
// 6.4 将主进程文件打包到 electron/preload,再将electron/preload 放到打包后APP的/electron/preload目录中
"from": "electron/preload",
"to": "electron/preload",
"filter": [
"**/*"
]
}
],
"win": {
"target": [
{
// 6.5 打包成 .exe文件
"target": "nsis",
// 6.5 之打包64位
"arch": [
"x64"
]
}
],
// 6.7 打包的ico
"icon": "build/icons/256x256.ico",
// 6.8 文件名
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}"
},
"nsis": {
// 6.9 不允许一键安装
"oneClick": false,
// 6.10 每台机器只安装一次,不是根据用户进行安装的
"perMachine": true,
// 6.11 允许选择安装路径
"allowToChangeInstallationDirectory": true
},
"mac": {
"target": "dmg",
"icon": "public/favicon.ico",
"category": "public.app-category.business",
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}"
},
"linux": {
"target": "AppImage",
"icon": "public/favicon.ico",
"category": "Office",
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}"
}
},
"repository": {
"type": "git",
"url": "https://github.com/xiguolangzi/Ruoyi-Web-Vue3-Electron.git"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@fingerprintjs/fingerprintjs": "^4.5.1",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.3.0",
"axios": "1.9.0",
"clipboard": "2.0.11",
"echarts": "5.6.0",
"element-plus": "2.9.9",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"lodash": "^4.17.21",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"pinia-plugin-persistedstate": "^4.2.0",
"qrcode.vue": "^3.6.0",
"splitpanes": "4.0.4",
"vue": "3.5.16",
"vue-cropper": "1.1.1",
"vue-i18n": "^11.1.6",
"vue-router": "4.5.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "5.2.4",
"7zip-bin": "^5.2.0",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"electron": "^36.5.0",
"electron-builder": "^26.0.12",
"npm-run-all": "^4.1.5",
"rimraf": "^6.0.1",
"sass-embedded": "1.89.1",
"unplugin-auto-import": "0.18.6",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "6.3.5",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1",
"wait-on": "^8.0.3"
},
"overrides": {
"quill": "2.0.2"
}
}
6 更新vite.config.js
javascript
import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_ENV, VITE_BUILD_TARGET, VITE_SERVER_URL, VITE_APP_TITLE, VITE_APP_BASE_API } = env
const isElectron = VITE_BUILD_TARGET === 'electron';
const isProduction = VITE_APP_ENV === 'production'
const isBuild = command === 'build'
console.log(`构建模式: ${mode}, 目标环境: ${VITE_BUILD_TARGET}, 是否生产环境: ${isProduction}, 请求路径代理: ${VITE_APP_BASE_API}, 是否打包操作:${isBuild}`)
return {
base: isElectron ? './' : '/', // Electron 需要相对路径
plugins: createVitePlugins(env, isBuild),
resolve: {
alias: {
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src')
},
// https://cn.vitejs.dev/config/#resolve-extensions
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
build: {
// https://vite.dev/config/build-options.html
sourcemap: isProduction ? false : 'inline',
outDir: isElectron ? 'dist-electron' : 'dist', // 打包输出路径
emptyOutDir: true, // 自动清空输出目录
assetsDir: 'assets',
chunkSizeWarningLimit: 2000,
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name].[hash][extname]'
}
}
},
// vite 相关配置
server: {
port: 8888,
host: true,
open: !isElectron, // Electron环境下不自动打开浏览器
proxy: {
// 拦截以/dev-api开头的请求
'/dev-api': {
target: VITE_SERVER_URL, // 转发到环境变量指定的后端地址
changeOrigin: true, // 修改请求头中的Origin为目标地址
rewrite: (p) => p.replace(/^/dev-api/, '') // 移除请求路径中的/dev-api前缀
},
// 拦截Swagger文档请求
'^/v3/api-docs/(.*)': {
target: VITE_SERVER_URL,
changeOrigin: true,
}
}
},
// 环境变量定义
define: {
__IS_ELECTRON__: JSON.stringify(isElectron),
__BUILD_TARGET__: JSON.stringify(VITE_BUILD_TARGET),
__APP_TITLE__: JSON.stringify(VITE_APP_TITLE),
__SERVER_URL__: JSON.stringify(VITE_SERVER_URL),
},
css: {
postcss: {
plugins: [
{
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: (atRule) => {
if (atRule.name === 'charset') {
atRule.remove()
}
}
}
}
]
}
}
}
})
7 vue3项目维护 electron 方法封装
javascript
/**
* 判断是否在Electron环境中 /src/utils/electron.js
*/
export const isElectron = () => {
return !!window.myElectronAPI
}
... ...
8 vue3项目路由维护
javascript
// /src/router/index.js
import { createWebHashHistory, createWebHistory, createRouter } from 'vue-router'
import { isElectron } from '@/utils/electron'
... ...
const router = createRouter({
history: isElectron() ? createWebHashHistory() : createWebHistory(),
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
return { top: 0 }
},
})
export default router
11 开发环境问题解决
11.1 CommonJS 模块系统 require
javascript
VUE3项目默认使用的 ES6的语法 import,不支持 require
-- 所有node引入可以使用import,担心后续会有问题,所以还是使用node的require
-- 解决办法:修改 package.json 配置
--- "type": "commonjs", // commonjs 或者 删除这个配置,之前vue3的时 "type": "module"
11.2 scp安全检查警告
11.2.1 解决方式一:维护 index.html
xml
<!-- 解决 electron csp安全检查问题, index.html文件添加的配置 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' https://at.alicdn.com http://at.alicdn.com;
connect-src 'self';
media-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
">
<!-- font-src 'self' https://at.alicdn.com http://at.alicdn.com 允许阿里字体图标的访问 -->
11.2.2 解决方式二:主进程处理 /electron/main/index.js
php
// 1 手动加载指定的环境文件(例如:.env.production.electron)
dotenv.config({ path: path.resolve(__dirname, '../../.env.production.electron') })
// 2 获取后端服务地址
const serverUrl = process.env.VITE_SERVER_URL || 'http://192.168.1.43:7070'
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
icon: 'build/icons/256x256.ico',
title: 'OkYun',
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, '../preload/preload.js'),
nodeIntegration: false,
contextIsolation: true,
sandbox: !isDev
}
})
// 3 动态注入 CSP 安全检查
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [`default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: ${serverUrl}; font-src 'self' https://at.alicdn.com http://at.alicdn.com; connect-src 'self' ${serverUrl}; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; `]
}
})
})
... ... 其他代码
}
11.3 如下的错误,不需要处理!
rust
[1] [31300:0618/065508.000:ERROR:CONSOLE:1] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
[1] [31300:0618/065508.000:ERROR:CONSOLE:1] "Request Autofill.setAddresses failed. {"code":-32601,"message":"'Autofill.setAddresses' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
11.4 打包错误
11.4.1 打包后文件路径问题!
css
处理办法:
-- 1 检查 /vite.config.js vue3项目的输出路径
build: {
... ...
outDir: isElectron ? 'dist-electron' : 'dist', // 输出路径是 "dist-electron"
... ...
}
-- 2 检查 /package.json 打包文件输出位置
"build": {
... ...,
"files": [
"package.json",
{
"from": "dist-electron",
"to": "dist-electron",
"filter": [
"**/*"
]
},
{
"from": "electron/main",
"to": "electron/main",
"filter": [
"**/*"
]
},
{
"from": "electron/preload",
"to": "electron/preload",
"filter": [
"**/*"
]
}
],
... ...
}
-- 3 检查主进程文件绑定的位置及文件名 /package.json
"main": "electron/main/index.js"
-- 4 检查预加载文件绑定的路径 /electron/main/index.js
webPreferences: {
preload: path.join(__dirname, '../preload/preload.js'),
}
11.4.2 windows打包权限问题
ini
• downloaded url=https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z duration=1.164s
⨯ cannot execute cause=exit status 2
out=
7-Zip (a) 21.07 (x64) : Copyright (c) 1999-2021 Igor Pavlov : 2021-12-26
Scanning the drive for archives:
1 file, 5635384 bytes (5504 KiB)
Extracting archive: C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235.7z
--
Path = C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235.7z
Type = 7z
Physical Size = 5635384
Headers Size = 1492
Method = LZMA2:24m LZMA:20 BCJ2
Solid = +
Blocks = 2
Sub items Errors: 2
Archives with Errors: 1
Sub items Errors: 2
errorOut=ERROR: Cannot create symbolic link : �ͻ���û����������Ȩ�� : C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235\darwin\10.12\lib\libcrypto.dylib
ERROR: Cannot create symbolic link : �ͻ���û����������Ȩ�� : C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235\darwin\10.12\lib\libssl.dylib
command='E:\java\project\Ruoyi-Vue3-Electron\node_modules\7zip-bin\win\x64\7za.exe' x -snld -bd 'C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235.7z' '-oC:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\262275235'
workingDir=C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign
• Above command failed, retrying 3 more times
• downloading url=https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z size=5.6 MB parts=1
• downloaded url=https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z duration=1.043s
⨯ cannot execute cause=exit status 2
out=
7-Zip (a) 21.07 (x64) : Copyright (c) 1999-2021 Igor Pavlov : 2021-12-26
Scanning the drive for archives:
1 file, 5635384 bytes (5504 KiB)
Extracting archive: C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\926026327.7z
--
Path = C:\Users\user\AppData\Local\electron-builder\Cache\winCodeSign\926026327.7z
Type = 7z
Physical Size = 5635384
Headers Size = 1492
Method = LZMA2:24m LZMA:20 BCJ2
Solid = +
Blocks = 2
... ... ...
-- 问题的原因:Windows 环境下解压代码签名工具包 (winCodeSign) 时遇到的权限问题
-- 解决办法:
--- package.json win打包配置不要加太多功能配置,有可能某些配置需要签名导致报错;
--- 使用管理员方式运行打包命令;
--- 问题怎么好的我也不知道,莫名其妙的就好了;