Skip to content

Kapitel 12: Fortgeschrittene Praxis

In diesem Kapitel bearbeiten wir zwei umfassende Projekte, die sich an der Unternehmensentwicklung orientieren.

Praxis 4: Einfacher Browser

12.1 Anforderungsanalyse

Ein einfacher Browser mit grundlegenden Funktionen.

Benutzeranforderungen

✓ Adressleiste für URL-Eingabe
✓ Seite laden
✓ Vorwärts-/Rückwärts-Navigation
✓ Aktualisieren
✓ Fenstersteuerung
✓ Lade-Status anzeigen
✓ Fehlerbehandlung

Technische Architektur

Hauptprozess (main.js):
  - BrowserWindow erstellen
  - Navigationsevents überwachen
  - Fenstersteuerung

Renderer-Prozess (index.html/renderer.js):
  - Adressleiste
  - Navigationbuttons
  - Webview für Seitenanzeige
  - Lade-Status

Projektstruktur

simple-browser/
├── main.js           # Hauptprozess
├── preload.js        # IPC-Brücke
├── package.json
├── index.html        # UI
└── styles.css        # Styling

12.2 Kernimplementierung

main.js (Hauptprozess)

javascript
const { app, BrowserWindow, ipcMain, shell } = require('electron');
const path = require('path');

let mainWin;

function createWindow() {
  mainWin = new BrowserWindow({
    width: 1400,
    height: 900,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      webviewTag: true  // Webview aktivieren
    }
  });

  mainWin.loadFile('index.html');
  
  // Externe Links im Standardbrowser öffnen
  mainWin.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url);
    return { action: 'deny' };
  });
}

// Navigationsevents an Renderer senden
function setupNavigationEvents() {
  const contents = mainWin.webContents;

  contents.on('did-start-loading', () => {
    mainWin.webContents.send('navigation-event', { type: 'start-loading' });
  });

  contents.on('did-stop-loading', () => {
    mainWin.webContents.send('navigation-event', { type: 'stop-loading' });
  });

  contents.on('page-title-updated', (event, title) => {
    mainWin.webContents.send('navigation-event', { type: 'title-updated', title });
  });

  contents.on('page-favicon-updated', (event, favicons) => {
    mainWin.webContents.send('navigation-event', { type: 'favicon-updated', favicons });
  });

  contents.on('new-window', (event, url) => {
    event.preventDefault();
    shell.openExternal(url);
  });
}

// IPC-Handler für Navigation
ipcMain.on('navigate', (event, url) => {
  const contents = mainWin.webContents;
  
  // URL validieren
  let validatedUrl = url;
  if (!url.startsWith('http://') && !url.startsWith('https://')) {
    validatedUrl = 'https://' + url;
  }
  
  contents.loadURL(validatedUrl).catch(error => {
    mainWin.webContents.send('navigation-event', { 
      type: 'error', 
      error: error.message 
    });
  });
});

ipcMain.on('go-back', () => {
  if (mainWin.webContents.canGoBack()) {
    mainWin.webContents.goBack();
  }
});

ipcMain.on('go-forward', () => {
  if (mainWin.webContents.canGoForward()) {
    mainWin.webContents.goForward();
  }
});

ipcMain.on('reload', () => {
  mainWin.webContents.reload();
});

ipcMain.on('stop', () => {
  mainWin.webContents.stop();
});

ipcMain.on('window-minimize', () => {
  mainWin.minimize();
});

ipcMain.on('window-maximize', () => {
  if (mainWin.isMaximized()) {
    mainWin.restore();
  } else {
    mainWin.maximize();
  }
});

ipcMain.on('window-close', () => {
  mainWin.close();
});

ipcMain.handle('get-current-url', () => {
  return mainWin.webContents.getURL();
});

ipcMain.handle('get-history', () => {
  // Vereinfachte History (in echter App würde man eine Datenbank nutzen)
  return [];
});

app.whenReady().then(() => {
  createWindow();
  setupNavigationEvents();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
    setupNavigationEvents();
  }
});

preload.js (IPC-Brücke)

javascript
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('browserAPI', {
  // Navigation
  navigate: (url) => ipcRenderer.send('navigate', url),
  goBack: () => ipcRenderer.send('go-back'),
  goForward: () => ipcRenderer.send('go-forward'),
  reload: () => ipcRenderer.send('reload'),
  stop: () => ipcRenderer.send('stop'),
  
  // Fenstersteuerung
  minimize: () => ipcRenderer.send('window-minimize'),
  maximize: () => ipcRenderer.send('window-maximize'),
  close: () => ipcRenderer.send('window-close'),
  
  // Abfragen
  getCurrentUrl: () => ipcRenderer.invoke('get-current-url'),
  getHistory: () => ipcRenderer.invoke('get-history'),
  
  // Events empfangen
  onNavigationEvent: (callback) => {
    ipcRenderer.on('navigation-event', (event, data) => callback(data));
  }
});

