当前位置: 首页 > news >正文

Vue3 + Electron + Node.js 桌面项目完整开发指南

以下是包含前后端全链路的开发方案,从项目创建到部署的全流程说明,附带详细注释。

一、技术栈总览

模块技术选型说明
桌面端框架Vue3 + Vite + Electron构建跨平台桌面应用
本地数据库SQLite3 + typeorm本地数据缓存
状态管理Pinia管理应用状态(含登录状态)
UI 组件Element Plus桌面级 UI 组件库
服务端Node.js + Express + MySQL提供 API 接口、鉴权、数据同步
通信协议Axios(HTTP)+ WebSocket(聊天)接口请求与实时通信
打包工具Electron-builder打包成桌面应用

二、项目架构设计

plaintext

project/
├── client/                # 桌面端(Vue3 + Electron)
│   ├── electron/          # Electron主进程
│   │   ├── main.js        # 窗口管理、IPC主进程逻辑
│   │   ├── preload.js     # 渲染进程与主进程通信桥接
│   │   └── db/            # SQLite操作
│   │       ├── connection.js  # 数据库连接
│   │       └── models/     # 数据模型(用户、聊天记录等)
│   ├── src/               # Vue渲染进程
│   │   ├── api/           # 接口请求封装
│   │   ├── components/    # 公共组件(图片、音视频等)
│   │   ├── router/        # 路由(含鉴权守卫)
│   │   ├── stores/        # Pinia状态管理
│   │   ├── utils/         # 工具函数(加密、日期等)
│   │   ├── views/         # 页面(登录、聊天、表格等)
│   │   └── main.js        # Vue入口
│   └── package.json       # 客户端依赖
├── server/                # 服务端(Node.js)
│   ├── src/
│   │   ├── config/        # 配置(数据库、端口等)
│   │   ├── controller/    # 接口逻辑
│   │   ├── middleware/    # 中间件(鉴权、日志等)
│   │   ├── model/         # 服务端数据模型
│   │   ├── router/        # 接口路由
│   │   └── app.js         # 服务端入口
│   └── package.json       # 服务端依赖
└── README.md              # 项目说明

三、详细开发步骤

1. 创建服务端(Node.js)
① 初始化服务端项目
mkdir project && cd project
mkdir server && cd server
npm init -y
npm install express mysql2 sequelize jsonwebtoken cors ws dotenv  # 核心依赖
npm install nodemon --save-dev  # 开发热重载
② 配置服务端基础结构

通过以上步骤,可完成一个功能完整的桌面应用,支持本地缓存、云端同步、多媒体处理和实时聊天等核心需求。

