基于 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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