阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Vue3与Electron集成

Vue3与Electron集成

作者:陈川 阅读数:37107人阅读 分类: Vue.js

Vue3与Electron集成的优势

Vue3的Composition API和Electron的主进程/渲染进程架构天然契合。Vue3的响应式系统在Electron环境中表现稳定,特别是使用<script setup>语法时,代码组织更加清晰。Electron的Node.js集成让Vue组件可以直接调用系统API,比如文件操作:

// 在Vue组件中直接使用Node.js模块
import fs from 'fs'
import { ref } from 'vue'

const fileContent = ref('')

function readFile(path) {
  fileContent.value = fs.readFileSync(path, 'utf-8')
}

项目初始化配置

使用Vite创建基础项目结构比传统webpack更快:

npm create vite@latest electron-vue-app --template vue-ts
cd electron-vue-app
npm install electron electron-builder --save-dev

关键配置在vite.config.ts中需要处理Electron的特殊需求:

export default defineConfig({
  base: './', // 必须相对路径
  build: {
    outDir: 'dist',
    emptyOutDir: false // 避免electron-builder清理
  }
})

主进程与渲染进程通信

主进程文件electron/main.ts典型结构:

import { app, BrowserWindow, ipcMain } from 'electron'

let win: BrowserWindow | null = null

app.whenReady().then(() => {
  win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  })

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL)
  } else {
    win.loadFile('dist/index.html')
  }
})

ipcMain.handle('get-system-info', () => {
  return {
    platform: process.platform,
    version: process.getSystemVersion()
  }
})

渲染进程调用示例:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ipcRenderer } from 'electron'

const systemInfo = ref<any>(null)

onMounted(async () => {
  systemInfo.value = await ipcRenderer.invoke('get-system-info')
})
</script>

处理开发环境与生产环境

Vite开发服务器需要与Electron主进程协同工作。在package.json中添加脚本:

{
  "scripts": {
    "dev": "concurrently -k \"vite\" \"wait-on http://localhost:5173 && electron .\"",
    "build": "vite build && electron-builder"
  }
}

需要安装额外依赖:

npm install concurrently wait-on --save-dev

原生API集成实例

实现文件选择对话框的完整示例:

主进程添加处理程序:

import { dialog } from 'electron'

ipcMain.handle('open-file-dialog', async () => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile']
  })
  return result.filePaths[0]
})

Vue组件调用:

<template>
  <button @click="selectFile">选择文件</button>
  <p>已选择:{{ selectedFile }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const selectedFile = ref('')

const selectFile = async () => {
  selectedFile.value = await ipcRenderer.invoke('open-file-dialog')
}
</script>

打包与分发配置

electron-builder的基础配置:

{
  "build": {
    "appId": "com.example.electronvue",
    "win": {
      "target": "nsis",
      "icon": "build/icon.ico"
    },
    "mac": {
      "target": "dmg",
      "icon": "build/icon.icns"
    },
    "linux": {
      "target": "AppImage"
    }
  }
}

推荐的文件结构:

├── build/
│   ├── icon.icns
│   ├── icon.ico
│   └── background.png
├── electron/
│   ├── main.ts
│   └── preload.ts
├── src/
│   └── /* Vue项目标准结构 */
└── vite.config.ts

安全最佳实践

启用上下文隔离时的preload脚本示例:

electron/preload.ts:

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
  openFileDialog: () => ipcRenderer.invoke('open-file-dialog')
})

主进程配置修改:

new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true, // 启用隔离
    nodeIntegration: false // 禁用直接Node集成
  }
})

Vue组件中安全调用方式:

declare global {
  interface Window {
    electronAPI: {
      openFileDialog: () => Promise<string>
    }
  }
}

const selectFile = async () => {
  selectedFile.value = await window.electronAPI.openFileDialog()
}

调试技巧

主进程调试配置:

{
  "scripts": {
    "debug": "electron --inspect=5858 ."
  }
}

渲染进程调试时,在创建BrowserWindow时启用DevTools:

win = new BrowserWindow({
  webPreferences: { devTools: true }
})
win.webContents.openDevTools()

Vite插件集成示例:

import electron from 'vite-plugin-electron'

export default defineConfig({
  plugins: [
    electron({
      entry: 'electron/main.ts',
      onstart(options) {
        options.startup()
      }
    })
  ]
})

性能优化策略

使用Web Workers处理CPU密集型任务:

// worker.js
self.onmessage = (e) => {
  const result = heavyCalculation(e.data)
  self.postMessage(result)
}

// Vue组件中
const worker = new Worker('worker.js')
worker.postMessage(inputData)
worker.onmessage = (e) => {
  processedData.value = e.data
}

主进程使用多个窗口时的内存管理:

const secondaryWindows = new Set()

function createSecondaryWindow() {
  const win = new BrowserWindow(/*...*/)
  secondaryWindows.add(win)
  win.on('closed', () => secondaryWindows.delete(win))
}

原生菜单集成

创建应用菜单的完整示例:

import { Menu } from 'electron'

const template = [
  {
    label: '文件',
    submenu: [
      {
        label: '打开',
        click: () => win.webContents.send('menu-open-file')
      },
      { type: 'separator' },
      { role: 'quit' }
    ]
  }
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

Vue组件接收菜单事件:

<script setup>
import { ipcRenderer } from 'electron'

ipcRenderer.on('menu-open-file', async () => {
  // 调用文件选择逻辑
})
</script>

自动更新实现

主进程更新逻辑:

import { autoUpdater } from 'electron-updater'

autoUpdater.autoDownload = false

autoUpdater.on('update-available', () => {
  win.webContents.send('update-available')
})

ipcMain.handle('download-update', () => {
  autoUpdater.downloadUpdate()
})

渲染进程UI交互:

<template>
  <div v-if="updateStatus">
    {{ updateStatus }}
    <button @click="downloadUpdate">下载更新</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const updateStatus = ref('')

ipcRenderer.on('update-available', () => {
  updateStatus.value = '有新版本可用'
})

const downloadUpdate = () => {
  ipcRenderer.invoke('download-update')
}
</script>

原生通知集成

系统通知的完整实现:

import { Notification } from 'electron'

function showNotification(title: string, body: string) {
  new Notification({ title, body }).show()
}

ipcMain.handle('show-notification', (_, title, body) => {
  showNotification(title, body)
})

Vue组件封装:

<script setup lang="ts">
function showNativeNotification(title: string, options?: NotificationOptions) {
  if ('Notification' in window) {
    new Notification(title, options)
  } else {
    ipcRenderer.invoke('show-notification', title, options.body)
  }
}
</script>

多窗口管理

实现窗口管理器的示例:

class WindowManager {
  private windows = new Map<string, BrowserWindow>()

  createWindow(id: string, options: Electron.BrowserWindowConstructorOptions) {
    if (this.windows.has(id)) {
      this.windows.get(id)?.focus()
      return
    }

    const win = new BrowserWindow(options)
    this.windows.set(id, win)

    win.on('closed', () => {
      this.windows.delete(id)
    })
  }
}

Vue组件中打开新窗口:

<script setup>
import { ipcRenderer } from 'electron'

function openSettingsWindow() {
  ipcRenderer.send('create-window', 'settings', {
    width: 800,
    height: 600,
    modal: true
  })
}
</script>

本地数据库集成

SQLite集成的完整方案:

npm install better-sqlite3 --save

主进程数据库服务:

import Database from 'better-sqlite3'

const db = new Database('app.db')

ipcMain.handle('query-db', (_, sql, params) => {
  try {
    const stmt = db.prepare(sql)
    return params ? stmt.all(params) : stmt.all()
  } catch (err) {
    console.error(err)
    return { error: err.message }
  }
})

Vue组件中执行查询:

<script setup>
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const queryResults = ref([])

async function fetchData() {
  queryResults.value = await ipcRenderer.invoke(
    'query-db',
    'SELECT * FROM users WHERE active = ?',
    [1]
  )
}
</script>

跨平台样式适配

处理平台差异的样式方案:

<template>
  <div :class="[platform, isMaximized && 'maximized']">
    <!-- 内容 -->
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { ipcRenderer } from 'electron'

const platform = ref('')
const isMaximized = ref(false)

onMounted(() => {
  platform.value = await ipcRenderer.invoke('get-platform')
  
  ipcRenderer.on('window-maximized', () => {
    isMaximized.value = true
  })
  
  ipcRenderer.on('window-unmaximized', () => {
    isMaximized.value = false
  })
})
</script>

<style>
.win32 {
  font-family: 'Segoe UI', sans-serif;
}

.darwin {
  font-family: -apple-system, sans-serif;
}

.maximized {
  padding: 12px;
}
</style>

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