Skip to content

第11章:基础实战

实战1:简易桌面记事本

11.1 需求分析

创建一个简易的桌面记事本应用,实现以下功能:

  • 创建应用窗口
  • 实现文本编辑功能
  • 本地文件保存
  • 文件打开功能
  • 基本的编辑操作(新建、保存、打开)

11.2 核心实现

  • BrowserWindow:创建应用窗口,设置窗口大小、标题等属性
  • dialog模块:使用文件对话框选择文件
  • fs模块:读写本地文件
  • IPC通信:主进程与渲染进程之间的通信

11.3 实现步骤

  1. 创建项目结构

    notepad-app/
    ├── main.js
    ├── index.html
    ├── package.json
    └── assets/
        └── icon.png
  2. 修改 package.json

    json
    {
      "name": "notepad-app",
      "version": "1.0.0",
      "description": "简易桌面记事本",
      "main": "main.js",
      "scripts": {
        "start": "electron ."
      },
      "devDependencies": {
        "electron": "^18.0.0"
      }
    }
  3. 修改 main.js

    javascript
    const { app, BrowserWindow, dialog, ipcMain, Menu } = require('electron')
    const fs = require('fs')
    const path = require('path')
    
    let mainWindow
    let currentFile = null
    
    function createWindow() {
      mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
      })
    
      mainWindow.loadFile('index.html')
      mainWindow.webContents.openDevTools()
    
      // 创建菜单
      createMenu()
    
      mainWindow.on('closed', () => {
        mainWindow = null
      })
    }
    
    function createMenu() {
      const template = [
        {
          label: '文件',
          submenu: [
            {
              label: '新建',
              accelerator: 'CmdOrCtrl+N',
              click: () => {
                mainWindow.webContents.send('new-file')
                currentFile = null
              }
            },
            {
              label: '打开',
              accelerator: 'CmdOrCtrl+O',
              click: openFile
            },
            {
              label: '保存',
              accelerator: 'CmdOrCtrl+S',
              click: saveFile
            },
            {
              label: '另存为',
              accelerator: 'CmdOrCtrl+Shift+S',
              click: saveAsFile
            },
            { type: 'separator' },
            {
              label: '退出',
              accelerator: 'CmdOrCtrl+Q',
              click: () => app.quit()
            }
          ]
        },
        {
          label: '编辑',
          submenu: [
            { role: 'undo' },
            { role: 'redo' },
            { type: 'separator' },
            { role: 'cut' },
            { role: 'copy' },
            { role: 'paste' },
            { role: 'selectall' }
          ]
        }
      ]
    
      const menu = Menu.buildFromTemplate(template)
      Menu.setApplicationMenu(menu)
    }
    
    function openFile() {
      dialog.showOpenDialog(mainWindow, {
        properties: ['openFile'],
        filters: [
          { name: '文本文件', extensions: ['txt'] },
          { name: '所有文件', extensions: ['*'] }
        ]
      }).then(result => {
        if (!result.canceled && result.filePaths.length > 0) {
          currentFile = result.filePaths[0]
          fs.readFile(currentFile, 'utf8', (err, data) => {
            if (err) {
              dialog.showMessageBox({
                type: 'error',
                title: '错误',
                message: '读取文件失败',
                buttons: ['确定']
              })
              return
            }
            mainWindow.webContents.send('file-opened', data, currentFile)
          })
        }
      })
    }
    
    function saveFile() {
      if (currentFile) {
        mainWindow.webContents.send('get-content', (event, content) => {
          fs.writeFile(currentFile, content, 'utf8', (err) => {
            if (err) {
              dialog.showMessageBox({
                type: 'error',
                title: '错误',
                message: '保存文件失败',
                buttons: ['确定']
              })
              return
            }
            dialog.showMessageBox({
              type: 'info',
              title: '成功',
              message: '文件保存成功',
              buttons: ['确定']
            })
          })
        })
      } else {
        saveAsFile()
      }
    }
    
    function saveAsFile() {
      dialog.showSaveDialog(mainWindow, {
        filters: [
          { name: '文本文件', extensions: ['txt'] },
          { name: '所有文件', extensions: ['*'] }
        ]
      }).then(result => {
        if (!result.canceled && result.filePath) {
          currentFile = result.filePath
          mainWindow.webContents.send('get-content', (event, content) => {
            fs.writeFile(currentFile, content, 'utf8', (err) => {
              if (err) {
                dialog.showMessageBox({
                  type: 'error',
                  title: '错误',
                  message: '保存文件失败',
                  buttons: ['确定']
                })
                return
              }
              dialog.showMessageBox({
                type: 'info',
                title: '成功',
                message: '文件保存成功',
                buttons: ['确定']
              })
            })
          })
        }
      })
    }
    
    // IPC 事件处理
    ipcMain.on('new-file', () => {
      currentFile = null
    })
    
    ipcMain.on('get-content-reply', (event, content) => {
      // 处理内容获取后的逻辑
    })
    
    app.whenReady().then(createWindow)
    
    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit()
    })
    
    app.on('activate', function () {
      if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
  4. 修改 index.html

    html
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>简易记事本</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          font-family: Arial, sans-serif;
        }
        #editor {
          width: 100%;
          height: 100vh;
          border: none;
          outline: none;
          padding: 20px;
          font-size: 16px;
          resize: none;
        }
      </style>
    </head>
    <body>
      <textarea id="editor" placeholder="请输入内容..."></textarea>
      
      <script>
        const { ipcRenderer } = require('electron')
        const editor = document.getElementById('editor')
    
        // 监听新建文件事件
        ipcRenderer.on('new-file', () => {
          editor.value = ''
          document.title = '未命名 - 记事本'
        })
    
        // 监听文件打开事件
        ipcRenderer.on('file-opened', (event, content, filePath) => {
          editor.value = content
          document.title = `${filePath} - 记事本`
        })
    
        // 监听获取内容事件
        ipcRenderer.on('get-content', (event) => {
          event.sender.send('get-content-reply', editor.value)
        })
    
        // 监听快捷键
        document.addEventListener('keydown', (e) => {
          // Ctrl/Cmd + S 保存
          if ((e.ctrlKey || e.metaKey) && e.key === 's') {
            e.preventDefault()
            ipcRenderer.send('save-file')
          }
          // Ctrl/Cmd + O 打开
          if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
            e.preventDefault()
            ipcRenderer.send('open-file')
          }
          // Ctrl/Cmd + N 新建
          if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
            e.preventDefault()
            ipcRenderer.send('new-file')
          }
        })
      </script>
    </body>
    </html>
  5. 运行应用

    bash
    npm install
    npm start