index.html (UI)

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Einfacher Browser</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <!-- Toolbar -->
  <div class="toolbar">
    <div class="window-controls">
      <button class="control close" onclick="window.browserAPI.close()"></button>
      <button class="control minimize" onclick="window.browserAPI.minimize()"></button>
      <button class="control maximize" onclick="window.browserAPI.maximize()"></button>
    </div>

    <div class="navigation">
      <button onclick="goBack()" title="Zurück">←</button>
      <button onclick="goForward()" title="Vorwärts">→</button>
      <button onclick="reload()" title="Aktualisieren">↻</button>
    </div>

    <div class="url-bar">
      <input type="text" id="urlInput" placeholder="URL eingeben..." onkeydown="handleUrlKeydown(event)">
      <button onclick="navigate()" title="Los">Los</button>
      <button id="stopButton" onclick="stop()" title="Stop" style="display: none;">✕</button>
    </div>
  </div>

  <!-- Lade-Status -->
  <div class="status-bar" id="statusBar">
    <span id="statusText">Bereit</span>
    <div class="progress-bar" id="progressBar" style="display: none;"></div>
  </div>

  <!-- Webview für Seitenanzeige -->
  <webview 
    id="webview" 
    src="https://www.google.com" 
    autosize="on"
    style="width: 100%; height: calc(100vh - 80px);"
  ></webview>

  <!-- Fehleranzeige -->
  <div class="error-overlay" id="errorOverlay" style="display: none;">
    <div class="error-content">
      <h2>Fehler beim Laden der Seite</h2>
      <p id="errorMessage"></p>
      <button onclick="retry()">Erneut versuchen</button>
    </div>
  </div>

  <script src="renderer.js"></script>
</body>
</html>

renderer.js

javascript
// renderer.js

// DOM-Elemente
const urlInput = document.getElementById('urlInput');
const statusText = document.getElementById('statusText');
const statusBar = document.getElementById('statusBar');
const progressBar = document.getElementById('progressBar');
const errorOverlay = document.getElementById('errorOverlay');
const errorMessage = document.getElementById('errorMessage');
const stopButton = document.getElementById('stopButton');
const webview = document.getElementById('webview');

// Navigation
function navigate() {
  let url = urlInput.value.trim();
  
  if (!url) return;
  
  // Automatisch https:// hinzufügen
  if (!url.startsWith('http://') && !url.startsWith('https://')) {
    url = 'https://' + url;
    urlInput.value = url;
  }
  
  window.browserAPI.navigate(url);
  hideError();
}

function goBack() {
  window.browserAPI.goBack();
}

function goForward() {
  window.browserAPI.goForward();
}

function reload() {
  window.browserAPI.reload();
  hideError();
}

function stop() {
  window.browserAPI.stop();
}

function retry() {
  reload();
}

// URL-Eingabe (Enter-Taste)
function handleUrlKeydown(event) {
  if (event.key === 'Enter') {
    navigate();
  }
}

// Navigationsevents empfangen
window.browserAPI.onNavigationEvent((data) => {
  switch (data.type) {
    case 'start-loading':
      statusText.textContent = 'Lädt...';
      progressBar.style.display = 'block';
      stopButton.style.display = 'inline-block';
      hideError();
      break;

    case 'stop-loading':
      statusText.textContent = 'Fertig';
      progressBar.style.display = 'none';
      stopButton.style.display = 'none';
      updateUrl();
      break;

    case 'title-updated':
      document.title = data.title + ' - Einfacher Browser';
      break;

    case 'error':
      showError(data.error);
      statusText.textContent = 'Fehler';
      progressBar.style.display = 'none';
      stopButton.style.display = 'none';
      break;
  }
});

// URL aktualisieren
async function updateUrl() {
  try {
    const url = await window.browserAPI.getCurrentUrl();
    urlInput.value = url;
  } catch (error) {
    console.error('Fehler beim Abrufen der URL:', error);
  }
}

// Fehler anzeigen
function showError(message) {
  errorMessage.textContent = message;
  errorOverlay.style.display = 'flex';
}

// Fehler verstecken
function hideError() {
  errorOverlay.style.display = 'none';
}

// Webview-Events
webview.addEventListener('did-start-loading', () => {
  statusText.textContent = 'Lädt...';
  progressBar.style.display = 'block';
});

