💻 海狸IM 桌面端开发指南
🏗️ 技术架构深度解析
⚡ 主进程 (Main Process)
Node.js Runtime窗口管理
创建、控制和管理应用窗口
系统集成
系统托盘、菜单、通知
生命周期
应用启动、退出、更新
🔗 IPC 通信桥
contextBridgeipcMainipcRendererpreload
安全的进程间通信机制
🎨 渲染进程 (Renderer Process)
Chromium EngineVue 3 框架
组件化、响应式UI开发
TypeScript
类型安全的代码开发
Element Plus
丰富的UI组件库
📂 项目结构全景图
📁 核心目录架构
beaver-desktop/
src/main/主进程源码
index.ts应用入口
window.ts窗口管理
ipc.ts进程通信
tray.ts系统托盘
src/render/渲染进程源码
components/Vue组件
pages/页面组件
store/状态管理
router/路由配置
src/shared/共享模块
build/构建配置
🏗️ 架构特点
安全隔离
主进程与渲染进程完全隔离,通过IPC安全通信
高性能
基于Chromium引擎,支持硬件加速和多进程
跨平台
一套代码支持Windows、macOS、Linux
现代化
Vue 3 + TypeScript + Vite 现代开发工具链
🔧 核心技术栈
🎨 前端技术
渲染进程⚡
Vite
v5.3+构建工具
🎪
Element Plus
v2.9+Vue 3版本的组件库
🎯
Pinia
v2.1+Vue 3官方状态管理
⚡ 主进程技术
Node.js环境⚙️
Electron
v31.2+跨平台桌面应用框架
🔗
IPC通信
Built-in安全的进程间通信
🔒
Context Isolation
Security上下文隔离安全机制
📦
electron-builder
v24.0+应用打包和分发
🛠️ 开发工具
工具链🔧
ESLint
v8.0+代码质量检查
🎨
Prettier
v3.0+代码格式化工具
🚀 环境搭建
1. 克隆项目
bash
git clone https://github.com/wsrh8888/beaver-desktop.git
cd beaver-desktop
2. 安装Node.js
bash
# 使用nvm安装Node.js 18+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 18
nvm use 18
# 验证安装
node --version # 应该显示 v18.x.x
npm --version
3. 安装项目依赖
bash
# 安装依赖
npm install
# 或者使用yarn
yarn install
4. 开发工具推荐
VS Code (推荐)
推荐插件:
- Vue Language Features (Volar)
- TypeScript Vue Plugin (Volar)
- ESLint
- Prettier
- Electron DevTools
WebStorm (可选)
专业的前端IDE,内置对Vue和TypeScript的支持。
⚙️ 项目配置
1. package.json 配置
json
{
"name": "beaver-desktop",
"version": "1.0.0",
"description": "海狸IM桌面端",
"main": "dist/main/index.js",
"scripts": {
"dev": "concurrently \"npm run dev:renderer\" \"npm run dev:main\"",
"dev:renderer": "vite --config build/vite.config.renderer.ts",
"dev:main": "vite --config build/vite.config.main.ts",
"build": "npm run build:renderer && npm run build:main",
"build:renderer": "vite build --config build/vite.config.renderer.ts",
"build:main": "vite build --config build/vite.config.main.ts",
"electron": "electron .",
"dist": "npm run build && electron-builder",
"dist:win": "npm run build && electron-builder --win",
"dist:mac": "npm run build && electron-builder --mac",
"dist:linux": "npm run build && electron-builder --linux"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"concurrently": "^8.0.0",
"electron": "^31.2.0",
"electron-builder": "^24.0.0",
"typescript": "^5.0.0",
"vite": "^5.3.0",
"vue": "^3.4.0"
},
"dependencies": {
"element-plus": "^2.9.0",
"pinia": "^2.1.0",
"vue-router": "^4.2.0"
}
}
2. Vite配置
typescript
// build/vite.config.renderer.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, '../src/render'),
'@shared': resolve(__dirname, '../src/shared')
}
},
build: {
outDir: 'dist/renderer',
emptyOutDir: true
}
})
typescript
// build/vite.config.main.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, '../src/main/index.ts'),
name: 'main',
fileName: 'index'
},
outDir: 'dist/main',
emptyOutDir: true,
rollupOptions: {
external: ['electron']
}
}
})
📋 开发流程
1. 主进程开发
应用入口
typescript
// src/main/index.ts
import { app, BrowserWindow } from 'electron'
import { createWindow } from './window'
import { createTray } from './tray'
import { setupMenu } from './menu'
import { setupIPC } from './ipc'
class Application {
private mainWindow: BrowserWindow | null = null
async init() {
// 等待应用就绪
await app.whenReady()
// 创建主窗口
this.mainWindow = createWindow()
// 设置系统托盘
createTray(this.mainWindow)
// 设置菜单
setupMenu()
// 设置IPC通信
setupIPC()
// 监听应用事件
this.setupAppEvents()
}
private setupAppEvents() {
// 所有窗口关闭时
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// 应用激活时
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
this.mainWindow = createWindow()
}
})
}
}
// 启动应用
new Application().init().catch(console.error)
窗口管理
typescript
// src/main/window.ts
import { BrowserWindow, screen } from 'electron'
import { join } from 'path'
export function createWindow(): BrowserWindow {
// 获取屏幕尺寸
const { width, height } = screen.getPrimaryDisplay().workAreaSize
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: Math.min(1200, width - 100),
height: Math.min(800, height - 100),
minWidth: 800,
minHeight: 600,
center: true,
show: false, // 先不显示,等加载完成再显示
autoHideMenuBar: true,
icon: join(__dirname, '../../resource/app.ico'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: join(__dirname, '../preload/index.js')
}
})
// 加载页面
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173')
mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
// 窗口准备好时显示
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
// 窗口事件处理
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault()
mainWindow.hide()
}
})
return mainWindow
}
系统托盘
typescript
// src/main/tray.ts
import { Tray, Menu, BrowserWindow } from 'electron'
import { join } from 'path'
export function createTray(mainWindow: BrowserWindow): Tray {
const tray = new Tray(join(__dirname, '../../resource/tray.ico'))
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
mainWindow.show()
}
},
{
label: '设置',
click: () => {
// 打开设置窗口
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.isQuiting = true
app.quit()
}
}
])
tray.setToolTip('海狸IM')
tray.setContextMenu(contextMenu)
// 双击托盘图标显示窗口
tray.on('double-click', () => {
mainWindow.show()
})
return tray
}
IPC通信
typescript
// src/main/ipc.ts
import { ipcMain, dialog, app } from 'electron'
export function setupIPC() {
// 获取应用版本
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
// 显示消息框
ipcMain.handle('show-message-box', async (_, options) => {
const result = await dialog.showMessageBox(options)
return result
})
// 选择文件
ipcMain.handle('show-open-dialog', async (_, options) => {
const result = await dialog.showOpenDialog(options)
return result
})
// 窗口控制
ipcMain.handle('window-minimize', (event) => {
const window = BrowserWindow.fromWebContents(event.sender)
window?.minimize()
})
ipcMain.handle('window-maximize', (event) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (window?.isMaximized()) {
window.unmaximize()
} else {
window?.maximize()
}
})
ipcMain.handle('window-close', (event) => {
const window = BrowserWindow.fromWebContents(event.sender)
window?.close()
})
}
2. 预加载脚本
typescript
// src/render/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
// 暴露安全的API到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 应用信息
getVersion: () => ipcRenderer.invoke('get-app-version'),
// 对话框
showMessageBox: (options: any) => ipcRenderer.invoke('show-message-box', options),
showOpenDialog: (options: any) => ipcRenderer.invoke('show-open-dialog', options),
// 窗口控制
windowMinimize: () => ipcRenderer.invoke('window-minimize'),
windowMaximize: () => ipcRenderer.invoke('window-maximize'),
windowClose: () => ipcRenderer.invoke('window-close'),
// 监听事件
onUpdateAvailable: (callback: Function) => {
ipcRenderer.on('update-available', callback)
},
// 移除监听
removeAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel)
}
})
// 类型声明
declare global {
interface Window {
electronAPI: {
getVersion: () => Promise<string>
showMessageBox: (options: any) => Promise<any>
showOpenDialog: (options: any) => Promise<any>
windowMinimize: () => Promise<void>
windowMaximize: () => Promise<void>
windowClose: () => Promise<void>
onUpdateAvailable: (callback: Function) => void
removeAllListeners: (channel: string) => void
}
}
}
3. 渲染进程开发
主应用入口
typescript
// src/render/app/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { router } from './router'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
// 状态管理
app.use(createPinia())
// 路由
app.use(router)
// UI组件库
app.use(ElementPlus)
// 挂载应用
app.mount('#app')
路由配置
typescript
// src/render/app/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import { useUserStore } from '../pinia/user'
const routes = [
{
path: '/',
redirect: '/chat'
},
{
path: '/chat',
component: () => import('../page/Chat/index.vue'),
meta: { requiresAuth: true }
},
{
path: '/contacts',
component: () => import('../page/Contacts/index.vue'),
meta: { requiresAuth: true }
},
{
path: '/profile',
component: () => import('../page/Profile/index.vue'),
meta: { requiresAuth: true }
},
{
path: '/login',
component: () => import('../page/Login/index.vue')
}
]
export const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.isLogin) {
next('/login')
} else {
next()
}
})
WebSocket管理
typescript
// src/render/app/ws-manager/index.ts
export class WsManager {
private ws: WebSocket | null = null
private heartbeatTimer: number | null = null
private reconnectTimer: number | null = null
private isConnected = false
private reconnectCount = 0
private maxReconnectCount = 5
private messageHandlers = new Map<string, Function>()
connect(url: string, token: string) {
if (this.ws) {
this.close()
}
this.ws = new WebSocket(`${url}?token=${token}`)
this.setupEventHandlers()
}
private setupEventHandlers() {
if (!this.ws) return
this.ws.onopen = () => {
console.log('WebSocket连接成功')
this.isConnected = true
this.reconnectCount = 0
this.startHeartbeat()
}
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
this.handleMessage(data)
} catch (error) {
console.error('消息解析失败:', error)
}
}
this.ws.onclose = () => {
console.log('WebSocket连接关闭')
this.isConnected = false
this.stopHeartbeat()
this.reconnect()
}
this.ws.onerror = (error) => {
console.error('WebSocket错误:', error)
this.isConnected = false
}
}
send(data: any) {
if (this.isConnected && this.ws) {
this.ws.send(JSON.stringify(data))
}
}
onMessage(type: string, handler: Function) {
this.messageHandlers.set(type, handler)
}
private handleMessage(data: any) {
const { type } = data
const handler = this.messageHandlers.get(type)
if (handler) {
handler(data)
}
}
private startHeartbeat() {
this.heartbeatTimer = window.setInterval(() => {
this.send({ type: 'ping' })
}, 30000)
}
private stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
}
private reconnect() {
if (this.reconnectCount >= this.maxReconnectCount) {
console.log('达到最大重连次数')
return
}
this.reconnectTimer = window.setTimeout(() => {
console.log(`第${this.reconnectCount + 1}次重连...`)
this.reconnectCount++
// 重新连接逻辑
}, 3000 * Math.pow(2, this.reconnectCount))
}
close() {
if (this.ws) {
this.ws.close()
this.ws = null
}
this.stopHeartbeat()
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
this.isConnected = false
}
}
export default new WsManager()
状态管理
typescript
// src/render/app/pinia/user/user.ts
import { defineStore } from 'pinia'
import { userApi } from '../../api/user'
interface UserInfo {
id: number
username: string
nickname: string
avatar: string
}
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null as UserInfo | null,
token: '',
isLogin: false
}),
getters: {
avatar: (state) => state.userInfo?.avatar || '/images/default-avatar.png',
nickname: (state) => state.userInfo?.nickname || '用户'
},
actions: {
async login(credentials: { username: string; password: string }) {
try {
const response = await userApi.login(credentials)
this.userInfo = response.data.userInfo
this.token = response.data.token
this.isLogin = true
// 保存到本地存储
localStorage.setItem('userInfo', JSON.stringify(this.userInfo))
localStorage.setItem('token', this.token)
return response
} catch (error) {
throw error
}
},
logout() {
this.userInfo = null
this.token = ''
this.isLogin = false
// 清除本地存储
localStorage.removeItem('userInfo')
localStorage.removeItem('token')
},
loadUserFromStorage() {
const userInfo = localStorage.getItem('userInfo')
const token = localStorage.getItem('token')
if (userInfo && token) {
this.userInfo = JSON.parse(userInfo)
this.token = token
this.isLogin = true
}
}
}
})
4. 页面组件开发
vue
<!-- src/render/app/page/Chat/index.vue -->
<template>
<div class="chat-container">
<!-- 标题栏 -->
<div class="title-bar">
<div class="title">海狸IM</div>
<div class="window-controls">
<el-button @click="minimizeWindow" size="small" text>
<el-icon><Minus /></el-icon>
</el-button>
<el-button @click="maximizeWindow" size="small" text>
<el-icon><FullScreen /></el-icon>
</el-button>
<el-button @click="closeWindow" size="small" text>
<el-icon><Close /></el-icon>
</el-button>
</div>
</div>
<!-- 主体内容 -->
<div class="main-content">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="user-info">
<el-avatar :src="userStore.avatar" :size="50" />
<div class="user-name">{{ userStore.nickname }}</div>
</div>
<!-- 会话列表 -->
<div class="conversation-list">
<div
v-for="conversation in conversationList"
:key="conversation.id"
class="conversation-item"
:class="{ active: currentConversation?.id === conversation.id }"
@click="selectConversation(conversation)"
>
<el-avatar :src="conversation.avatar" :size="40" />
<div class="conversation-info">
<div class="name">{{ conversation.name }}</div>
<div class="last-message">{{ conversation.lastMessage }}</div>
</div>
<div v-if="conversation.unreadCount > 0" class="unread-count">
{{ conversation.unreadCount }}
</div>
</div>
</div>
</div>
<!-- 聊天区域 -->
<div class="chat-area">
<div v-if="currentConversation" class="chat-content">
<!-- 聊天头部 -->
<div class="chat-header">
<div class="chat-title">{{ currentConversation.name }}</div>
</div>
<!-- 消息列表 -->
<div class="message-list" ref="messageListRef">
<div
v-for="message in messageList"
:key="message.id"
class="message-item"
:class="{ 'own-message': message.senderId === userStore.userInfo?.id }"
>
<el-avatar :src="message.senderAvatar" :size="36" />
<div class="message-content">
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="message-input-area">
<el-input
v-model="inputMessage"
type="textarea"
:rows="3"
placeholder="输入消息..."
@keydown.enter.exact="sendMessage"
@keydown.enter.shift.exact.prevent="inputMessage += '\n'"
/>
<div class="input-actions">
<el-button @click="selectFile" text>
<el-icon><Paperclip /></el-icon>
</el-button>
<el-button type="primary" @click="sendMessage" :disabled="!inputMessage.trim()">
发送
</el-button>
</div>
</div>
</div>
<div v-else class="no-conversation">
<div class="no-conversation-text">选择一个会话开始聊天</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import { useUserStore, useChatStore } from '../../pinia'
import WsManager from '../../ws-manager'
import { ElMessage } from 'element-plus'
const userStore = useUserStore()
const chatStore = useChatStore()
const inputMessage = ref('')
const messageListRef = ref<HTMLElement>()
const conversationList = computed(() => chatStore.conversationList)
const currentConversation = computed(() => chatStore.currentConversation)
const messageList = computed(() => chatStore.messageList)
// 窗口控制
const minimizeWindow = () => {
window.electronAPI.windowMinimize()
}
const maximizeWindow = () => {
window.electronAPI.windowMaximize()
}
const closeWindow = () => {
window.electronAPI.windowClose()
}
// 选择会话
const selectConversation = (conversation: any) => {
chatStore.setCurrentConversation(conversation)
chatStore.loadMessages(conversation.id)
}
// 发送消息
const sendMessage = () => {
if (!inputMessage.value.trim() || !currentConversation.value) return
const message = {
id: Date.now(),
content: inputMessage.value,
senderId: userStore.userInfo?.id,
senderAvatar: userStore.avatar,
timestamp: Date.now(),
conversationId: currentConversation.value.id
}
chatStore.addMessage(message)
// 发送WebSocket消息
WsManager.send({
type: 'message',
data: message
})
inputMessage.value = ''
scrollToBottom()
}
// 选择文件
const selectFile = async () => {
try {
const result = await window.electronAPI.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'All Files', extensions: ['*'] }
]
})
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0]
// 处理文件上传
uploadFile(filePath)
}
} catch (error) {
console.error('选择文件失败:', error)
}
}
// 上传文件
const uploadFile = (filePath: string) => {
// 文件上传逻辑
console.log('上传文件:', filePath)
}
// 格式化时间
const formatTime = (timestamp: number) => {
const date = new Date(timestamp)
return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`
}
// 滚动到底部
const scrollToBottom = () => {
nextTick(() => {
if (messageListRef.value) {
messageListRef.value.scrollTop = messageListRef.value.scrollHeight
}
})
}
onMounted(() => {
// 加载用户信息
userStore.loadUserFromStorage()
// 连接WebSocket
if (userStore.token) {
WsManager.connect('ws://localhost:21040/ws', userStore.token)
}
// 监听WebSocket消息
WsManager.onMessage('message', (data: any) => {
chatStore.addMessage(data.data)
scrollToBottom()
})
// 加载会话列表
chatStore.loadConversations()
})
</script>
<style lang="scss" scoped>
.chat-container {
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.title-bar {
height: 40px;
background: #2c3e50;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
color: white;
-webkit-app-region: drag;
}
.window-controls {
display: flex;
gap: 5px;
-webkit-app-region: no-drag;
}
.main-content {
flex: 1;
display: flex;
overflow: hidden;
}
.sidebar {
width: 280px;
background: white;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
}
.user-info {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
text-align: center;
}
.user-name {
margin-top: 10px;
font-weight: 500;
}
.conversation-list {
flex: 1;
overflow-y: auto;
}
.conversation-item {
display: flex;
align-items: center;
padding: 15px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
&:hover {
background: #f8f9fa;
}
&.active {
background: #e3f2fd;
}
}
.conversation-info {
flex: 1;
margin-left: 12px;
overflow: hidden;
}
.conversation-info .name {
font-weight: 500;
margin-bottom: 4px;
}
.conversation-info .last-message {
font-size: 12px;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unread-count {
background: #ff4757;
color: white;
border-radius: 10px;
padding: 2px 8px;
font-size: 12px;
min-width: 20px;
text-align: center;
}
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-content {
height: 100%;
display: flex;
flex-direction: column;
}
.chat-header {
height: 60px;
background: white;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
padding: 0 20px;
}
.chat-title {
font-size: 16px;
font-weight: 500;
}
.message-list {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #f8f9fa;
}
.message-item {
display: flex;
margin-bottom: 20px;
&.own-message {
flex-direction: row-reverse;
}
}
.message-content {
max-width: 60%;
margin: 0 12px;
}
.message-text {
background: white;
padding: 12px 16px;
border-radius: 12px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.own-message .message-text {
background: #FF7D45;
color: white;
}
.message-time {
font-size: 12px;
color: #999;
margin-top: 4px;
text-align: center;
}
.message-input-area {
background: white;
border-top: 1px solid #e0e0e0;
padding: 20px;
}
.input-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.no-conversation {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: white;
}
.no-conversation-text {
color: #999;
font-size: 16px;
}
</style>
🔧 构建和打包
1. 开发环境启动
bash
# 启动开发环境
npm run dev
# 分别启动渲染进程和主进程
npm run dev:renderer
npm run dev:main
2. 构建生产版本
bash
# 构建所有进程
npm run build
# 构建渲染进程
npm run build:renderer
# 构建主进程
npm run build:main
3. 打包应用
bash
# 打包所有平台
npm run dist
# 打包Windows
npm run dist:win
# 打包macOS
npm run dist:mac
# 打包Linux
npm run dist:linux
4. electron-builder配置
yaml
# build/electron-builder.yml
appId: com.beaver.im
productName: 海狸IM
copyright: Copyright © 2024 海狸IM
directories:
output: release
files:
- dist/**/*
- package.json
mac:
category: public.app-category.social-networking
target:
- target: dmg
arch: [x64, arm64]
win:
target:
- target: nsis
arch: [x64]
linux:
target:
- target: AppImage
arch: [x64]
nsis:
oneClick: false
perMachine: true
allowToChangeInstallationDirectory: true
createDesktopShortcut: always
🐛 调试技巧
1. 主进程调试
bash
# 使用VS Code调试主进程
# .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": ["."],
"outputCapture": "std"
}
]
}
2. 渲染进程调试
typescript
// 在渲染进程中使用开发者工具
if (process.env.NODE_ENV === 'development') {
// 自动打开开发者工具
mainWindow.webContents.openDevTools()
}
3. 日志调试
typescript
// 使用electron-log
import log from 'electron-log'
log.info('应用启动')
log.error('发生错误', error)
📚 相关资源
如果在开发过程中遇到问题,欢迎在GitHub Issues中提问或加入QQ群(1013328597)交流!