一、为什么选择 Vue 3 + Electron?
Vue 3 核心优势:
✅ Composition API - 逻辑复用更优雅
✅ 响应式系统 - 性能优于 React
✅ 单文件组件 - 模板/脚本/样式一体化
✅ 更小的运行时 - ~16KB gzip
✅ 更好的 TypeScript 支持
✅ 平滑的学习曲线
二、项目初始化
2.1 创建 Vue 3 项目
javascript
# 使用 create-vue 创建项目
npm create vue@latest electron-vue-demo
# 选择以下配置:
# ✔ TypeScript? ... Yes
# ✔ JSX Support? ... No
# ✔ Vue Router? ... Yes
# ✔ Pinia? ... Yes
# ✔ Vitest? ... Yes
# ✔ ESLint? ... Yes
# ✔ Prettier? ... Yes
cd electron-vue-demo
2.2 添加 Electron
javascript
# 安装 Electron
npm install electron electron-builder electron-vite -D
# 安装 electron-vite 插件
npm install @electron-toolkit/preload -D
2.3 配置 electron-vite
electron.vite.config.ts:
javascript
import { defineConfig } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
main: {
build: {
outDir: 'dist-electron/main',
rollupOptions: {
external: ['electron']
}
},
resolve: {
alias: {
'@main': path.resolve(__dirname, 'src/main')
}
}
},
preload: {
build: {
outDir: 'dist-electron/preload',
rollupOptions: {
input: path.resolve(__dirname, 'src/preload/index.ts'),
external: ['electron']
}
}
},
renderer: {
root: '.',
build: {
outDir: 'dist/renderer',
rollupOptions: {
input: path.resolve(__dirname, 'index.html')
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/renderer')
}
},
plugins: [vue()],
server: {
port: 5173,
strictPort: true
}
}
})
2.4 目录结构
javascript
electron-vue-demo/
├── src/
│ ├── main/ # 主进程代码
│ │ ├── index.ts # 入口
│ │ ├── window/ # 窗口管理
│ │ ├── ipc/ # IPC 处理器
│ │ └── native/ # 原生功能
│ │
│ ├── preload/ # 预加载脚本
│ │ └── index.ts
│ │
│ └── renderer/ # Vue 3 渲染进程
│ ├── main.ts # Vue 入口
│ ├── App.vue # 根组件
│ ├── assets/ # 静态资源
│ ├── components/ # 公共组件
│ ├── views/ # 页面组件
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia 状态
│ ├── composables/ # 组合式函数
│ └── styles/ # 全局样式
│
├── index.html # HTML 模板
├── package.json
├── electron.vite.config.ts
└── tsconfig.json
2.5 package.json 配置
javascript
{
"name": "electron-vue-demo",
"version": "1.0.0",
"description": "Modern Electron + Vue 3 Desktop Application",
"main": "dist-electron/main/index.js",
"scripts": {
"dev": "electron-vite dev",
"dev:renderer": "vite",
"build": "electron-vite build",
"preview": "electron-vite preview",
"electron:build": "npm run build && electron-builder",
"lint": "eslint . --ext .ts,.tsx,.vue",
"type-check": "vue-tsc --noEmit"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@vitejs/plugin-vue": "^4.5.0",
"electron": "^28.0.0",
"electron-builder": "^24.9.0",
"electron-vite": "^2.0.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0"
}
}
三、主进程实现
3.1 主进程入口
src/main/index.ts:
javascript
import { app, BrowserWindow, Menu } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/preload'
import { createMainWindow } from './window/mainWindow'
import { setupIpcHandlers } from './ipc'
import { initTray } from './native/tray'
import { initMenu } from './native/menu'
import { setupAutoUpdater } from './updater'
import { logger } from './utils/logger'
// 开发环境判断
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
// 单实例锁定
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', () => {
// 有人试图运行第二个实例,聚焦主窗口
const mainWindow = BrowserWindow.getAllWindows()[0]
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
})
}
// 应用准备就绪
app.whenReady().then(async () => {
// 设置 Electron 应用
electronApp.setAppUserModelId('com.electron.vue-demo')
// 开发环境设置
if (isDev) {
// 安装 Vue Devtools
try {
const { default: installExtension, VUEJS_DEVTOOLS } = await import('electron-devtools-installer')
await installExtension(VUEJS_DEVTOOLS)
logger.info('Vue Devtools installed')
} catch (error) {
logger.warn('Failed to install Vue Devtools:', error)
}
}
// 优化窗口行为
optimizer.watchWindowShortcuts()
// 创建主窗口
const mainWindow = createMainWindow()
// 初始化 IPC 处理器
setupIpcHandlers()
// 初始化系统托盘
initTray(mainWindow)
// 初始化菜单
initMenu(mainWindow)
// 设置自动更新
if (!isDev) {
setupAutoUpdater(mainWindow)
}
// macOS: 点击 dock 图标时重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow()
}
})
})
// 所有窗口关闭时
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// 应用退出前
app.on('will-quit', () => {
logger.info('Application is quitting')
})
3.2 主窗口管理
src/main/window/mainWindow.ts:
javascript
import { BrowserWindow, shell } from 'electron'
import path from 'path'
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
let mainWindow: BrowserWindow | null = null
export function createMainWindow(): BrowserWindow {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
frame: true,
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
show: false, // 先隐藏,ready-to-show 再显示
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false,
webSecurity: !isDev // 开发环境允许跨域
},
icon: path.join(__dirname, '../../../resources/icon.png')
})
// 加载页面
if (isDev) {
mainWindow.loadURL('http://localhost:5173')
mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
// 窗口准备就绪后显示
mainWindow.once('ready-to-show', () => {
mainWindow?.show()
// 开发环境自动打开 DevTools
if (isDev) {
mainWindow?.webContents.openDevTools()
}
})
// 处理外部链接
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// 窗口关闭时清理
mainWindow.on('closed', () => {
mainWindow = null
})
return mainWindow
}
export function getMainWindow(): BrowserWindow | null {
return mainWindow
}
3.3 IPC 处理器
src/main/ipc/index.ts:
javascript
import { ipcMain } from 'electron'
import { fileHandlers } from './fileHandlers'
import { systemHandlers } from './systemHandlers'
import { windowHandlers } from './windowHandlers'
export function setupIpcHandlers() {
// 文件操作处理器
fileHandlers()
// 系统操作处理器
systemHandlers()
// 窗口操作处理器
windowHandlers()
}
src/main/ipc/fileHandlers.ts:
javascript
import { ipcMain, dialog } from 'electron'
import fs from 'fs/promises'
import path from 'path'
export function fileHandlers() {
// 读取文件
ipcMain.handle('file:read', async (event, filePath: string) => {
try {
const content = await fs.readFile(filePath, 'utf-8')
return { success: true, content }
} catch (error) {
return { success: false, error: error.message }
}
})
// 写入文件
ipcMain.handle('file:write', async (event, filePath: string, content: string) => {
try {
await fs.writeFile(filePath, content, 'utf-8')
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 选择文件
ipcMain.handle('file:select', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] }
]
})
if (result.canceled) return null
return result.filePaths[0]
})
// 选择目录
ipcMain.handle('file:select-directory', async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory']
})
if (result.canceled) return null
return result.filePaths[0]
})
// 保存文件对话框
ipcMain.handle('file:save-dialog', async (event, defaultName?: string) => {
const result = await dialog.showSaveDialog({
defaultPath: defaultName,
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'All Files', extensions: ['*'] }
]
})
if (result.canceled) return null
return result.filePath
})
}
src/main/ipc/systemHandlers.ts:
javascript
import { ipcMain, shell, nativeTheme } from 'electron'
import os from 'os'
export function systemHandlers() {
// 获取系统信息
ipcMain.handle('system:info', async () => {
return {
platform: process.platform,
arch: process.arch,
version: process.version,
electronVersion: process.versions.electron,
chromeVersion: process.versions.chrome,
nodeVersion: process.versions.node,
hostname: os.hostname(),
cpus: os.cpus().length,
totalMemory: os.totalmem(),
freeMemory: os.freemem()
}
})
// 打开外部链接
ipcMain.handle('system:open-external', async (event, url: string) => {
await shell.openExternal(url)
})
// 显示通知
ipcMain.handle('system:notification', async (event, title: string, body: string) => {
// 主进程通知会在所有窗口上显示
const { Notification } = await import('electron')
new Notification({ title, body }).show()
})
// 获取主题
ipcMain.handle('system:theme', async () => {
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
})
// 监听主题变化(主动推送)
nativeTheme.on('updated', () => {
const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
const windows = BrowserWindow.getAllWindows()
windows.forEach(win => {
win.webContents.send('system:theme-changed', theme)
})
})
}
src/main/ipc/windowHandlers.ts:
javascript
import { ipcMain, BrowserWindow } from 'electron'
export function windowHandlers() {
// 窗口最小化
ipcMain.on('window:minimize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
win?.minimize()
})
// 窗口最大化/还原
ipcMain.on('window:maximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win?.isMaximized()) {
win.unmaximize()
} else {
win?.maximize()
}
})
// 关闭窗口
ipcMain.on('window:close', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
win?.close()
})
// 获取窗口最大化状态
ipcMain.handle('window:is-maximized', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
return win?.isMaximized() || false
})
}
四、预加载脚本
src/preload/index.ts:
javascript
import { contextBridge, ipcRenderer } from 'electron'
// 定义暴露的 API 类型
export interface ElectronAPI {
// 文件操作
file: {
read: (path: string) => Promise<{ success: boolean; content?: string; error?: string }>
write: (path: string, content: string) => Promise<{ success: boolean; error?: string }>
select: () => Promise<string | null>
selectDirectory: () => Promise<string | null>
saveDialog: (defaultName?: string) => Promise<string | null>
}
// 系统操作
system: {
getInfo: () => Promise<SystemInfo>
openExternal: (url: string) => Promise<void>
showNotification: (title: string, body: string) => Promise<void>
getTheme: () => Promise<'light' | 'dark'>
onThemeChange: (callback: (theme: 'light' | 'dark') => void) => () => void
}
// 窗口操作
window: {
minimize: () => void
maximize: () => void
close: () => void
isMaximized: () => Promise<boolean>
}
// 版本信息
versions: {
electron: () => string
node: () => string
chrome: () => string
}
}
interface SystemInfo {
platform: string
arch: string
version: string
electronVersion: string
chromeVersion: string
nodeVersion: string
hostname: string
cpus: number
totalMemory: number
freeMemory: number
}
// 实现 API
const electronAPI: ElectronAPI = {
file: {
read: (path) => ipcRenderer.invoke('file:read', path),
write: (path, content) => ipcRenderer.invoke('file:write', path, content),
select: () => ipcRenderer.invoke('file:select'),
selectDirectory: () => ipcRenderer.invoke('file:select-directory'),
saveDialog: (defaultName) => ipcRenderer.invoke('file:save-dialog', defaultName)
},
system: {
getInfo: () => ipcRenderer.invoke('system:info'),
openExternal: (url) => ipcRenderer.invoke('system:open-external', url),
showNotification: (title, body) => ipcRenderer.invoke('system:notification', title, body),
getTheme: () => ipcRenderer.invoke('system:theme'),
onThemeChange: (callback) => {
const handler = (_event: any, theme: 'light' | 'dark') => callback(theme)
ipcRenderer.on('system:theme-changed', handler)
return () => ipcRenderer.removeListener('system:theme-changed', handler)
}
},
window: {
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
isMaximized: () => ipcRenderer.invoke('window:is-maximized')
},
versions: {
electron: () => process.versions.electron,
node: () => process.versions.node,
chrome: () => process.versions.chrome
}
}
// 暴露 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
// 类型声明
declare global {
interface Window {
electronAPI: ElectronAPI
}
}
五、Vue 3 渲染进程
5.1 入口文件
src/renderer/main.ts:
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './styles/global.css'
// 创建应用
const app = createApp(App)
// 使用插件
app.use(createPinia())
app.use(router)
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('Vue Error:', err, info)
window.electronAPI?.system.showNotification('错误', err?.toString() || '未知错误')
}
// 挂载
app.mount('#app')
// 开发环境输出信息
if (import.meta.env.DEV) {
console.log('Electron API available:', !!window.electronAPI)
console.log('Electron versions:', window.electronAPI?.versions)
}
5.2 根组件
src/renderer/App.vue:
javascript
<template>
<div class="app-container" :class="{ dark: isDarkTheme }">
<!-- 自定义标题栏 -->
<div v-if="!isFullScreen" class="title-bar">
<div class="title-bar-drag-area"></div>
<div class="window-controls">
<button @click="minimizeWindow" class="control-btn">─</button>
<button @click="maximizeWindow" class="control-btn">
{{ isMaximized ? '❐' : '□' }}
</button>
<button @click="closeWindow" class="control-btn close">✕</button>
</div>
</div>
<!-- 主内容区 -->
<div class="main-layout">
<aside class="sidebar">
<div class="logo">
<img src="/vite.svg" alt="Logo" />
<span>Vue Electron</span>
</div>
<nav class="nav-menu">
<router-link to="/" class="nav-item">
<span class="icon">🏠</span>
<span>仪表盘</span>
</router-link>
<router-link to="/files" class="nav-item">
<span class="icon">📁</span>
<span>文件管理</span>
</router-link>
<router-link to="/settings" class="nav-item">
<span class="icon">⚙️</span>
<span>设置</span>
</router-link>
<router-link to="/about" class="nav-item">
<span class="icon">ℹ️</span>
<span>关于</span>
</router-link>
</nav>
<div class="system-info">
<div class="info-item">
<span>Node:</span>
<span>{{ versions.node }}</span>
</div>
<div class="info-item">
<span>Electron:</span>
<span>{{ versions.electron }}</span>
</div>
</div>
</aside>
<main class="content">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</main>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const isDarkTheme = ref(false)
const isMaximized = ref(false)
const isFullScreen = ref(false)
const versions = ref({
node: '',
electron: '',
chrome: ''
})
// 窗口控制
const minimizeWindow = () => {
window.electronAPI?.window.minimize()
}
const maximizeWindow = async () => {
window.electronAPI?.window.maximize()
isMaximized.value = await window.electronAPI?.window.isMaximized() || false
}
const closeWindow = () => {
window.electronAPI?.window.close()
}
// 主题监听
let unsubscribeTheme: (() => void) | undefined
onMounted(async () => {
// 获取版本信息
versions.value = {
node: window.electronAPI?.versions.node() || '',
electron: window.electronAPI?.versions.electron() || '',
chrome: window.electronAPI?.versions.chrome() || ''
}
// 获取当前主题
const theme = await window.electronAPI?.system.getTheme()
isDarkTheme.value = theme === 'dark'
// 监听主题变化
unsubscribeTheme = window.electronAPI?.system.onThemeChange((theme) => {
isDarkTheme.value = theme === 'dark'
document.documentElement.setAttribute('data-theme', theme)
})
// 获取窗口状态
isMaximized.value = await window.electronAPI?.window.isMaximized() || false
})
onUnmounted(() => {
unsubscribeTheme?.()
})
</script>
<style scoped>
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
background: var(--bg-primary);
color: var(--text-primary);
}
.title-bar {
height: 32px;
display: flex;
justify-content: space-between;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
-webkit-app-region: drag;
}
.title-bar-drag-area {
flex: 1;
-webkit-app-region: drag;
}
.window-controls {
display: flex;
-webkit-app-region: no-drag;
}
.control-btn {
width: 46px;
height: 32px;
border: none;
background: transparent;
color: var(--text-primary);
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.control-btn:hover {
background: var(--hover-bg);
}
.control-btn.close:hover {
background: #e81123;
color: white;
}
.main-layout {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 240px;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
.logo {
padding: 20px;
display: flex;
align-items: center;
gap: 12px;
font-size: 18px;
font-weight: 600;
border-bottom: 1px solid var(--border-color);
}
.logo img {
width: 32px;
height: 32px;
}
.nav-menu {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
color: var(--text-primary);
text-decoration: none;
transition: background 0.2s;
}
.nav-item:hover {
background: var(--hover-bg);
}
.nav-item.router-link-active {
background: var(--primary-color);
color: white;
}
.system-info {
padding: 16px;
border-top: 1px solid var(--border-color);
font-size: 12px;
color: var(--text-secondary);
}
.info-item {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
5.3 路由配置
src/renderer/router/index.ts:
javascript
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '仪表盘' }
},
{
path: '/files',
name: 'Files',
component: () => import('@/views/Files.vue'),
meta: { title: '文件管理' }
},
{
path: '/settings',
name: 'Settings',
component: () => import('@/views/Settings.vue'),
meta: { title: '设置' }
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: { title: '关于' }
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
// 更新窗口标题
document.title = `${to.meta.title || 'Electron App'} - Vue Electron`
next()
})
export default router
5.4 Pinia Store
src/renderer/stores/app.ts:
javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAppStore = defineStore('app', () => {
// State
const theme = ref<'light' | 'dark'>('light')
const sidebarCollapsed = ref(false)
const notifications = ref<Array<{ id: string; title: string; message: string; type: string }>>([])
// Getters
const isDarkTheme = computed(() => theme.value === 'dark')
// Actions
function setTheme(newTheme: 'light' | 'dark') {
theme.value = newTheme
localStorage.setItem('theme', newTheme)
document.documentElement.setAttribute('data-theme', newTheme)
}
function toggleTheme() {
setTheme(theme.value === 'light' ? 'dark' : 'light')
}
function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value
}
function addNotification(title: string, message: string, type: string = 'info') {
const id = Date.now().toString()
notifications.value.push({ id, title, message, type })
// 5秒后自动移除
setTimeout(() => {
removeNotification(id)
}, 5000)
}
function removeNotification(id: string) {
const index = notifications.value.findIndex(n => n.id === id)
if (index !== -1) {
notifications.value.splice(index, 1)
}
}
// 初始化
function init() {
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null
if (savedTheme) {
theme.value = savedTheme
document.documentElement.setAttribute('data-theme', savedTheme)
} else {
// 跟随系统主题
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
theme.value = prefersDark ? 'dark' : 'light'
}
}
init()
return {
// State
theme,
sidebarCollapsed,
notifications,
// Getters
isDarkTheme,
// Actions
setTheme,
toggleTheme,
toggleSidebar,
addNotification,
removeNotification
}
})
5.5 组合式函数
src/renderer/composables/useFileSystem.ts:
javascript
import { ref } from 'vue'
export function useFileSystem() {
const loading = ref(false)
const error = ref<string | null>(null)
// 读取文件
async function readFile(filePath: string) {
loading.value = true
error.value = null
try {
const result = await window.electronAPI.file.read(filePath)
if (result.success) {
return result.content
} else {
error.value = result.error || '读取文件失败'
return null
}
} catch (err) {
error.value = err instanceof Error ? err.message : '未知错误'
return null
} finally {
loading.value = false
}
}
// 写入文件
async function writeFile(filePath: string, content: string) {
loading.value = true
error.value = null
try {
const result = await window.electronAPI.file.write(filePath, content)
if (!result.success) {
error.value = result.error || '写入文件失败'
}
return result.success
} catch (err) {
error.value = err instanceof Error ? err.message : '未知错误'
return false
} finally {
loading.value = false
}
}
// 选择文件
async function selectFile() {
return await window.electronAPI.file.select()
}
// 选择目录
async function selectDirectory() {
return await window.electronAPI.file.selectDirectory()
}
// 保存文件对话框
async function saveFileDialog(defaultName?: string) {
return await window.electronAPI.file.saveDialog(defaultName)
}
return {
loading,
error,
readFile,
writeFile,
selectFile,
selectDirectory,
saveFileDialog
}
}
六、全局样式
src/renderer/styles/global.css:
javascript
:root {
/* Light theme */
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #333333;
--text-secondary: #666666;
--border-color: #e0e0e0;
--hover-bg: #f0f0f0;
--primary-color: #42b883;
--primary-hover: #33a06f;
--danger-color: #e74c3c;
--danger-hover: #c0392b;
--success-color: #27ae60;
--warning-color: #f39c12;
}
[data-theme="dark"] {
--bg-primary: #1e1e1e;
--bg-secondary: #2d2d2d;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--border-color: #404040;
--hover-bg: #3d3d3d;
--primary-color: #42b883;
--primary-hover: #33a06f;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
overflow: hidden;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
/* 按钮样式 */
button {
font-family: inherit;
}
/* 工具类 */
.text-center {
text-align: center;
}
.mt-4 {
margin-top: 16px;
}
.mb-4 {
margin-bottom: 16px;
}
.p-4 {
padding: 16px;
}
七、运行与调试
7.1 开发命令
javascript
# 启动开发服务器
npm run dev
# 构建
npm run build
# 打包应用
npm run electron:build
# 类型检查
npm run type-check
# 代码检查
npm run lint
7.2 调试技巧
javascript
// 在 Vue 组件中调试
console.log('Vue component log')
window.electronAPI?.system.showNotification('Debug', 'Message')
// 主进程日志
import { logger } from './utils/logger'
logger.info('Main process log')
// 使用 Vue Devtools(开发环境自动安装)
// 使用 Electron 内置 DevTools