webview.addEventListener('did-stop-loading', () => {
  statusText.textContent = 'Fertig';
  progressBar.style.display = 'none';
  updateUrl();
});

webview.addEventListener('page-title-updated', (event) => {
  document.title = event.title + ' - Einfacher Browser';
});

webview.addEventListener('did-fail-load', (event) => {
  if (event.errorCode !== -3) {  // -3 = Abbruch
    showError(event.errorDescription || 'Unbekannter Fehler');
    statusText.textContent = 'Fehler';
    progressBar.style.display = 'none';
  }
});

// Initial URL setzen
urlInput.value = 'https://www.google.com';

// Tastaturkurzel
document.addEventListener('keydown', (event) => {
  if (event.ctrlKey || event.metaKey) {
    switch (event.key) {
      case 'l':
        event.preventDefault();
        urlInput.select();
        break;
      case 'r':
        event.preventDefault();
        reload();
        break;
    }
  }
});

styles.css

css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  background: #f0f0f0;
  overflow: hidden;
}

/* Toolbar */
.toolbar {
  display: flex;
  align-items: center;
  padding: 8px;
  background: #f5f5f5;
  border-bottom: 1px solid #ccc;
  gap: 8px;
  height: 48px;
}

/* Fenstersteuerung (macOS-Style) */
.window-controls {
  display: flex;
  gap: 6px;
}

.control {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  padding: 0;
}

.control.close {
  background: #ff5f57;
}

.control.minimize {
  background: #ffbd2e;
}

.control.maximize {
  background: #28ca42;
}

/* Navigation */
.navigation {
  display: flex;
  gap: 4px;
}

.navigation button {
  width: 32px;
  height: 32px;
  border: none;
  background: white;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.navigation button:hover {
  background: #e0e0e0;
}

/* URL-Leiste */
.url-bar {
  flex: 1;
  display: flex;
  gap: 4px;
}

#urlInput {
  flex: 1;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 16px;
  font-size: 14px;
  outline: none;
}

#urlInput:focus {
  border-color: #4A90D9;
}

#urlInput {
  flex: 1;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 16px;
  font-size: 14px;
  outline: none;
}

.url-bar button {
  padding: 6px 16px;
  border: none;
  background: #4A90D9;
  color: white;
  border-radius: 4px;
  cursor: pointer;
}

.url-bar button:hover {
  background: #357ABD;
}

/* Statusleiste */
.status-bar {
  padding: 4px 12px;
  background: #e8e8e8;
  font-size: 12px;
  color: #666;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 24px;
}

.progress-bar {
  flex: 1;
  height: 2px;
  background: #e0e0e0;
  border-radius: 1px;
  overflow: hidden;
}

.progress-bar::after {
  content: '';
  display: block;
  width: 40%;
  height: 100%;
  background: #4A90D9;
  animation: progress 1s infinite linear;
}

@keyframes progress {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

/* Fehleroverlay */
.error-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.error-content {
  background: white;
  padding: 32px;
  border-radius: 8px;
  text-align: center;
  max-width: 400px;
}

.error-content h2 {
  color: #d32f2f;
  margin-bottom: 16px;
}

.error-content button {
  margin-top: 16px;
  padding: 8px 24px;
  background: #4A90D9;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.error-content button:hover {
  background: #357ABD;
}

12.3 Praxis: Browser-Kernfunktionen implementieren

Vollständiges Beispiel siehe oben.


Praxis 5: Electron + Vue Desktop-Anwendung

12.4 Anforderungsanalyse

Eine Vue-basierte Desktop-Anwendung mit Interaktion zwischen Frontend und Hauptprozess.

Benutzeranforderungen

✓ Vue-Projekt in Electron integrieren
✓ Datendarstellung im Frontend
✓ Benutzerinteraktion
✓ Lokaler Speicher (electron-store)
✓ IPC-Kommunikation

12.5 Kernimplementierung

Projektsetup

bash
# Vue-Projekt erstellen
vue create vue-electron-app
cd vue-electron-app

# Electron-Plugin hinzufügen
vue add electron-builder

src/background.js (Hauptprozess)

javascript
import { app, protocol, BrowserWindow } from 'electron';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import path from 'path';
import Store from 'electron-store';

const store = new Store();

protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
]);

async function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  });

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
    if (!process.env.IS_TEST) win.webContents.openDevTools();
  } else {
    createProtocol('app');
    win.loadURL('app://./index.html');
  }
}

// IPC-Handler
const { ipcMain } = require('electron');

