vue3项目实现electron桌面应用-项目实战

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打包配置不要加太多功能配置,有可能某些配置需要签名导致报错;
      --- 使用管理员方式运行打包命令;
      --- 问题怎么好的我也不知道,莫名其妙的就好了;
  ​
相关推荐
MiyueFE12 分钟前
为什么 JavaScript 中 Map 比 Object 更好
javascript
晴殇i23 分钟前
3 分钟掌握图片懒加载核心技术:面试攻略
前端·面试·trae
Running_C31 分钟前
一文读懂vite和webpack,秒拿offer
前端
咸鱼青菜好好味32 分钟前
node的项目实战相关
前端
hqsgdmn33 分钟前
自动导入插件unplugin-auto-import/unplugin-vue-components
前端
不知火_caleb39 分钟前
前端应用更新提示的优雅实现:如何让用户及时刷新页面?
前端
極光未晚40 分钟前
JavaScript 中 this 指向的全面解析
javascript
前端小巷子40 分钟前
跨标签页通信(四):SharedWorker
前端·面试·浏览器
风铃喵游42 分钟前
平地起高楼: 环境搭建
前端·架构
昌平第一王昭君1 小时前
基于antd pro封装的一个可拖动的modalform
前端