从现有的React前端项目,封装成一个基于Electron的软件包,支持打包生成windows和linux版本 。
使用以下步骤,可以在公司当前React前端框架项目的基础上,不做较大改动,生成一个应用程序。
注:因为单机版系统在局域网或者无网络情况下,此步骤不包含版本自动升级功能。
新增package.json
// package.json
// 初始化npm配置
npm init
// 将electron、electron-builder、electron-squirrel-startup添加到开发依赖中
npm install --save-dev electron electron-builder electron-squirrel-startup
/*
注:云桌面内部electron无法安装,建议将项目放在外网环境下,开发完之后,再归档入库。
*/
创建preload.js
预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
closeApp: () => ipcRenderer.send("closeAPP"),
windowTrigger: (type) => ipcRenderer.send("windowTrigger", type),
onMainWinChange: (callback) => ipcRenderer.on('mainWin-max', callback),
// 能暴露的不仅仅是函数,我们还可以暴露变量
})
创建main.js
入口文件,确保package.json文件中“main”: "main.js"
对应起来。
// main.js
const {
app,
BrowserWindow,
ipcMain,
Menu,
Tray,
session,
} = require("electron");
const path = require("path");
const isDev = require("electron-is-dev");
let loading = null;
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require("electron-squirrel-startup")) {
app.quit();
}
// 启动动画页
const showLoading = (cb) => {
loading = new BrowserWindow({
show: false,
frame: false, // 无边框(窗口、工具栏等),只包含网页内容
width: 850,
height: 490,
maxWidth: 850,
maxHeight: 490,
resizable: false,
transparent: true, // 窗口是否支持透明,如果想做高级效果最好为true
});
loading.once("show", cb);
if (isDev) {
loading.loadURL("http://localhost:8080/app-loading.html");
} else {
loading.loadURL(`file://${__dirname}/dist/Web/TTS/app-loading.html`);
}
loading.isLoading = true
loading.show();
};
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
minWidth: 1440,
minHeight: 1000,
frame: false,
show: false,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
// webSecurity: false
// nodeIntegration: true // 允许渲染进程中使用node模块
},
});
//判断是否是开发模式
if (isDev) {
// 加载的端口号根据嵌入项目修改
mainWindow.loadURL("http://localhost:8080/"); // http://localhost:8080/ 前端开发环境地址
} else {
// console.log(__dirname);
// mainWindow.loadFile(`./dist/Web/TTS/index.html`);
mainWindow.loadURL(`file://${__dirname}/dist/Web/TTS/index.html`);
}
// 接收渲染进程的信息,渲染器触发窗口最小化、最大化和窗口化时
ipcMain.on("windowTrigger", (event, type) => {
const currentWin = BrowserWindow.getFocusedWindow();
// console.log("currentWin", currentWin);
// console.log("type", type);
if (type === "min") {
currentWin.minimize();
}
if (type === "max") {
currentWin.maximize();
}
if (type === "mid") {
currentWin.restore();
currentWin.setMinimumSize(1200, 800);
currentWin.center();
}
});
// //接收渲染进程的信息
ipcMain.on("closeAPP", () => {
const allWindows = BrowserWindow.getAllWindows();
const currentWin = BrowserWindow.getFocusedWindow();
console.log("close", allWindows.length);
if (allWindows.length > 1) {
currentWin.close();
} else {
currentWin.hide();
}
});
// ipcMain.on("setCookie", (event, cookieArr) => {
// console.log("cookieArr", cookieArr)
// for (let i = 0;i < cookieArr.length;i++) {
// const cookie = cookieArr[i]
// session.defaultSession.cookies.set(cookie)
// }
// })
// Open the DevTools.
// mainWindow.webContents.openDevTools();
// 打开新窗口重新设置配置
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// 打开Manage外链时候,新开窗口,保留自带的窗口导航栏
if (url.indexOf("/Manage/") > -1) {
return {
action: "allow",
};
}
return {
action: "allow",
overrideBrowserWindowOptions: {
frame: false,
minWidth: 1440,
minHeight: 1000,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
},
};
});
mainWindow.once("ready-to-show", () => {
loading.hide();
loading.close();
mainWindow.show();
});
// 创建托盘图标
const tray = new Tray(
path.join(app.getAppPath(), "/dist/Web/TTS/static/images/16x16.png")
);
const contextMenu = Menu.buildFromTemplate([
{
label: "打开主窗口",
click: (menuItem, browserWindow, event) => {
const allWindows = BrowserWindow.getAllWindows();
for (let i = 0; i < allWindows.length; i++) {
allWindows[i].show();
}
},
},
{
label: "隐藏主窗口",
click: (menuItem, browserWindow, event) => {
const allWindows = BrowserWindow.getAllWindows();
for (let i = 0; i < allWindows.length; i++) {
allWindows[i].hide();
}
// browserWindow.show()
},
},
{
label: "退出",
click: (menuItem, browserWindow, event) => {
app.quit();
},
},
]);
tray.setToolTip("视图全目标追踪系统");
tray.on("double-click", function (event, bounds) {
const allWindows = BrowserWindow.getAllWindows();
for (let i = 0; i < allWindows.length; i++) {
allWindows[i].show();
}
});
tray.setContextMenu(contextMenu);
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", () => showLoading(createWindow));
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
console.log("window-all-closed");
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
app.on("browser-window-created", (ev, window) => {
// console.log(ev)
console.log("new browser-window be created");
// // 如果不是loading状态,才使窗口最大化
// if (loading) {
// console.log("loading.isVisible", loading.isVisible());
// } else {
// }
// console.log("window.show", window.show)
// if (!loading && (loading && !loading.isVisible())) {
// console.log("maxxxxxx");
// // window.maximize();
// }
window.once('ready-to-show', () => {
// loading弹框不用全屏化
if (window.isLoading) {
return
}
window.maximize()
})
window.on("maximize", (e) => {
// console.log('窗口最大化时触发')
window.webContents.send("mainWin-max", true);
});
window.on("unmaximize", (e) => {
// console.log('当窗口从最大化状态退出时触发')
window.webContents.send("mainWin-max", false);
});
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
将react项目所需要的dependencies
和devDependencies
添加到package.json
,修改package.json
中name
、productName
、description
等内容。
进行 npm i
,安装剩余依赖。如果在云桌面外部且使用了@yisa私有包,需要把@yisa手动复制到node_modules
下面。
添加scripts
命令:
{
...
"scripts": {
"dev": "webpack-dev-server --config ./scripts/dev.js",
"build": "webpack --config ./scripts/build.js",
"start": "concurrently \"npm run dev\" \"electron .\"",
"ele": "electron .",
"package": "npm run build && electron-builder build --publish never",
"package-win": "npm run build && electron-builder build --win --x64",
"package-linux": "npm run build && electron-builder build --linux",
"package-mac": "npm run build && electron-builder build --mac"
},
...
}
尝试npm run dev
,查看程序是否正常启动,如有报错解决报错,此处不受Electron影响。
npm run ele
启动开发环境客户端。
正常启动。
关于打包工具,Electron官方教程使用的是electron-forge,但是因为其无法自定义修改安装位置,所以这里使用了electron-builder作为打包工具。
package.json
增加build
配置:
{
...
"build": {
"productName": "视图全目标追踪系统",
"appId": "ei_fronte01",
"copyright": "xxxx",
"files": [
"./dist/Web",
"node_modules/",
"main.js",
"preload.js"
],
"win": {
"icon": "./src/assets/icons/256x256.png",
"target": [
"nsis",
"msi"
]
},
"linux": {
"icon": "./src/assets/icons/256x256.png",
"target": [
"deb",
"rpm",
"AppImage"
],
"category": "Development"
},
"directories": {
"output": "dist/App"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
},
"electronDownload": {
"mirror": "https://npm.taobao.org/mirrors/electron/"
}
},
...
}
// 注:具体配置信息查看文档:https://www.electron.build/configuration/configuration
最后执行npm run package-win
,等待几分钟之后查看dist\App
下边的安装包。
请注意Electron不允许跨系统打包,所以打linux包需要在linux环境上。为避免在linux服务器上部署打包前端文件过于繁琐,且公司网络限制导致Electron环境安装失败问题。可以在Windows上使用electronuserland/builder Docker镜像,具体步骤详见此处。
file协议与http协议之间请求镖头无法携带cookie信息,导致后端接口不通。
解决方案:
后端去除http请求表头必须携带cookie的判断(这个判断是php方法自带的,跟后端沟通过,这个值要么是写死,要么不带的话接口就无法请求,需要在用到session这个值得地方单独处理一下。后期等更换IAM管理,这种问题就不存在了)。前端拿到token信息之后可以存到localstorage、sessionstorage或者全局变量。 单机版如果用老版后台登录权限管理的,这个问题避免不了。
asar文件可以被解密,暂不解决。
像Electron安装失败,可能是请求GitHub资源超时,可以更换npm源。
如果启动、打包失败,可能是某个配置项或者某行代码出错,这种自己排错就好。
系统整体交互优化不足,不太像个软件程序,需要与产品沟通逐步优化。
系统导航之间的调换,如果是当前系统建议在当前页面跳转,如果需要新页面打开的话,需要注意多窗口共享配置及用户信息的问题;如果是外链系统,不要使用无边框配置,因为用户打开了就没有办法关掉新窗口,毕竟外链系统无法交互electron,可以使用mainWindow.webContents.setWindowOpenHandler()
监听修改配置,详细使用方法查看此处。
main.js加载一个app-loading.html作为启动动画,需要提前配置好,功能样式根据产品需求自定。
待收集…