从AI模型到桌面应用:用Electron + FastAPI + YOLO打造开箱即用的智能检测系统(前端文档一)
本文介绍了一个基于Electron+FastAPI+YOLO的目标检测桌面应用开发方案。该项目通过整合YOLO目标检测、大语言模型对话和系统监控等功能,实现了图像/视频智能分析、AI问答等核心能力。文章详细阐述了技术选型(Electron跨平台、FastAPI高性能后端)、UI设计理念(深色主题+玻璃拟态)以及功能实现方案(包括视频转码、CORS处理等关键技术难点)。该应用具有可视化操作界面、批量
·
基于 Electron 构建的目标检测系统桌面应用,采用现代化深色主题设计,实现图像/视频目标检测、AI 对话、系统监控等功能。
📋 目录
项目概览
本项目是一个功能完整的目标检测系统桌面应用,具有以下特点:



核心功能
| 模块 | 功能描述 |
|---|---|
| 🖼️ 图像目标检测 | 上传图片、YOLO 目标检测、显示检测框 |
| 🎬 视频目标检测 | 上传视频、逐帧检测、播放检测结果 |
| 📝 系统日志 | 查看系统运行日志、实时刷新 |
| 💻 系统监控 | CPU/内存/磁盘实时监控、ECharts 可视化 |
| 📁 资源管理 | 图片/视频资源浏览、下载、删除 |
| 💬 大模型聊天 | 集成 LLM API、多模型选择、参数调节 |
| 👁️ 多模态聊天 | 图文混合对话、视觉语言模型 |
| 📖 项目文档 | 外部文档链接跳转 |
| 👥 访客记录 | 访问记录展示 |
设计特色
-
深色主题:专业科技感界面,护眼低亮度设计
-
玻璃拟态:毛玻璃效果(Glassmorphism)
-
流畅动画:淡入淡出、弹性变换效果
-
响应式布局:自适应窗口大小
-
语义化 HTML:良好的可访问性
技术架构
技术栈
| 层级 | 技术 | 说明 |
|---|---|---|
| 运行时 | Electron 33.0.0 | 跨平台桌面应用框架 |
| 主进程 | Node.js | 系统交互、窗口管理 |
| 渲染进程 | HTML5 + CSS3 + JavaScript | 界面渲染与交互 |
| 图标库 | Font Awesome 6.4.0 | 矢量图标 |
| 图表库 | ECharts 5.4.3 | 数据可视化 |
| 字体 | Inter (Google Fonts) | 现代无衬线字体 |
架构图
┌─────────────────────────────────────────────────────────────┐ │ Electron Application │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ IPC ┌─────────────────────────────┐ │ │ │ Main Process│◄──────────►│ Renderer Process │ │ │ │ (main.js) │ │ │ │ │ │ │ │ ┌───────────────────────┐ │ │ │ │ - 窗口管理 │ │ │ index.html │ │ │ │ │ - 系统交互 │ │ │ styles.css │ │ │ │ │ - 外链处理 │ │ │ renderer.js │ │ │ │ └─────────────┘ │ └───────────────────────┘ │ │ │ │ │ │ │ ┌─────────────┐ │ ┌───────────────────────┐ │ │ │ │ Preload │ │ │ Backend API │ │ │ │ │(preload.js) │ │ │ http://localhost:10077 │ │ │ │ │ 安全桥接 │ │ │ │ │ │ │ - API 暴露 │◄──────────►│ │ - FastAPI 服务 │ │ │ │ │ - 版本信息 │ │ │ - YOLO 检测 │ │ │ │ └─────────────┘ │ │ - LLM/VLLM 对话 │ │ │ │ │ └───────────────────────┘ │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
项目结构
electron-app2/ ├── 📄 package.json # 项目配置 ├── 📄 main.js # Electron 主进程 ├── 📄 preload.js # 预加载脚本(安全桥接) ├── 📄 index.html # 主界面 HTML ├── 📄 styles.css # 全局样式表 ├── 📄 renderer.js # 渲染进程逻辑(核心业务) ├── 📁 images/ # 静态图片资源 │ └── ... ├── 📁 backend/ # 后端 API 服务 │ └── ... ├── 📄 前端界面.md # 本文档 └── 📄 后端文档.md # 后端 API 文档
快速开始
1. 安装依赖
npm install
2. 启动开发模式
npm run dev # 或 npm start
3. 启动后端服务
cd backend python main.py
4. 应用预览
应用启动后将显示目标检测系统桌面窗口,包含:
-
左侧导航菜单
-
顶部面包屑和时间
-
中央内容区域
Electron 配置详解
1. 主进程 (main.js)
主进程负责创建窗口、管理应用生命周期和系统交互。
const { app, BrowserWindow, shell, Menu } = require('electron')
const path = require('path')
function createWindow() {
// 隐藏默认菜单栏
Menu.setApplicationMenu(null)
const mainWindow = new BrowserWindow({
width: 1400, // 默认宽度
height: 900, // 默认高度
minWidth: 1000, // 最小宽度
minHeight: 700, // 最小高度
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false, // 禁用 Node.js 集成(安全)
contextIsolation: true // 启用上下文隔离(安全)
},
icon: path.join(__dirname, 'icon.png'),
title: '目标检测系统',
show: false // 先隐藏,避免白屏闪烁
})
mainWindow.loadFile('index.html')
// 窗口准备好后再显示
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
// 拦截外部链接,使用系统默认浏览器打开
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url)
return { action: 'deny' }
})
}
安全配置说明:
| 配置项 | 值 | 说明 |
|---|---|---|
nodeIntegration |
false |
禁止渲染进程直接访问 Node.js API |
contextIsolation |
true |
隔离预加载脚本与网页脚本的上下文 |
preload |
指定脚本 | 安全地暴露必要 API 给渲染进程 |
2. 预加载脚本 (preload.js)
预加载脚本是主进程和渲染进程之间的安全桥梁。
const { contextBridge, ipcRenderer, shell } = require('electron')
// 安全地暴露 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 发送消息到主进程
sendMessage: (channel, data) => {
const validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
// 接收主进程消息
onMessage: (channel, callback) => {
const validChannels = ['fromMain']
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => callback(...args))
}
},
// 打开外部链接
openExternal: (url) => {
shell.openExternal(url)
}
})
// 暴露版本信息
contextBridge.exposeInMainWorld('versions', {
node: process.versions.node,
chrome: process.versions.chrome,
electron: process.versions.electron
})
暴露的 API:
| API | 用途 |
|---|---|
electronAPI.sendMessage |
向主进程发送消息 |
electronAPI.onMessage |
监听主进程消息 |
electronAPI.openExternal |
使用系统浏览器打开 URL |
versions |
获取运行时版本信息 |
界面结构
1. HTML 布局 (index.html)
采用经典的侧边栏 + 主内容区布局:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>目标检测系统</title> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> </head> <body> <!-- 侧边栏导航 --> <aside class="sidebar"> <div class="sidebar-header"> <h1><i class="fas fa-microchip"></i> 目标检测系统</h1> <span>v1.0.0 | AI Analysis System</span> </div> <nav class="menu-list" id="sidebar-menu"> <div class="menu-item active" data-view="image-detection"> <i class="fas fa-image"></i> 图像目标检测 </div> <!-- 更多菜单项... --> </nav> </aside> <!-- 主内容区 --> <main class="main-content"> <header class="content-header"> <div class="breadcrumb"> <span>系统</span> / <span class="current" id="current-view-name">图像目标检测</span> </div> <div class="header-actions"> <span id="system-time">2026-01-29 17:54</span> </div> </header> <div class="viewport" id="main-viewport"> <!-- 动态内容注入区域 --> </div> </main> <script src="renderer.js"></script> </body> </html>
导航菜单项 (10个):
| 菜单项 | data-view | 图标 |
|---|---|---|
| 图像目标检测 | image-detection |
fa-image |
| 视频目标检测 | video-detection |
fa-video |
| 系统日志 | system-logs |
fa-file-alt |
| 图像资源下载 | image-resources |
fa-images |
| 视频资源下载 | video-resources |
fa-film |
| 系统监控 | monitoring |
fa-microchip |
| 项目文档 | docs |
fa-book |
| 访客记录 | visitors |
fa-users |
| 大模型聊天 | chat-llm |
fa-comment-dots |
| 多模态聊天 | chat-multimodal |
fa-photo-video |
样式系统
1. 设计令牌 (CSS Variables)
样式系统采用 CSS 自定义属性(Design Tokens)实现主题化管理:
:root {
/* === 颜色系统 === */
--bg-dark: #0f1115; /* 主背景色 */
--bg-deep: #050505; /* 深色背景(侧边栏) */
--surface-dark: #181b21; /* 卡片/面板背景 */
--surface-hover: #1f232b; /* 悬停状态背景 */
/* 品牌色 */
--primary: #3b82f6; /* 主色(蓝色) */
--primary-hover: #2563eb; /* 主色悬停 */
--primary-glow: rgba(59, 130, 246, 0.4); /* 主色发光 */
/* 辅助色 */
--secondary: #64748b; /* 次要色 */
--accent: #8b5cf6; /* 强调色(紫色) */
--danger: #ef4444; /* 危险色(红色) */
--success: #10b981; /* 成功色(绿色) */
/* 文本色 */
--text-primary: #f8fafc; /* 主文本(白) */
--text-secondary: #94a3b8; /* 次文本(灰) */
--text-tertiary: #64748b; /* 辅助文本(深灰) */
/* 边框色 */
--border-light: rgba(255, 255, 255, 0.08); /* 轻边框 */
--border-glass: rgba(255, 255, 255, 0.13); /* 玻璃边框 */
/* === 玻璃拟态 === */
--glass-bg: rgba(22, 27, 34, 0.7);
--glass-blur: 16px;
--glass-border: 1px solid rgba(255, 255, 255, 0.06);
--glass-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
/* === 布局 === */
--sidebar-width: 260px;
--header-height: 64px;
--radius-sm: 6px;
--radius-md: 12px;
--radius-lg: 16px;
/* === 动画 === */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--transition: all 0.2s ease;
}
2. 组件样式
侧边栏 (Sidebar)
.sidebar {
width: var(--sidebar-width);
background: var(--bg-deep);
border-right: 1px solid var(--border-light);
display: flex;
flex-direction: column;
padding: 24px 0;
z-index: 100;
}
.menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--text-secondary);
transition: var(--transition);
}
.menu-item.active {
background: rgba(59, 130, 246, 0.1);
color: var(--primary);
font-weight: 600;
}
.menu-item.active i {
transform: scale(1.1);
filter: drop-shadow(0 0 5px var(--primary-glow));
}
卡片 / 玻璃面板
.card, .glass-panel {
background: var(--surface-dark);
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 24px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: var(--transition);
}
.card:hover {
border-color: var(--border-glass);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
transform: translateY(-2px); /* 微上浮效果 */
}
.glass-panel {
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur)); /* 毛玻璃效果 */
}
按钮系统
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-weight: 500;
transition: var(--transition);
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--primary);
color: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
background: var(--primary-hover);
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4);
}
.btn-outline {
background: transparent;
border: 1px solid var(--border-glass);
color: var(--text-secondary);
}
.btn-danger {
background: rgba(239, 68, 68, 0.1);
color: var(--danger);
border: 1px solid rgba(239, 68, 68, 0.2);
}
.btn-sm { padding: 6px 12px; font-size: 0.8rem; }
上传区域
.upload-zone {
border: 2px dashed var(--border-light);
border-radius: var(--radius-md);
padding: 60px 20px;
text-align: center;
background: rgba(255, 255, 255, 0.02);
transition: var(--transition);
cursor: pointer;
}
.upload-zone:hover {
border-color: var(--primary);
background: rgba(59, 130, 246, 0.05);
}
.upload-zone:hover .upload-icon {
color: var(--primary);
transform: translateY(-5px); /* 上浮动画 */
}
检测标注框
.detection-container {
position: relative;
display: inline-block;
width: 100%;
}
.detection-box {
position: absolute;
border: 2px solid var(--primary);
box-shadow: 0 0 8px var(--primary-glow);
background: rgba(59, 130, 246, 0.1);
pointer-events: none;
}
.detection-label {
position: absolute;
top: -24px;
left: -2px;
background: var(--primary);
color: #fff;
font-size: 11px;
padding: 4px 8px;
border-radius: 4px;
font-weight: 600;
}
聊天气泡
.message {
max-width: 80%;
padding: 16px 20px;
border-radius: 20px;
line-height: 1.6;
}
.message.user {
align-self: flex-end;
background: var(--primary);
color: #fff;
border-bottom-right-radius: 4px;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.message.ai {
align-self: flex-start;
background: var(--surface-dark);
border: 1px solid var(--border-light);
color: var(--text-primary);
border-bottom-left-radius: 4px;
}
动画效果
/* 淡入动画 */
.fade-in {
animation: fadeIn 0.4s ease forwards;
opacity: 0;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 加载动画 */
.loading-dots::after {
content: '...';
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 0.3; }
50% { opacity: 1; }
100% { opacity: 0.3; }
}
渲染逻辑
1. 应用状态管理
使用简单的对象作为全局状态容器:
const API_BASE = 'http://localhost:10077';
const state = {
currentViewId: 'image-detection', // 当前视图 ID
charts: {}, // ECharts 实例
logs: [], // 日志数据
uploadedImages: [], // 上传的图片列表
detectedImages: [], // 检测结果列表
llmModels: [], // LLM 模型列表
isChatting: false // 聊天状态
};
2. 视图路由系统
采用单页应用 (SPA) 路由模式,通过 JavaScript 动态切换视图:
const Views = {
'image-detection': {
title: '图像目标检测',
render: () => `<div class="fade-in">...</div>`,
init: async () => { /* 初始化逻辑 */ },
destroy: () => { /* 销毁逻辑 */ }
},
'video-detection': { ... },
'system-logs': { ... },
// ...更多视图
};
// 视图切换函数
function switchView(id) {
// 销毁当前视图
if (Views[state.cId]?.destroy) Views[state.cId].destroy();
state.cId = id;
// 更新菜单激活状态
document.querySelectorAll('.menu-item').forEach(el =>
el.classList.toggle('active', el.dataset.view === id)
);
// 更新面包屑
document.getElementById('current-view-name').textContent = Views[id].title;
// 渲染新视图
const viewport = document.getElementById('main-viewport');
viewport.innerHTML = Views[id].render();
// 初始化新视图
if (Views[id].init) Views[id].init();
}
// 菜单点击事件
document.getElementById('sidebar-menu').onclick = (e) => {
const item = e.target.closest('.menu-item');
if (item) switchView(item.dataset.view);
};
视图生命周期:
创建 激活 销毁 │ │ │ ▼ ▼ ▼ render() ──────────► init() ──────────────► destroy() │ │ │ │ 返回 HTML 字符串 │ 异步初始化逻辑 │ 清理定时器等 │ 并写入 DOM │ 加载数据、绑定事件 │
3. API 通信层
封装统一的 API 调用方法:
const api = {
async call(endpoint, method = 'GET', body = null, isForm = false) {
try {
const options = { method };
if (body) {
if (isForm) {
// FormData (文件上传)
options.body = body;
} else {
// JSON 数据
options.headers = { 'Content-Type': 'application/json' };
options.body = JSON.stringify(body);
}
}
const res = await fetch(`${API_BASE}${endpoint}`, options);
if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
return await res.json();
} catch (e) {
console.error(`API Error [${endpoint}]:`, e);
showToast(`请求失败: ${e.message}`, 'error');
return null;
}
}
};
// 使用示例
const result = await api.call('/api/images/list');
const uploadRes = await api.call('/api/images/upload/test.jpg', 'POST', formData, true);
Toast 提示:
const showToast = (msg, type = 'info') => {
let toast = document.getElementById('global-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'global-toast';
toast.className = 'toast';
document.body.appendChild(toast);
}
toast.textContent = msg;
toast.style.backgroundColor = type === 'error' ? 'var(--danger)' : 'var(--surface-dark)';
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
};
联系方式
-
微信公众号:DetectionHub
更多推荐


所有评论(0)