
哈喽大家好,我是小李。
就在昨天下午,领导突然走过来跟我说,我们的项目需要打包成客户端,这样用户下载之后就可以直接使用了!你研究一下怎么实现,然后明天跟我确认一下方案。
我思考了一下,说:好的(md), 我研究一下。
之前我们一直做的都是 web 端的应用或者是本地的小程序,虽然我也开发过一些移动端的应用,例如 android, harmony 等等,但是我还是第一次听说过还能把 web 端的应用打成桌面的安装包,然后让用户下载之后使用。
在这里说一下我们项目的背景:一个基于 Vite + React + Ant Design + Redux Persist + HashRouter 的前端单体控制台应用,以业务域目录组织页面,以聊天编排和任务联动为核心,兼具常规后台管理能力和事件驱动式智能体交互能力。
后端模块核心部分使用的 python + fastAPI + mysql + redis, 并且引入了openclaw 的架构设计模式。
说到 openclaw,不知道到今天为止迭代了多少个版本了,但是它的核心其实就是帮助用户干活,而干活的时候需要的这些数据是存储在用户的电脑上的,所以就需要让它去访问这些文件,而 web 端的应用是没有办法访问的,所以我们就需要桌面端的版本,来为它提供更丰富的能力。
接到这个需求,我先是去网上调研了一下,打包的工具有哪几种?读了很多的帖子和文章,也问了AI, 总结下来,用的比较多的主要有两种:
一种是通过 Electron 来打包,另一种是 通过 MixOne 来构建。
前者是目前市场上最主流、生态最完善的选择,比较适合我们这种已经有一定复杂度的业务项目。它的核心思想是:用 Electron 创建一个“壳”,把你现有的 Web 应用装进去。你现有的 React 路由、状态管理、Ant Design 组件几乎可以原封不动地复用。
而后者它的生态就没有 Electron 这么丰富了,它是最近几年才兴起的一个脚手架工具,它的核心优势是“去 IPC 化”。在标准 Electron 开发中,我们需要手动编写很多进程间通信的代码;而 MixOne 提供了语法糖,让你可以在前端组件中像调用普通函数一样直接调用系统 API(如文件对话框、本地数据库等),通过这种方式,我们能很显著地提升开发效率。
具体要怎么选择呢?
其实答案也显而易见,我们肯定是要用第一种方式。一方面,新的东西一般都不稳定;另一方面,因为我们的项目比较大,能求稳还是要求稳。
其实使用 Electron 的话,仍然有两种方式,一种是轻封装,另一种是深度集成。
前者是通过一个脚手架来直接打包,后者的话则需要引入更多的工具,例如进程通信等等,复杂度会明显上升,本文的话我们先以轻封装为例,深度集成的话后面有机会再来跟大家分享。
那废话不多说,我们直接开干。
官网的地址也给大家贴在下面,如果有必要的话大家可以去读一读。
https://www.electronjs.org/zh/docs/latest/
接下来,我们直接开干!
首先,需要安装一下依赖:
1、安装依赖
npm install electron electron-builder vite-plugin-electron -D直接在终端中输入上述命令,执行就好。

安装成功之后,你会看到类似上面的输出。
注意,在安装之前需要确认一下node的版本。最好是22以上。否则后面可能会遇到兼容性的问题。