// Daten abrufen
ipcMain.handle('get-data', async (event) => {
  const data = store.get('appData') || {
    users: [
      { id: 1, name: 'Max Mustermann', email: 'max@example.com' },
      { id: 2, name: 'Anna Schmidt', email: 'anna@example.com' }
    ],
    settings: {
      theme: 'light',
      language: 'de'
    }
  };
  return data;
});

// Daten speichern
ipcMain.handle('save-data', async (event, data) => {
  store.set('appData', data);
  return { success: true };
});

// Einstellungen abrufen
ipcMain.handle('get-settings', (event) => {
  return store.get('settings') || { theme: 'light', language: 'de' };
});

// Einstellungen speichern
ipcMain.handle('save-settings', (event, settings) => {
  store.set('settings', settings);
  
  // Theme anwenden
  const win = BrowserWindow.getFocusedWindow();
  if (win) {
    win.webContents.send('settings-changed', settings);
  }
  
  return { success: true };
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

app.on('ready', async () => {
  createWindow();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

public/preload.js (IPC-Brücke)

javascript
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  // Datenverwaltung
  getData: () => ipcRenderer.invoke('get-data'),
  saveData: (data) => ipcRenderer.invoke('save-data', data),
  
  // Einstellungen
  getSettings: () => ipcRenderer.invoke('get-settings'),
  saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
  
  // Events
  onSettingsChanged: (callback) => {
    ipcRenderer.on('settings-changed', (event, settings) => callback(settings));
  }
});

src/App.vue

vue
<template>
  <div id="app" :class="`theme-${settings.theme}`">
    <header>
      <h1>Vue + Electron App</h1>
      <nav>
        <router-link to="/">Startseite</router-link>
        <router-link to="/users">Benutzer</router-link>
        <router-link to="/settings">Einstellungen</router-link>
      </nav>
    </header>

    <main>
      <router-view />
    </main>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      settings: {
        theme: 'light',
        language: 'de'
      }
    };
  },
  async mounted() {
    // Einstellungen laden
    this.settings = await window.electronAPI.getSettings();
    
    // Auf Einstellungsänderungen hören
    window.electronAPI.onSettingsChanged((newSettings) => {
      this.settings = newSettings;
      this.applyTheme();
    });
    
    this.applyTheme();
  },
  methods: {
    applyTheme() {
      if (this.settings.theme === 'dark') {
        document.body.classList.add('dark-theme');
      } else {
        document.body.classList.remove('dark-theme');
      }
    }
  }
};
</script>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
}

body.dark-theme {
  background: #333;
  color: white;
}

#app {
  min-height: 100vh;
}

header {
  background: #4A90D9;
  color: white;
  padding: 16px;
}

header h1 {
  margin-bottom: 8px;
}

nav a {
  color: white;
  text-decoration: none;
  margin-right: 16px;
  padding: 4px 8px;
  border-radius: 4px;
}

nav a:hover {
  background: rgba(255, 255, 255, 0.2);
}

main {
  padding: 20px;
}

.theme-dark main {
  background: #333;
  color: white;
}
</style>

src/views/Home.vue

vue
<template>
  <div class="home">
    <h2>Willkommen</h2>
    <p>Dies ist eine Vue + Electron Desktop-Anwendung.</p>
    
    <div class="stats">
      <div class="stat-card">
        <h3>Benutzer</h3>
        <p class="stat-number">{{ userCount }}</p>
      </div>
      
      <div class="stat-card">
        <h3>Theme</h3>
        <p class="stat-text">{{ settings.theme }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      userCount: 0,
      settings: {
        theme: 'light',
        language: 'de'
      }
    };
  },
  async mounted() {
    const data = await window.electronAPI.getData();
    this.userCount = data.users ? data.users.length : 0;
    
    this.settings = await window.electronAPI.getSettings();
  }
};
</script>

<style scoped>
.home {
  max-width: 800px;
  margin: 0 auto;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin-top: 24px;
}

.stat-card {
  background: #f5f5f5;
  padding: 20px;
  border-radius: 8px;
  text-align: center;
}

.stat-number {
  font-size: 32px;
  font-weight: bold;
  color: #4A90D9;
  margin-top: 8px;
}

.stat-text {
  font-size: 20px;
  margin-top: 8px;
}

.theme-dark .stat-card {
  background: #555;
  color: white;
}
</style>

src/views/Users.vue