实战2:系统通知工具

11.4 需求分析

创建一个系统通知工具,实现以下功能:

  • 定时发送系统通知
  • 自定义通知内容与图标
  • 通知点击跳转功能
  • 通知管理(添加、删除、编辑通知)

11.5 核心实现

  • Notification模块:创建和管理系统通知
  • 定时器:使用 setIntervalsetTimeout 实现定时功能
  • 主进程控制:在主进程中管理通知逻辑
  • IPC通信:主进程与渲染进程之间的通信

11.6 实现步骤

  1. 创建项目结构

    notification-app/
    ├── main.js
    ├── index.html
    ├── package.json
    └── assets/
        └── icon.png
  2. 修改 package.json

    json
    {
      "name": "notification-app",
      "version": "1.0.0",
      "description": "系统通知工具",
      "main": "main.js",
      "scripts": {
        "start": "electron ."
      },
      "devDependencies": {
        "electron": "^18.0.0"
      }
    }
  3. 修改 main.js

    javascript
    const { app, BrowserWindow, Notification, ipcMain } = require('electron')
    const path = require('path')
    
    let mainWindow
    let notificationTimers = []
    
    function createWindow() {
      mainWindow = new BrowserWindow({
        width: 600,
        height: 400,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
      })
    
      mainWindow.loadFile('index.html')
      mainWindow.webContents.openDevTools()
    
      mainWindow.on('closed', () => {
        // 清理定时器
        notificationTimers.forEach(timer => clearInterval(timer))
        mainWindow = null
      })
    }
    
    // 发送通知
    function sendNotification(title, body, icon) {
      const notification = new Notification({
        title: title,
        body: body,
        icon: icon || path.join(__dirname, 'assets', 'icon.png')
      })
    
      notification.on('click', () => {
        mainWindow.show()
      })
    
      notification.show()
    }
    
    // 设置定时通知
    function setNotificationTimer(title, body, icon, interval) {
      // 立即发送一次通知
      sendNotification(title, body, icon)
      
      // 设置定时器
      const timer = setInterval(() => {
        sendNotification(title, body, icon)
      }, interval * 1000)
    
      notificationTimers.push(timer)
      return notificationTimers.length - 1
    }
    
    // 取消定时通知
    function cancelNotificationTimer(index) {
      if (notificationTimers[index]) {
        clearInterval(notificationTimers[index])
        notificationTimers[index] = null
        return true
      }
      return false
    }
    
    // IPC 事件处理
    ipcMain.handle('send-notification', (event, title, body, icon) => {
      sendNotification(title, body, icon)
      return '通知已发送'
    })
    
    ipcMain.handle('set-notification-timer', (event, title, body, icon, interval) => {
      const index = setNotificationTimer(title, body, icon, interval)
      return index
    })
    
    ipcMain.handle('cancel-notification-timer', (event, index) => {
      return cancelNotificationTimer(index)
    })
    
    app.whenReady().then(createWindow)
    
    app.on('window-all-closed', function () {
      // 清理定时器
      notificationTimers.forEach(timer => clearInterval(timer))
      if (process.platform !== 'darwin') app.quit()
    })
    
    app.on('activate', function () {
      if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
  4. 修改 index.html

    html
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>系统通知工具</title>
      <style>
        body {
          font-family: Arial, sans-serif;
          margin: 20px;
          padding: 0;
          background-color: #f0f0f0;
        }
        .container {
          max-width: 500px;
          margin: 0 auto;
          background-color: white;
          padding: 20px;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        h1 {
          color: #333;
          text-align: center;
        }
        .form-group {
          margin: 15px 0;
        }
        label {
          display: block;
          margin-bottom: 5px;
          font-weight: bold;
        }
        input, textarea, select {
          width: 100%;
          padding: 8px;
          border: 1px solid #ddd;
          border-radius: 4px;
          box-sizing: border-box;
        }
        textarea {
          resize: vertical;
          min-height: 100px;
        }
        button {
          padding: 10px 15px;
          background-color: #4CAF50;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          margin-right: 10px;
        }
        button:hover {
          background-color: #45a049;
        }
        .timers-list {
          margin-top: 20px;
          border-top: 1px solid #ddd;
          padding-top: 15px;
        }
        .timer-item {
          padding: 10px;
          background-color: #f9f9f9;
          border: 1px solid #ddd;
          border-radius: 4px;
          margin-bottom: 10px;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .timer-info {
          flex: 1;
        }
        .timer-actions {
          display: flex;
          gap: 5px;
        }
        .cancel-btn {
          background-color: #f44336;
        }
        .cancel-btn:hover {
          background-color: #d32f2f;
        }
      </style>
    </head>
    <body>
      <div class="container">
        <h1>系统通知工具</h1>
        
        <div class="form-group">
          <label for="title">通知标题</label>
          <input type="text" id="title" placeholder="请输入通知标题">
        </div>
        
        <div class="form-group">
          <label for="body">通知内容</label>
          <textarea id="body" placeholder="请输入通知内容"></textarea>
        </div>
        
        <div class="form-group">
          <label for="interval">通知间隔(秒)</label>
          <input type="number" id="interval" min="1" value="60">
        </div>
        
        <div style="text-align: center; margin: 20px 0;">
          <button id="send-btn">立即发送通知</button>
          <button id="set-timer-btn">设置定时通知</button>
        </div>
        
        <div class="timers-list">
          <h3>定时通知列表</h3>
          <div id="timers-container">
            <!-- 定时通知项将在这里动态添加 -->
          </div>
        </div>
      </div>
      
      <script>
        const { ipcRenderer } = require('electron')
        const titleInput = document.getElementById('title')
        const bodyInput = document.getElementById('body')
        const intervalInput = document.getElementById('interval')
        const sendBtn = document.getElementById('send-btn')
        const setTimerBtn = document.getElementById('set-timer-btn')
        const timersContainer = document.getElementById('timers-container')
    
        let timers = []
    
        // 发送通知
        sendBtn.addEventListener('click', async () => {
          const title = titleInput.value
          const body = bodyInput.value
          
          if (!title || !body) {
            alert('请填写通知标题和内容')
            return
          }
    
          await ipcRenderer.invoke('send-notification', title, body)
          alert('通知已发送')
        })
    
        // 设置定时通知
        setTimerBtn.addEventListener('click', async () => {
          const title = titleInput.value
          const body = bodyInput.value
          const interval = parseInt(intervalInput.value)
          
          if (!title || !body || isNaN(interval)) {
            alert('请填写完整的通知信息')
            return
          }
    
          const index = await ipcRenderer.invoke('set-notification-timer', title, body, null, interval)
          
          // 添加到列表
          addTimerToList(index, title, body, interval)
          alert('定时通知已设置')
        })
    
        // 添加定时器到列表
        function addTimerToList(index, title, body, interval) {
          const timerItem = document.createElement('div')
          timerItem.className = 'timer-item'
          timerItem.dataset.index = index
          
          timerItem.innerHTML = `
            <div class="timer-info">
              <strong>${title}</strong>
              <p>${body}</p>
              <small>间隔: ${interval}秒</small>
            </div>
            <div class="timer-actions">
              <button class="cancel-btn" onclick="cancelTimer(${index})">取消</button>
            </div>
          `
          
          timersContainer.appendChild(timerItem)
          timers.push({ index, title, body, interval })
        }
    
        // 取消定时器
        window.cancelTimer = async (index) => {
          const success = await ipcRenderer.invoke('cancel-notification-timer', index)
          if (success) {
            const timerItem = document.querySelector(`.timer-item[data-index="${index}"]`)
            if (timerItem) {
              timerItem.remove()
            }
            alert('定时通知已取消')
          }
        }
      </script>
    </body>
    </html>
  5. 运行应用

    bash
    npm install
    npm start

实战3:网络请求工具

11.7 需求分析

创建一个网络请求工具,实现以下功能:

  • 发送 GET/POST 请求
  • 展示响应数据
  • 本地存储请求记录
  • 查看历史请求记录

11.8 核心实现

  • axios:发送网络请求
  • electron-store:本地存储请求记录
  • IPC通信:主进程与渲染进程之间的通信
  • dialog模块:保存响应数据到文件

11.9 实现步骤

  1. 创建项目结构

    network-tool/
    ├── main.js
    ├── index.html
    ├── package.json
    └── assets/
        └── icon.png
  2. 修改 package.json

    json
    {
      "name": "network-tool",
      "version": "1.0.0",
      "description": "网络请求工具",
      "main": "main.js",
      "scripts": {
        "start": "electron ."
      },
      "devDependencies": {
        "electron": "^18.0.0"
      },
      "dependencies": {
        "axios": "^0.27.2",
        "electron-store": "^8.0.1"
      }
    }
  3. 修改 main.js

    javascript
    const { app, BrowserWindow, ipcMain, dialog } = require('electron')
    const axios = require('axios')
    const Store = require('electron-store')
    const path = require('path')
    const fs = require('fs')
    
    // 创建存储实例
    const store = new Store({
      name: 'network-tool',
      defaults: {
        requestHistory: []
      }
    })
    
    let mainWindow
    
    function createWindow() {
      mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
      })
    
      mainWindow.loadFile('index.html')
      mainWindow.webContents.openDevTools()
    
      mainWindow.on('closed', () => {
        mainWindow = null
      })
    }
    
    // 发送 GET 请求
    async function sendGetRequest(url, headers = {}) {
      try {
        const response = await axios.get(url, { headers })
        return response
      } catch (error) {
        throw error
      }
    }
    
    // 发送 POST 请求
    async function sendPostRequest(url, data = {}, headers = {}) {
      try {
        const response = await axios.post(url, data, { headers })
        return response
      } catch (error) {
        throw error
      }
    }
    
    // 保存请求记录
    function saveRequestHistory(request) {
      const history = store.get('requestHistory')
      history.unshift(request)
      // 只保留最近 50 条记录
      if (history.length > 50) {
        history.splice(50)
      }
      store.set('requestHistory', history)
    }
    
    // 获取请求历史
    function getRequestHistory() {
      return store.get('requestHistory')
    }
    
    // 清空请求历史
    function clearRequestHistory() {
      store.set('requestHistory', [])
    }
    
    // 保存响应数据到文件
    function saveResponseToFile(data) {
      dialog.showSaveDialog(mainWindow, {
        filters: [
          { name: 'JSON 文件', extensions: ['json'] },
          { name: '文本文件', extensions: ['txt'] },
          { name: '所有文件', extensions: ['*'] }
        ]
      }).then(result => {
        if (!result.canceled && result.filePath) {
          fs.writeFile(result.filePath, JSON.stringify(data, null, 2), 'utf8', (err) => {
            if (err) {
              dialog.showMessageBox({
                type: 'error',
                title: '错误',
                message: '保存文件失败',
                buttons: ['确定']
              })
              return
            }
            dialog.showMessageBox({
              type: 'info',
              title: '成功',
              message: '文件保存成功',
              buttons: ['确定']
            })
          })
        }
      })
    }
    
    // IPC 事件处理
    ipcMain.handle('send-get-request', async (event, url, headers) => {
      try {
        const response = await sendGetRequest(url, headers)
        const requestData = {
          type: 'GET',
          url: url,
          headers: headers,
          timestamp: new Date().toISOString(),
          response: {
            status: response.status,
            data: response.data,
            headers: response.headers
          }
        }
        saveRequestHistory(requestData)
        return requestData
      } catch (error) {
        throw error
      }
    })
    
    ipcMain.handle('send-post-request', async (event, url, data, headers) => {
      try {
        const response = await sendPostRequest(url, data, headers)
        const requestData = {
          type: 'POST',
          url: url,
          data: data,
          headers: headers,
          timestamp: new Date().toISOString(),
          response: {
            status: response.status,
            data: response.data,
            headers: response.headers
          }
        }
        saveRequestHistory(requestData)
        return requestData
      } catch (error) {
        throw error
      }
    })
    
    ipcMain.handle('get-request-history', () => {
      return getRequestHistory()
    })
    
    ipcMain.handle('clear-request-history', () => {
      clearRequestHistory()
      return '历史记录已清空'
    })
    
    ipcMain.handle('save-response', (event, data) => {
      saveResponseToFile(data)
      return '保存成功'
    })
    
    app.whenReady().then(createWindow)
    
    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit()
    })
    
    app.on('activate', function () {
      if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
  4. 修改 index.html

    html
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>网络请求工具</title>
      <style>
        body {
          font-family: Arial, sans-serif;
          margin: 0;
          padding: 0;
          background-color: #f0f0f0;
        }
        .container {
          display: flex;
          height: 100vh;
        }
        .left-panel {
          width: 300px;
          background-color: #f9f9f9;
          border-right: 1px solid #ddd;
          padding: 15px;
          overflow-y: auto;
        }
        .right-panel {
          flex: 1;
          padding: 20px;
          overflow-y: auto;
        }
        h1 {
          color: #333;
          text-align: center;
          margin-top: 0;
        }
        .form-group {
          margin: 15px 0;
        }
        label {
          display: block;
          margin-bottom: 5px;
          font-weight: bold;
        }
        input, textarea, select {
          width: 100%;
          padding: 8px;
          border: 1px solid #ddd;
          border-radius: 4px;
          box-sizing: border-box;
        }
        textarea {
          resize: vertical;
          min-height: 100px;
        }
        .button-group {
          display: flex;
          gap: 10px;
          margin: 20px 0;
        }
        button {
          padding: 10px 15px;
          background-color: #4CAF50;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          flex: 1;
        }
        button:hover {
          background-color: #45a049;
        }
        .history-section {
          margin-top: 20px;
        }
        .history-item {
          padding: 10px;
          background-color: white;
          border: 1px solid #ddd;
          border-radius: 4px;
          margin-bottom: 10px;
          cursor: pointer;
        }
        .history-item:hover {
          background-color: #f0f0f0;
        }
        .history-item .method {
          font-weight: bold;
          margin-right: 10px;
        }
        .history-item .url {
          font-size: 14px;
          color: #666;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .history-item .time {
          font-size: 12px;
          color: #999;
          margin-top: 5px;
        }
        .response-section {
          margin-top: 20px;
          padding: 15px;
          background-color: white;
          border: 1px solid #ddd;
          border-radius: 4px;
        }
        .response-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 15px;
        }
        .response-status {
          font-weight: bold;
        }
        .response-body {
          background-color: #f9f9f9;
          padding: 15px;
          border-radius: 4px;
          font-family: monospace;
          white-space: pre-wrap;
          word-wrap: break-word;
        }
        .error {
          color: red;
          margin-top: 10px;
        }
      </style>
    </head>
    <body>
      <div class="container">
        <!-- 左侧历史记录面板 -->
        <div class="left-panel">
          <h1>请求历史</h1>
          <button onclick="clearHistory()">清空历史</button>
          <div class="history-section" id="history-container">
            <!-- 历史记录将在这里动态添加 -->
          </div>
        </div>
        
        <!-- 右侧请求面板 -->
        <div class="right-panel">
          <h1>网络请求工具</h1>
          
          <div class="form-group">
            <label for="method">请求方法</label>
            <select id="method">
              <option value="GET">GET</option>
              <option value="POST">POST</option>
            </select>
          </div>
          
          <div class="form-group">
            <label for="url">请求 URL</label>
            <input type="text" id="url" placeholder="https://api.example.com" value="https://jsonplaceholder.typicode.com/todos/1">
          </div>
          
          <div class="form-group">
            <label for="headers">请求头(JSON 格式)</label>
            <textarea id="headers" placeholder='{"Content-Type": "application/json"}'>{"Content-Type": "application/json"}</textarea>
          </div>
          
          <div class="form-group">
            <label for="data">请求数据(JSON 格式)</label>
            <textarea id="data" placeholder='{"key": "value"}'>{"title": "foo", "body": "bar", "userId": 1}</textarea>
          </div>
          
          <div class="button-group">
            <button id="send-btn">发送请求</button>
            <button id="save-btn" disabled>保存响应</button>
          </div>
          
          <div class="response-section" id="response-section" style="display: none;">
            <div class="response-header">
              <div class="response-status" id="response-status"></div>
              <button onclick="saveResponse()">保存响应</button>
            </div>
            <div class="response-body" id="response-body"></div>
          </div>
          
          <div class="error" id="error-message"></div>
        </div>
      </div>
      
      <script>
        const { ipcRenderer } = require('electron')
        const methodSelect = document.getElementById('method')
        const urlInput = document.getElementById('url')
        const headersInput = document.getElementById('headers')
        const dataInput = document.getElementById('data')
        const sendBtn = document.getElementById('send-btn')
        const saveBtn = document.getElementById('save-btn')
        const responseSection = document.getElementById('response-section')
        const responseStatus = document.getElementById('response-status')
        const responseBody = document.getElementById('response-body')
        const errorMessage = document.getElementById('error-message')
        const historyContainer = document.getElementById('history-container')
    
        let currentResponse = null
    
        // 加载历史记录
        async function loadHistory() {
          const history = await ipcRenderer.invoke('get-request-history')
          historyContainer.innerHTML = ''
          
          history.forEach(item => {
            const historyItem = document.createElement('div')
            historyItem.className = 'history-item'
            historyItem.onclick = () => loadHistoryItem(item)
            
            historyItem.innerHTML = `
              <div>
                <span class="method">${item.type}</span>
                <span class="url">${item.url}</span>
              </div>
              <div class="time">${new Date(item.timestamp).toLocaleString()}</div>
            `
            
            historyContainer.appendChild(historyItem)
          })
        }
    
        // 加载历史记录项
        function loadHistoryItem(item) {
          methodSelect.value = item.type
          urlInput.value = item.url
          headersInput.value = JSON.stringify(item.headers, null, 2)
          if (item.data) {
            dataInput.value = JSON.stringify(item.data, null, 2)
          }
          showResponse(item.response)
        }
    
        // 发送请求
        sendBtn.addEventListener('click', async () => {
          const method = methodSelect.value
          const url = urlInput.value
          
          if (!url) {
            showError('请输入请求 URL')
            return
          }
          
          let headers = {}
          let data = {}
          
          try {
            headers = JSON.parse(headersInput.value)
          } catch (e) {
            showError('请求头格式错误')
            return
          }
          
          if (method === 'POST') {
            try {
              data = JSON.parse(dataInput.value)
            } catch (e) {
              showError('请求数据格式错误')
              return
            }
          }
          
          try {
            showError('')
            responseSection.style.display = 'none'
            
            let result
            if (method === 'GET') {
              result = await ipcRenderer.invoke('send-get-request', url, headers)
            } else {
              result = await ipcRenderer.invoke('send-post-request', url, data, headers)
            }
            
            showResponse(result.response)
            loadHistory()
          } catch (error) {
            showError(`请求失败: ${error.message}`)
          }
        })
    
        // 显示响应
        function showResponse(response) {
          currentResponse = response
          responseSection.style.display = 'block'
          responseStatus.textContent = `状态码: ${response.status}`
          responseBody.textContent = JSON.stringify(response.data, null, 2)
          saveBtn.disabled = false
        }
    
        // 显示错误
        function showError(message) {
          errorMessage.textContent = message
        }
    
        // 保存响应
        async function saveResponse() {
          if (currentResponse) {
            await ipcRenderer.invoke('save-response', currentResponse)
          }
        }
    
        // 清空历史
        async function clearHistory() {
          if (confirm('确定要清空所有历史记录吗?')) {
            await ipcRenderer.invoke('clear-request-history')
            loadHistory()
          }
        }
    
        // 初始化加载历史记录
        loadHistory()
      </script>
    </body>
    </html>
  5. 运行应用

    bash
    npm install
    npm start

11.10 小结

通过本章的三个基础实战案例,你已经掌握了 Electron 应用开发的核心技能:

  • 简易桌面记事本:学习了如何使用 BrowserWindow 创建窗口,使用 dialog 模块选择文件,使用 fs 模块读写文件,以及使用 IPC 通信实现主进程与渲染进程之间的交互。

  • 系统通知工具:学习了如何使用 Notification 模块创建系统通知,使用定时器实现定时功能,以及管理通知的生命周期。

  • 网络请求工具:学习了如何使用 axios 发送网络请求,使用 electron-store 存储请求历史,以及使用 dialog 模块保存响应数据。

这些实战案例覆盖了 Electron 开发的常见场景,帮助你巩固了前面章节所学的核心知识点。在接下来的章节中,我们将学习更复杂的进阶实战案例。

© 2026 编程马·菜鸟教程 版权所有