六、注意事项

  • 数据库配置server/src/config/db.js):
    const { Sequelize } = require('sequelize');
    require('dotenv').config(); // 加载环境变量// 连接MySQL(服务端数据库)
    const sequelize = new Sequelize(process.env.DB_NAME || 'electron_app',process.env.DB_USER || 'root',process.env.DB_PASS || '123456',{host: process.env.DB_HOST || 'localhost',dialect: 'mysql'}
    );// 测试连接
    sequelize.authenticate().then(() => console.log('服务端数据库连接成功')).catch(err => console.error('连接失败:', err));module.exports = sequelize;

  • 用户模型server/src/model/user.js):
  • const { DataTypes } = require('sequelize');
    const sequelize = require('../config/db');// 服务端用户表(存储用户账号信息)
    const User = sequelize.define('User', {username: {type: DataTypes.STRING,unique: true,allowNull: false},password: {type: DataTypes.STRING,allowNull: false // 存储加密后的密码},avatar: {type: DataTypes.STRING, // 头像URLdefaultValue: ''}
    });// 同步表结构(开发环境)
    User.sync();module.exports = User;

  • 鉴权中间件server/src/middleware/auth.js):
    const jwt = require('jsonwebtoken');
    require('dotenv').config();// 验证token的中间件
    const auth = (req, res, next) => {const token = req.headers.authorization?.split(' ')[1]; // 从请求头获取tokenif (!token) return res.status(401).json({ msg: '未登录' });try {// 验证token(密钥从环境变量获取)const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret_key');req.user = decoded; // 将用户信息挂载到reqnext();} catch (err) {res.status(401).json({ msg: 'token无效' });}
    };module.exports = auth;

  • 登录接口server/src/controller/authController.js):
    const User = require('../model/user');
    const bcrypt = require('bcrypt');
    const jwt = require('jsonwebtoken');// 用户登录
    exports.login = async (req, res) => {const { username, password } = req.body;try {// 查询用户const user = await User.findOne({ where: { username } });if (!user) return res.status(400).json({ msg: '用户不存在' });// 验证密码(bcrypt比对)const isMatch = await bcrypt.compare(password, user.password);if (!isMatch) return res.status(400).json({ msg: '密码错误' });// 生成token(有效期24小时)const token = jwt.sign({ id: user.id, username: user.username },process.env.JWT_SECRET || 'secret_key',{ expiresIn: '24h' });res.json({success: true,token,user: { id: user.id, username: user.username, avatar: user.avatar }});} catch (err) {console.error(err);res.status(500).json({ msg: '服务器错误' });}
    };

  • WebSocket 聊天服务server/src/app.js 中集成):
    const express = require('express');
    const http = require('http');
    const WebSocket = require('ws');
    const cors = require('cors');
    const sequelize = require('./config/db');
    const auth = require('./middleware/auth');const app = express();
    const server = http.createServer(app);
    const wss = new WebSocket.Server({ server }); // 创建WebSocket服务// 中间件
    app.use(cors()); // 允许跨域
    app.use(express.json()); // 解析JSON请求// 路由示例(登录接口)
    app.post('/api/login', require('./controller/authController').login);// WebSocket连接处理(实时聊天)
    wss.on('connection', (ws) => {console.log('新客户端连接');// 接收消息并广播给所有客户端ws.on('message', (data) => {const message = JSON.parse(data.toString());// 广播消息wss.clients.forEach(client => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify(message));}});});ws.on('close', () => {console.log('客户端断开连接');});
    });// 启动服务
    const PORT = process.env.PORT || 3000;
    server.listen(PORT, () => {console.log(`服务端运行在 http://localhost:${PORT}`);
    });

  • 启动配置server/package.json 添加脚本):
    "scripts": {"start": "node src/app.js","dev": "nodemon src/app.js"
    }
    2. 创建客户端(Vue3 + Electron)
    ① 初始化 Vue 项目并集成 Electron
    cd ..  # 返回project目录
    npm create vite@latest client -- --template vue  # 创建Vue项目
    cd client
    npm install
    npm install electron electron-builder vite-plugin-electron electron-squirrel-startup --save-dev  # Electron相关依赖
    npm install sqlite3 typeorm pinia element-plus axios vue-router video.js  # 核心依赖
    ② 配置 Vite 集成 Electron(client/vite.config.js):
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import electron from 'vite-plugin-electron';export default defineConfig({plugins: [vue(),electron({entry: 'electron/main.js'  // Electron主进程入口})]
    });
    ③ 配置 Electron 主进程(client/electron/main.js):
    const { app, BrowserWindow, ipcMain } = require('electron');
    const path = require('path');
    const { initDB } = require('./db/connection'); // 导入SQLite初始化// 确保应用单实例运行
    if (!app.requestSingleInstanceLock()) {app.quit();
    }// 创建窗口函数
    function createWindow() {const mainWindow = new BrowserWindow({width: 1200,height: 800,webPreferences: {preload: path.join(__dirname, 'preload.js'), // 预加载脚本nodeIntegration: false, // 禁用节点集成(安全)contextIsolation: true // 启用上下文隔离}});// 开发环境加载Vite服务,生产环境加载本地文件if (process.env.VITE_DEV_SERVER_URL) {mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL);mainWindow.webContents.openDevTools(); // 开发环境打开调试工具} else {mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));}
    }// 初始化SQLite数据库并创建窗口
    app.whenReady().then(async () => {await initDB(); // 初始化本地数据库createWindow();app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) createWindow();});
    });// 关闭所有窗口时退出应用(macOS除外)
    app.on('window-all-closed', () => {if (process.platform !== 'darwin') app.quit();
    });// IPC主进程处理(示例:登录验证)
    ipcMain.handle('login', async (_, { username, password }) => {// 调用服务端登录接口try {const response = await fetch('http://localhost:3000/api/login', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ username, password })});const data = await response.json();// 登录成功后缓存用户信息到本地数据库if (data.success) {const { User } = require('./db/models/User');await User.upsert({  // 存在则更新,不存在则插入username: data.user.username,password: password, // 实际项目中应加密存储token: data.token});}return data;} catch (err) {return { success: false, msg: '接口请求失败' };}
    });
    ④ 配置预加载脚本(client/electron/preload.js):
    const { contextBridge, ipcRenderer } = require('electron');// 暴露有限的API给渲染进程(安全通信)
    contextBridge.exposeInMainWorld('electron', {ipcRenderer: {invoke: (channel, data) => ipcRenderer.invoke(channel, data), // 调用主进程方法on: (channel, callback) => ipcRenderer.on(channel, callback) // 监听主进程事件}
    });
    ⑤ 配置本地 SQLite 数据库(client/electron/db/connection.js):
    const { DataSource } = require('typeorm');
    const path = require('path');
    const { app } = require('electron');// 数据库存储路径(用户数据目录,避免权限问题)
    const dbPath = path.join(app.getPath('userData'), 'local.db');// 创建数据源
    const AppDataSource = new DataSource({type: 'sqlite',database: dbPath,entities: [path.join(__dirname, 'models/*.js')], // 数据模型路径synchronize: true, // 开发环境自动同步表结构(生产环境建议关闭)logging: false // 关闭日志
    });// 初始化数据库连接
    exports.initDB = async () => {if (!AppDataSource.isInitialized) {await AppDataSource.initialize();console.log('本地SQLite数据库初始化成功');}
    };exports.AppDataSource = AppDataSource;
    ⑥ 创建本地用户模型(client/electron/db/models/User.js):
    const { Entity, Column, PrimaryColumn } = require('typeorm');@Entity()
    exports.User = class User {@PrimaryColumn() // 用户名作为主键username;@Column() // 加密后的密码password;@Column({ default: '' }) // 登录tokentoken;@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) // 最后登录时间lastLogin;
    };
    ⑦ 配置 Vue 路由与鉴权(client/src/router/index.js):
    import { createRouter, createWebHashHistory } from 'vue-router';
    import { useUserStore } from '../stores/user';const routes = [{path: '/login',component: () => import('../views/Login.vue'),meta: { noAuth: true } // 无需登录},{path: '/',component: () => import('../views/Home.vue'),meta: { requiresAuth: true }, // 需要登录children: [{ path: 'chat', component: () => import('../views/Chat.vue') },{ path: 'media', component: () => import('../views/Media.vue') },{ path: 'table', component: () => import('../views/Table.vue') }]}
    ];const router = createRouter({history: createWebHashHistory(),routes
    });// 路由守卫:未登录拦截
    router.beforeEach((to, from, next) => {const userStore = useUserStore();// 需要登录但未登录时,跳转登录页if (to.meta.requiresAuth && !userStore.isLogin) {next('/login');} else {next();}
    });export default router;
    ⑧ 登录页面实现(client/src/views/Login.vue):
    <template><el-form :model="form" label-width="80px" @submit.prevent="handleLogin"><el-form-item label="用户名"><el-input v-model="form.username"></el-input></el-form-item><el-form-item label="密码"><el-input v-model="form.password" type="password"></el-input></el-form-item><el-form-item><el-button type="primary" @click="handleLogin">登录</el-button></el-form-item></el-form>
    </template><script setup>
    import { ref } from 'vue';
    import { useRouter } from 'vue-router';
    import { useUserStore } from '../stores/user';const form = ref({ username: '', password: '' });
    const router = useRouter();
    const userStore = useUserStore();// 登录处理
    const handleLogin = async () => {// 调用主进程的登录接口const result = await window.electron.ipcRenderer.invoke('login', form.value);if (result.success) {// 登录成功,保存状态到PiniauserStore.setUser({username: result.user.username,token: result.token});router.push('/chat'); // 跳转到聊天页} else {alert(result.msg);}
    };
    </script>
    ⑨ 聊天功能实现(client/src/views/Chat.vue):
    <template><div class="chat-container"><div class="messages"><div v-for="msg in messages" :key="msg.id"><strong>{{ msg.username }}:</strong> {{ msg.content }}</div></div><el-input v-model="message" placeholder="输入消息" @keyup.enter="sendMessage"></el-input></div>
    </template><script setup>
    import { ref, onMounted } from 'vue';
    import { useUserStore } from '../stores/user';const userStore = useUserStore();
    const messages = ref([]);
    const message = ref('');
    let ws = null;// 初始化WebSocket连接
    onMounted(() => {// 连接服务端WebSocket(带token鉴权)ws = new WebSocket(`ws://localhost:3000?token=${userStore.token}`);// 接收消息ws.onmessage = (event) => {const data = JSON.parse(event.data);messages.value.push(data);// 保存到本地数据库window.electron.ipcRenderer.invoke('saveChatMessage', data);};// 加载本地历史消息loadLocalMessages();
    });// 发送消息
    const sendMessage = () => {if (!message.value) return;const msg = {id: Date.now(),username: userStore.user.username,content: message.value,time: new Date().toISOString()};ws.send(JSON.stringify(msg)); // 发送到服务端message.value = '';
    };// 加载本地缓存的聊天记录
    const loadLocalMessages = async () => {const localMsgs = await window.electron.ipcRenderer.invoke('getChatMessages');messages.value = localMsgs;
    };
    </script>

    四、运行与部署

    1. 运行服务端
    cd server
    npm run dev  # 启动开发服务器(默认3000端口)
    2. 运行客户端(开发模式)
    cd client
    npm run dev  # 启动Vue + Electron开发环境
    3. 打包客户端(生成桌面应用)
  • 配置client/package.json
    "scripts": {"dev": "vite","build": "vite build && electron-builder"
    },
    "build": {"appId": "com.example.electronapp","productName": "MyElectronApp","directories": {"output": "dist-electron"},"win": {"target": "nsis"  // Windows安装包},"mac": {"target": "dmg"   // macOS安装包},"linux": {"target": "deb"   // Linux安装包}
    }

  • 执行打包:
    cd client
    npm run build  # 生成安装包(在dist-electron目录)

    五、核心功能说明

  • 数据同步策略

    • 本地优先:查询数据时先读 SQLite,提升响应速度
    • 后台同步:定期调用接口拉取更新,差异数据写入本地
    • 提交更新:修改数据时先更本地,再异步同步到服务端
  • 鉴权流程

    • 客户端登录 → 服务端验证 → 返回 JWT token
    • token 存储在本地 SQLite + Pinia
    • 接口请求时通过 Axios 拦截器自动添加 token
    • 路由守卫拦截未登录状态
  • 多媒体处理

    • 图片:通过 FileReader 转 Base64,存储本地或上传服务端
    • 音视频:使用 video.js 播放,支持本地文件和网络地址
    • 聊天:WebSocket 实时通信,消息本地缓存 + 服务端同步
  • 安全问题

    • 密码必须加密存储(bcrypt)
    • 避免在渲染进程直接操作文件系统,通过 IPC 由主进程处理
    • JWT 密钥不要硬编码,使用环境变量
  • 性能优化

    • SQLite 大量数据查询需添加索引
    • 大文件(音视频)建议分片上传
    • 聊天记录分页加载,避免一次性加载过多
  • 跨平台兼容

    • 文件路径处理使用path模块,避免硬编码斜杠
    • 不同系统的权限差异(如 macOS 的沙箱机制)
http://www.dtcms.com/a/535834.html

相关文章:

  • 【Node.js】Node.js 模块系统
  • 古籍影文公开古籍OCR检测数据集VOC格式共计8个文件
  • 网站的对比哪些网站是做免费推广的
  • 网站建设的整体流程有哪些?建筑工程网站建站方案
  • 区块链的密码学基石:沙米尔秘密共享(SSS)数学原理详解
  • 单例模式详解:从基础到高级的八种实现方式
  • 改版网站收费wordpress国人主题
  • web3.0是什么
  • 计网:网络层
  • git学习3
  • HarmonyOS图形图像处理与OpenGL ES实战
  • SunX:以合规正品,重塑Web3交易信任
  • nacos 使用oceanbase(oracle模式)作为数据源
  • 网站排名优化策划网站一个人可以做吗
  • 基于springboot的民宿在线预定平台开发与设计
  • 脚本探索--Spatial HD进行CNV分析
  • 介绍一下Hystrix的“舱壁模式”和“熔断状态机”
  • 基数排序(Radix Sort)算法简介
  • 【C++项目】基于设计模式的同步异步日志系统(前置基础知识)
  • JDK8时间相关类,时间对象都是不可变的
  • Java内存模型(JMM)与JVM内存模型
  • h5响应式网站模板如何做公司自己的网站首页
  • CentOS7 使用 centos-release-scl-rh yum库安装 devtoolset
  • UI自动化测试:Jenkins配置
  • 软件开发公司网站模板网站开发工程师绩效
  • c++中list详解
  • 杨凌美畅用 TDengine 时序数据库,支撑 500 条产线 2 年历史数据追溯
  • 4.Rocky Linux 网络配置
  • <数据集>yolo螺丝螺母识别数据集<目标检测>
  • Visual Studio 2022 安装使用:Entity Framework Core