vue
<template>
  <div class="users">
    <h2>Benutzerverwaltung</h2>
    
    <button @click="showAddForm = !showAddForm" class="add-button">
      {{ showAddForm ? 'Abbrechen' : 'Benutzer hinzufügen' }}
    </button>
    
    <div v-if="showAddForm" class="add-form">
      <input v-model="newUser.name" placeholder="Name">
      <input v-model="newUser.email" placeholder="E-Mail">
      <button @click="addUser">Speichern</button>
    </div>
    
    <table class="user-table">
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>E-Mail</th>
          <th>Aktionen</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in users" :key="user.id">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
          <td>
            <button @click="deleteUser(user.id)" class="delete-button">Löschen</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  name: 'Users',
  data() {
    return {
      users: [],
      showAddForm: false,
      newUser: {
        name: '',
        email: ''
      }
    };
  },
  async mounted() {
    await this.loadUsers();
  },
  methods: {
    async loadUsers() {
      const data = await window.electronAPI.getData();
      this.users = data.users || [];
    },
    async addUser() {
      if (!this.newUser.name || !this.newUser.email) {
        alert('Bitte Name und E-Mail eingeben');
        return;
      }
      
      const data = await window.electronAPI.getData();
      const newId = this.users.length > 0 
        ? Math.max(...this.users.map(u => u.id)) + 1 
        : 1;
      
      const updatedUsers = [...this.users, { ...this.newUser, id: newId }];
      
      await window.electronAPI.saveData({
        ...data,
        users: updatedUsers
      });
      
      this.showAddForm = false;
      this.newUser = { name: '', email: '' };
      await this.loadUsers();
    },
    async deleteUser(id) {
      if (!confirm('Möchten Sie diesen Benutzer wirklich löschen?')) {
        return;
      }
      
      const data = await window.electronAPI.getData();
      const updatedUsers = this.users.filter(u => u.id !== id);
      
      await window.electronAPI.saveData({
        ...data,
        users: updatedUsers
      });
      
      await this.loadUsers();
    }
  }
};
</script>

<style scoped>
.users {
  max-width: 800px;
  margin: 0 auto;
}

.add-button {
  padding: 8px 16px;
  background: #4A90D9;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-bottom: 16px;
}

.add-form {
  background: #f5f5f5;
  padding: 16px;
  border-radius: 8px;
  margin-bottom: 16px;
}

.add-form input {
  display: block;
  width: 100%;
  padding: 8px;
  margin-bottom: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.add-form button {
  padding: 8px 16px;
  background: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.user-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 16px;
}

.user-table th,
.user-table td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #ccc;
}

.user-table th {
  background: #f5f5f5;
}

.delete-button {
  padding: 4px 12px;
  background: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.theme-dark .add-form {
  background: #555;
}

.theme-dark .user-table th {
  background: #555;
}
</style>

src/views/Settings.vue

vue
<template>
  <div class="settings">
    <h2>Einstellungen</h2>
    
    <div class="setting-item">
      <label>Theme:</label>
      <select v-model="settings.theme" @change="saveSettings">
        <option value="light">Hell</option>
        <option value="dark">Dunkel</option>
      </select>
    </div>
    
    <div class="setting-item">
      <label>Sprache:</label>
      <select v-model="settings.language" @change="saveSettings">
        <option value="de">Deutsch</option>
        <option value="en">English</option>
      </select>
    </div>
    
    <div class="setting-item">
      <label>Benachrichtigungen:</label>
      <input 
        type="checkbox" 
        v-model="settings.notifications" 
        @change="saveSettings"
      >
    </div>
  </div>
</template>

<script>
export default {
  name: 'Settings',
  data() {
    return {
      settings: {
        theme: 'light',
        language: 'de',
        notifications: true
      }
    };
  },
  async mounted() {
    this.settings = await window.electronAPI.getSettings();
  },
  methods: {
    async saveSettings() {
      await window.electronAPI.saveSettings(this.settings);
      alert('Einstellungen gespeichert!');
    }
  }
};
</script>

<style scoped>
.settings {
  max-width: 600px;
  margin: 0 auto;
}

.setting-item {
  margin: 16px 0;
  display: flex;
  align-items: center;
  gap: 16px;
}

.setting-item label {
  min-width: 150px;
  font-weight: bold;
}

.setting-item select,
.setting-item input {
  padding: 8px;
  font-size: 14px;
}
</style>

12.6 Praxis: Vue+Electron-Projekt aufsetzen und interagieren

Vollständiges Beispiel siehe oben.

Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • Einen einfachen Browser mit Navigation zu implementieren
  • Webview-Events zu überwachen und zu verarbeiten
  • Eine Vue+Electron-Anwendung aufzusetzen
  • IPC-Kommunikation zwischen Vue und Hauptprozess zu realisieren
  • Lokale Datenspeicherung mit electron-store zu implementieren

Im nächsten Kapitel werden wir das Packaging und die Veröffentlichung behandeln.

Frei für alle Anfänger