2、创建主进程文件
在src 同一级别的目录下新建文件夹 electron, 里面创建一个文件:main.js,这个就是 Electron 的主进程文件。
const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = process.env.NODE_ENV === 'development';
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false, // 保持关闭,因为你不需 Node.js 能力
contextIsolation: true,
},
// 可选:隐藏菜单栏
autoHideMenuBar: true,
});
// 关键:开发环境加载 Vite 开发服务器,生产环境加载打包后的文件
if (isDev) {
win.loadURL('http://localhost:5173'); // 你的 Vite 默认端口
win.webContents.openDevTools(); // 开发时方便调试
} else {
win.loadFile(path.join(__dirname, '../dist/index.html'));
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});3、配置vite.config.js文件
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import electron from 'vite-plugin-electron';
export default defineConfig({
plugins: [
react(),
electron({
entry: 'electron/main.js', // 指定主进程入口
}),
],
base: './',
server: {
port: 5173,
},
build: {
outDir: 'dist',
},
});4、修改package.json
{
"main": "electron/main.js",
"scripts": {
"dev": "vite",
"electron:dev": "vite & electron .",
"build": "vite build",
"electron:build": "vite build && electron-builder"
},
"build": {
"appId": "com.yourcompany.yourapp",
"productName": "你的应用名称",
"directories": {
"output": "release"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"win": {
"target": "nsis",
"icon": "public/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "public/icon.icns"
}
}
}5、调整路由
如果你的 React Router 当前使用 BrowserRouter,刷新打包后的客户端可能会白屏。有两种解决方案:
方案 1:改用 HashRouter
import { HashRouter } from 'react-router-dom';
// 其他代码不变
<HashRouter>
<App />
</HashRouter>方案 2:保持 BrowserRouter,在 Electron 主进程中配置 webPreferences 和路由重定向(稍复杂,不推荐)。
npm run electron:build
如果不出意外的话,打包结束之后你能在项目中和src同一级别的目录下看到release文件夹下有几个文件:

这哥里面的 .exe 文件就是我们需要的。
但是,出意外了


这个错误是因为 Electron Builder 在尝试编译原生模块(@parcel/watcher)时,找不到 Windows 编译工具链(Visual Studio 或 Windows Build Tools)。
这里推荐两种解决方案:
第一种当然就是直接安装了。(先别急,往后看)
管理员模式打开powershell,执行下面的代码:
npm install --global windows-build-tools或者你也可以使用下面这个
npm install --global --production windows-build-tools但是后者会自动安装 Visual Studio Build Tools 和 Python 环境,以及其他的一些必要的依赖。
第二种方式更简单:前提是你的项目不需要 Electron 的原生模块支持,我们可以直接修改 build 配置。让它在打包的时候跳过原生模块的编译。

修改之后,我们再次打包。
你以为会很顺利吗?
答案是不会。

这个模块人家官方不支持了。早就废弃掉了,npm官方也说了。

这里我们直接跳过对原生模块的编译,走上面的方法二。
然后看着挺正常的,但是就刷个dy的时间就报错了。。。

这个问题也很好解决,是由于文件的权限问题导致的。
上一个打包的进程还在占用文件,我们又开始了下一次打包。因为我们的配置改了,直接结束进程就行,然后删除release文件夹下的所有内容。
再次执行打包命令。

完美!
完成打包后在 release 目录下找到 .exe 或 .dmg 安装包。

双击 .exe文件就可以安装了。
如果安装报错的话,最有可能出现问题的地方就是语法问题。
Electron 主进程文件使用了 ES Module 语法(import),但 Electron 默认使用 CommonJS(require)。
这里直接修改一下模块的导入方式就行。
改了之后再次打包,生成的文件应该就可以直接安装了。

打开之后也跟我们网页端的效果是一模一样的。
文章的最后,有两个问题一起来看看
1、安装之后并没有生成桌面快捷方式,每次运行都需要重新安装,怎么解决?
这里需要补充一下NSIS配置,安装后自动创建桌面快捷方式

2、登录模块,输入账号和密码,提示请检查网络连接(本地网络是正常的),为什么?
1)、先定位这个报错是从哪抛出来的
这里其实都不用考虑太多,你可以想一想,你打包之后的应用是不是装在D盘里(你电脑的本地文件中),而我们网络请求走的是服务器的域名,这在 Electron 渲染进程里很容易被当成跨域请求拦截掉, axios 就会落到你现在看到的“请检查网络连接”。

具体的解决方式就是在 electron\main.js 中添加这个配置,让打包后的本地页面可以正常访问远程登录接口。
还有一种方式就是把登录请求改成主进程代理或本地后端转发,说具体一点就是渲染进程不再直接 axios.post('https://agent.zzgo.com.cn/...'),而是调用 window.electronAPI.login(...),由 Electron 主进程去请求远程接口,主进程把结果再回给前端页面
这样做有很多优点:
1)、不再依赖 webSecurity: false
2)、不会被 file:// -> https:// 的跨域问题卡住
3)、后面不只是登录,所有的接口都可以走这套流程。
具体的运行效果这里就不给大家展示了,因为涉及到数据安全的问题,如果大家有更多想法,欢迎在评论区交流讨论。
好啦,本期文章就分享到这里,感谢大家的阅读,我们下期再见~