Vue + Electron Hands-on Project
In this chapter, we will useVue + ElectronCreate a note-taking app. This hands-on project is namedVueElectronNotes, with features covering multi-window, tray, file operations, and IPC communication. It is a very suitable project for beginners to practice on.
If you don't yet know Vue, you can first refer to:Vue Tutorial。
I. Project Features
- Desktop note-taking app (Notes)
- The main window displays the list of notes
- A child window pops up for creating new notes
- System tray icon, minimize to tray
- Local file save/read
- Secure communication between the renderer process and the main process
II. Project Initialization
1. Create a Vue project
vue create VueElectronNotes cd VueElectronNotes
Select default preset (Babel + ESLint).
2. Install Electron and dependencies
npm install --save-dev electron electron-builder concurrently wait-on npm install electron-store
electron-storeFor local data persistence
III. Main Process Configuration
electron-main.js:
Example
const { app, BrowserWindow, Tray, Menu, ipcMain } = require('electron')
const path = require('path')
const Store = require('electron-store')
let mainWindow, noteWindow, tray
const store = new Store()
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:8080')
} else {
mainWindow.loadFile('dist/index.html')
}
mainWindow.on('minimize', (e) => {
e.preventDefault()
mainWindow.hide()
})
createTray()
}
function createNoteWindow() {
noteWindow = new BrowserWindow({
width: 400,
height: 300,
parent: mainWindow,
modal: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
noteWindow.loadFile('note.html')
}
function createTray() {
tray = new Tray(path.join(__dirname, 'assets/icon.png'))
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show main window', click: () => mainWindow.show() },
{ label: 'Exit', click: () => app.quit() }
])
tray.setToolTip('VueElectronNotes')
tray.setContextMenu(contextMenu)
}
app.whenReady().then(createMainWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
ipcMain.handle('get-notes', () => {
return store.get('notes', [])
})
ipcMain.on('save-note', (event, note) => {
const notes = store.get('notes', [])
notes.push(note)
store.set('notes', notes)
mainWindow.webContents.send('update-notes', notes)
})
const path = require('path')
const Store = require('electron-store')
let mainWindow, noteWindow, tray
const store = new Store()
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:8080')
} else {
mainWindow.loadFile('dist/index.html')
}
mainWindow.on('minimize', (e) => {
e.preventDefault()
mainWindow.hide()
})
createTray()
}
function createNoteWindow() {
noteWindow = new BrowserWindow({
width: 400,
height: 300,
parent: mainWindow,
modal: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
noteWindow.loadFile('note.html')
}
function createTray() {
tray = new Tray(path.join(__dirname, 'assets/icon.png'))
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show main window', click: () => mainWindow.show() },
{ label: 'Exit', click: () => app.quit() }
])
tray.setToolTip('VueElectronNotes')
tray.setContextMenu(contextMenu)
}
app.whenReady().then(createMainWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
ipcMain.handle('get-notes', () => {
return store.get('notes', [])
})
ipcMain.on('save-note', (event, note) => {
const notes = store.get('notes', [])
notes.push(note)
store.set('notes', notes)
mainWindow.webContents.send('update-notes', notes)
})
Description:
mainWindow: Main window, displays the list of notesnoteWindow: Child window, used to create new notesTray: System tray icon and menuipcMain: The main process receives messages from the renderer process
4. Renderer Process and Vue
preload.js:
Example
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
getNotes: () => ipcRenderer.invoke('get-notes'),
saveNote: (note) => ipcRenderer.send('save-note', note),
onUpdateNotes: (callback) => ipcRenderer.on('update-notes', callback)
})
contextBridge.exposeInMainWorld('electronAPI', {
getNotes: () => ipcRenderer.invoke('get-notes'),
saveNote: (note) => ipcRenderer.send('save-note', note),
onUpdateNotes: (callback) => ipcRenderer.on('update-notes', callback)
})
src/App.vue:
Example
<template>
<div>
<h1>note list</h1>
<button @click="openNoteWindow">New Note</button>
<ul>
<li v-for="(note, index) in notes" :key="index">{{ note }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return { notes: [] }
},
methods: {
openNoteWindow() {
window.open('note.html', 'New Note', 'width=400,height=300')
}
},
mounted() {
window.electronAPI.getNotes().then((notes) => {
this.notes = notes
})
window.electronAPI.onUpdateNotes((event, notes) => {
this.notes = notes
})
}
}
</script>
<div>
<h1>note list</h1>
<button @click="openNoteWindow">New Note</button>
<ul>
<li v-for="(note, index) in notes" :key="index">{{ note }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return { notes: [] }
},
methods: {
openNoteWindow() {
window.open('note.html', 'New Note', 'width=400,height=300')
}
},
mounted() {
window.electronAPI.getNotes().then((notes) => {
this.notes = notes
})
window.electronAPI.onUpdateNotes((event, notes) => {
this.notes = notes
})
}
}
</script>
note.html + note.js:
note.html:
Example
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>New Note</title></head>
<body>
<h2>New Note</h2>
<textarea id="note"></textarea><br>
<button id="saveBtn">Save</button>
<script src="note.js"></script>
</body>
</html>
<html>
<head><meta charset="UTF-8"><title>New Note</title></head>
<body>
<h2>New Note</h2>
<textarea id="note"></textarea><br>
<button id="saveBtn">Save</button>
<script src="note.js"></script>
</body>
</html>
note.js:
Example
const saveBtn = document.getElementById('saveBtn')
saveBtn.addEventListener('click', () => {
const note = document.getElementById('note').value
if (note) {
window.electronAPI.saveNote(note)
window.close()
}
})
saveBtn.addEventListener('click', () => {
const note = document.getElementById('note').value
if (note) {
window.electronAPI.saveNote(note)
window.close()
}
})
Description:
note.htmlis a pop-up window for entering notesnote.jsCallelectronAPI.saveNoteSend to the main process- The main window will receive it
update-notesThe message updates the list
V. Run the Project
Development mode:
npm run electron:serve
- Start Vue Dev Server + Electron simultaneously
- Can debug UI in real time
Packaging the application:
npm run electron:build
- Output executable file to
dist_electron - Supports Windows/macOS/Linux