# 本代码仅适用于window环境
# 请在此目录下新建文件夹pc,pc内容来自SVN: https://xxxxxx.com/src
## API服务器
手动配置(默认 https://www.xxxxxx.com/demo)
## 推送服务器
取apiServer的一个端口
## 更新服务器
1. dev-app-update.yml
```yml
provider: generic
url: 'https://www.xxxxxx.com/demo/update/'
updaterCacheDirName: xxxxxx-desk-updater
```
2. main.js
```js
const updateServer = 'https://www.xxxxxx.com/demo/update/';
```
3. package.json
``` json
"publish": [{"url": "https://www.xxxxxx.com/demo/update/"}],
```
## 信息
name:xxxxxx-desk
author:(文字)
description:(文字)
## 图标
托盘: PNG 格式,正方形,尺寸无要求
运行程序图标: 256x256.ico
安装程序图标: 256x256.ico
卸载程序图标: 256x256.ico
安装过程显示的头部图标: 256x256.ico
# dev-app-update.yml
```yml
provider: generic
url: 'https://www.xxxxxx.com/demo/update/'
updaterCacheDirName: xxxxxx-desk-updater
```
# electron-builder
使用electron-builder对Electron项目进行打包
https://blog.csdn.net/qubernet/article/details/104409672?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
## Electron应用使用electron-builder配合electron-updater实现自动更新
https://segmentfault.com/a/1190000012904543
```
npm start
npm i electron-squirrel-startup --save
npm install electron-updater --save
```
```javascript
if (typeof module === 'object') {window.jQuery = window.$ = module.exports;};
```
```json
{
"name": "xxxxxx-desk",
"author": "公司名称有限公司",
"productName": "软件名",
"version": "1.0.4",
"description": "软件名-数字化管理平台",
"main": "./src/main.js",
"scripts": {
"start": "set NODE_ENV=development&& electron ./src/main.js",
"test": "set NODE_ENV=development&& electron ./src/test.js",
"build": "set NODE_ENV=production&& electron-builder --win --x64"
},
"keywords": [],
"license": "ISC",
"devDependencies": {
"electron": "^11.0.0"
},
"files": [
"dist/electron/**/*"
],
"build": {
"productName": "软件名",
"appId": "com.xxxxxx.www",
"copyright": "公司名称有限公司",
"directories": {
"output": "out"
},
"publish": [
{
"provider": "generic",
"url": "https://www.xxxxxx.com/update/"
}
],
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./src/img/logo.ico",
"uninstallerIcon": "./src/img/logo.ico",
"installerHeaderIcon": "./src/img/logo.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "软件名 1.0.4"
},
"win": {
"icon": "./src/img/logo.ico",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
]
}
},
"dependencies": {
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "^4.3.5",
"fs-extra": "^9.0.1"
}
}
```
```js
//main.js
const {
app,
BrowserWindow,
globalShortcut,
Menu,
ipcMain,
Tray,
Notification,
screen
} = require('electron');
const { autoUpdater } = require('electron-updater');
const dataPath = app.getPath('userData')
const path = require('path');
const net = require('net');
const fs = require('fs-extra')
const configFile = dataPath + '/xxxxxx-config.json';
const appName = "软件名桌面端";
const srcPath = path.join(__dirname, './');
const pcPath = path.join(__dirname, '../pc');
console.log(dataPath)
let appConfig = { tenant: '', currentUser: '', updateServer: 'https://www.xxxxxx.com/update/', apiServer: 'https://www.xxxxxx.com/demo/', socketPort: 17999 };
//socket在线测试 http://tcplab.openluat.com/
if (fs.existsSync(configFile)) {
getAppConfig();
} else {
updateAppConfig()// 本地配置文件不存在
}
let mainWindow, updateWindow, configWindow;
let appTenantModeEnabled = true;
let socketClient = new net.Socket()
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
}
ipcMainForPreload();
const createMainWindow = () => {
console.log(" ======== MainWindow create =========")
if (mainWindow) {
return
}
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
webSecurity: false,
preload: srcPath + '/js/preload.js',
defaultFontFamily: {
standard: 'Microsoft YaHei'
},
minimumFontSize: 12
}
});
// mainWindow.webContents.openDevTools();
mainWindow.loadURL(pcPath + '/index.html');
console.log('loadURL: ' + pcPath + '/index.html')
mainWindow.on('close', (event) => {
if (!appIcon.isQuiting) {
event.preventDefault()
mainWindow.hide();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
appIcon.create();
if (typeof updateWindow.close == 'function') {
updateWindow.close();
}
};
const createUpdateWindow = () => {
updateWindow = new BrowserWindow({
width: 400,
height: 100,
frame: false,
backgroundColor: 'rgb(51,163,243)',
webPreferences: {
nodeIntegration: true,
webSecurity: false
}
});
updateWindow.loadURL(srcPath + '/html/update.html');
// updateWindow.webContents.openDevTools();
updateWindow.on('close', (event) => {
});
updateWindow.on('closed', () => {
console.log(" ======== updateWindow closed =========")
updateWindow = null;
});
};
const toggleConfigWindow = () => {
if (configWindow) {
configWindow.close();
} else {
createConfigWindow();
}
}
const createConfigWindow = () => {
if (configWindow) {
configWindow.close();
}
configWindow = new BrowserWindow({
width: 600,
height: 300,
frame: false,
backgroundColor: '#555',
webPreferences: {
nodeIntegration: true,
webSecurity: false
}
});
configWindow.loadURL(srcPath + '/html/config.html');
configWindow.on('close', (event) => {
});
configWindow.on('closed', () => {
console.log(" ======== configWindow closed =========")
configWindow = null;
});
}
app.on('ready', () => {
Menu.setApplicationMenu(appMenu);
globalShortcut.register('ctrl+F12', () => {
if (mainWindow) mainWindow.webContents.openDevTools();
});
// globalShortcut.register('ctrl+F5', () => {
// if (mainWindow) {
// const clearObj = {
// storages: ['appcache', 'filesystem', 'localstorage', 'shadercache', 'cachestorage'],
// };
// mainWindow.webContents.session.clearStorageData(clearObj, () => {
// mainWindow.reload();
// })
// }
// });
createUpdateWindow();
// startSocket();
// createMainWindow();
// checkForUpdates()
});
// Quit when all windows are closed.
// app.on('window-all-closed', () => {
// // On OS X it is common for applications and their menu bar
// // to stay active until the user quits explicitly with Cmd + Q
// 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 (mainWindow === null) {
createMainWindow();
}
});
// 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.
var appIcon = {
path1: srcPath + '/img/1.png',
path2: srcPath + '/img/2.png',
tray: null,
timer: null,
isQuiting: false,
//
unread: {
title: '您有新消息啦',
last: 'mail',
count: {
mail: 0,
todo: 0,
msg: 0
}
},
notify: null,
//
leaveInter: null,
isLeave: true,
create: function () {
this.tray = new Tray(this.path1);
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主界面',
click: () => {
this.stop();
mainWindow.show();
}
}, {
type: 'separator'
}, {
label: '退出',
click: () => {
this.isQuiting = true;
app.quit();
}
}
]);
// 单击托盘小图标显示应用
this.tray.on('click', () => {
// 显示主程序
this.stop();
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
// 关闭托盘显示
// appTray.destroy();
});
this.tray.on('mouse-move', () => {
if (this.timer && (this.unread.count.mail || this.unread.count.todo || this.unread.count.msg)) {
if (this.isLeave) {
//触发mouse-enter
this.showNotify();
this.isLeave = false;
this.checkTrayLeave(() => {
this.closeNotify();
});
}
}
})
this.tray.setToolTip(appName);
this.tray.setContextMenu(contextMenu);
},
showNotify: function () {
this.closeNotify();
let body = '';
if (this.unread.count.mail) { body += ' 邮件(' + this.unread.count.mail + ')'; }
if (this.unread.count.todo) { body += ' 待办(' + this.unread.count.todo + ')'; }
if (this.unread.count.msg) { body += ' 消息(' + this.unread.count.msg + ')'; }
this.notify = showNotification(this.unread.title, body, () => {
appIcon.stop();
mainWindow.show();
mainWindow.webContents.send("openByType", this.unread.last);
this.unread.count.mail = 0;
this.unread.count.todo = 0;
this.unread.count.msg = 0;
})
},
closeNotify: function () {
if (this.notify) { this.notify.close() }
},
flash: function () {
if (!this.tray) {
this.create();
}
if (this.timer) {
clearInterval(this.timer);
}
var count = 0;
this.timer = setInterval(() => {
if (count++ % 2 == 0) {
this.tray.setImage(this.path1);
} else {
this.tray.setImage(this.path2);
}
}, 400);
mainWindow.flashFrame(true);
},
stop: function () {
clearInterval(this.timer)
this.timer = null;
this.tray.setImage(this.path1);
mainWindow.flashFrame(false);
},
checkTrayLeave: function (cb) {
const that = this;
if (this.leaveInter) {
clearInterval(this.leaveInter)
}
this.leaveInter = setInterval(function () {
let trayBounds = that.tray.getBounds();
point = screen.getCursorScreenPoint();
if (!(trayBounds.x < point.x && trayBounds.y < point.y && point.x < (trayBounds.x + trayBounds.width) && point.y < (trayBounds.y + trayBounds.height))) {
//触发mouse-leave
if (typeof cb == 'function') {
cb()
}
clearInterval(that.leaveInter);
that.isLeave = true;
}
}, 100)
}
}
function startSocket() {
if (!appConfig.currentUser || !appConfig.currentUser.token) {
console.log('appConfig.currentUser.token err')
console.log(appConfig.currentUser.token)
return;
}
console.log(" *** startSocket *** ")
// socketClient.close();
let socketHost = appConfig.apiServer.split('/')[2].split(':')[0]
let socketPort = appConfig.socketPort || 17999
socketClient.connect(socketPort, socketHost, function () {
console.log(" *** connect *** ")
let __data = JSON.stringify({ 'token': appConfig.currentUser.token });
//客户端向服务端socket发送 token 数据
console.log(__data)
socketClient.write(__data);
});
socketClient.setEncoding('utf8');
socketClient.on('data', (chunk) => {
console.log(" *** " + chunk + " *** ")
try {
var jsonChunk = JSON.parse(chunk);
appIcon.flash();
switch (jsonChunk.type) {
case 'email':
appIcon.unread.count.mail = appIcon.unread.count.mail + jsonChunk.count;
appIcon.unread.last = 'mail'
console.log("new mail", appIcon.unread.count.mail)
// jsonChunk.count
// jsonChunk.message
break;
case 'issue':
appIcon.unread.count.todo++;
appIcon.unread.last = 'todo'
console.log("new todo", appIcon.unread.count.todo)
break;
case 'message':
appIcon.unread.count.msg++;
appIcon.unread.last = 'msg'
console.log("new msg", appIcon.unread.count.msg)
break;
}
// 提示新消息来了
appIcon.showNotify();
} catch (e) {
console.log("catch chunk = ", chunk, e)
console.log(e)
}
})
socketClient.on('error', (e) => {
console.log("error", e.message);
})
}
function showNotification(title, body, handleClick) {
const notification = {
title: title || '提示',
body: body || '',
icon: srcPath + '/img/50x50.png',
silent: true,
}
let myNotification = new Notification(notification);
myNotification.show();
if (typeof handleClick == 'function') {
myNotification.on('click', () => {
handleClick()
})
}
return myNotification;
}
function ipcMainForPreload() {
// 给preload.js调用的方法
ipcMain.on('currentUser-message', (event, arg) => {
appConfig.currentUser = JSON.parse(arg)
updateAppConfig();
startSocket();
})
// 主进程监听渲染进程传来的信息
ipcMain.on('update-checkapp', (e, arg) => {
console.log("主进程监听渲染进程传来的信息 update");
checkForUpdates();
});
ipcMain.on('config-hide', (e, arg) => {
console.log("config-hide");
configWindow.close();
mainWindow.show();
});
ipcMain.on('config-update', (e, arg) => {
console.log("config-update");
let j = JSON.parse(arg)
appConfig.tenant = j.tenant
appConfig.apiServer = j.apiServer
updateAppConfig();
configWindow.close();
mainWindow.show();
mainWindow.loadURL(pcPath + '/index.html');
});
ipcMain.on('configWindow-show', (e, arg) => {
createConfigWindow()
});
ipcMain.on('set-tenantModeEnabled', (e, v) => {
// appTenantModeEnabled = v;
});
// 同步
ipcMain.on('sync-getconfig', (event, arg) => {
getAppConfig();
event.returnValue = JSON.stringify({ tenant: appConfig.tenant, apiServer: appConfig.apiServer })
});
ipcMain.on('sync-getTenant', function (event, arg) {
event.returnValue = getAppConfig().tenant;
});
ipcMain.on('sync-getApiServer', function (event, arg) {
event.returnValue = getAppConfig().apiServer;
});
ipcMain.on('sync-getAppTenantModeEnabled', function (event, arg) {
event.returnValue = appTenantModeEnabled;
});
}
let checkForUpdates = () => {
// 更新前,删除本地安装包 ↓ updaterCacheDirName的值与dev-app-update.yml中的updaterCacheDirName值一致
let updaterCacheDirName = 'xxxxxx-desk-updater'
const updatePendingPath = path.join(autoUpdater.app.baseCachePath, updaterCacheDirName, 'pending')
fs.emptyDir(updatePendingPath)
// 调试环境必须主动设置当前版本,electron-update有bug会去取electron的版本,而不是app的版本
if (process.env.NODE_ENV == 'development') {
autoUpdater.currentVersion = "1.5.0";
}
// 配置安装包远端服务器
autoUpdater.setFeedURL(appConfig.updateServer);
// 下面是自动更新的整个生命周期所发生的事件
autoUpdater.on('error', function (message) {
// 出错
console.log('*************', "error")
sendUpdateMessage('检查更新出错!', message);
createMainWindow();
});
autoUpdater.on('checking-for-update', function (message) {
// 正在检查
console.log('*************', "checking")
sendUpdateMessage('正在检查更新...', message);
});
autoUpdater.on('update-available', function (message) {
// 即将更新
console.log('*************', "available")
sendUpdateMessage('即将更新...', message);
});
autoUpdater.on('update-not-available', function (message) {
// 不用更新
console.log('*************', "It is lasted")
sendUpdateMessage('已经是最新版本了!', message);
createMainWindow();
});
// 更新下载进度事件
autoUpdater.on('download-progress', function (progressObj) {
console.log('*************', progressObj)
sendUpdateMessage('正在下载...', progressObj);
});
// 更新下载完成事件
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
sendUpdateMessage('下载完成,即将重新安装...', '');
console.log('*************', 'downloaded')
autoUpdater.quitAndInstall();
// ipcMain.on('updateNow', (e, arg) => {
// autoUpdater.quitAndInstall();
// });
});
//执行自动更新检查
autoUpdater.checkForUpdates();
};
function sendUpdateMessage(m, n) {
updateWindow.webContents.send("update-message", JSON.stringify({ message: m, data: n }))
}
function getAppConfig() {
appConfig = fs.readJsonSync(configFile)
// console.log("=== > getAppConfig ")
// console.log(appConfig) // => JPY
return appConfig;
}
function updateAppConfig() {
// console.log("=== > updateAppConfig ")
// console.log(appConfig) // => JP
fs.outputJsonSync(configFile, appConfig)
return appConfig;
}
const menuTemplate = [
{
label: '首页(F1)',
accelerator: 'F1',
click: function (item, focusedWindow) {
mainWindow.loadURL(pcPath + '/index.html');
}
}
,
{
label: '设置(F2)',
accelerator: 'F2',
click: function (item, focusedWindow) {
toggleConfigWindow();
}
}
,
{
label: '全屏(F3)',
accelerator: 'F3',
click: function (item, focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
{
label: '刷新(F5)',
accelerator: 'F5',
click: function (item, focusedWindow) {
if (focusedWindow)
focusedWindow.reload();
}
},
{
type: 'separator'
},
// {
// label: 'View App',
// submenu: [
// ]
// }
];
const appMenu = Menu.buildFromTemplate(menuTemplate);
```
```js
//preload.js
const ipcRenderer = require('electron').ipcRenderer;
// 点击消息,转向对应界面
ipcRenderer.on('openByType', function(event, __type) {
switch(__type){
case 'mail':
window.location.hash = '#/action/app/email/email/view'
break;
case 'todo':
window.location.hash = '#/action/app/matter/index'
break;
case 'msg':
window.location.hash = '#/action/app/my/message/view'
break;
}
});
// 给网页调用的方法
global.preloadIpcRenderer = {
getTenant: function () {
return ipcRenderer.sendSync('sync-getTenant');;
},
getApiServer: function () {
return ipcRenderer.sendSync('sync-getApiServer');;
},
showConfigWindow: function () {
ipcRenderer.send('configWindow-show');
},
sendCurrentUser: function (v) {
ipcRenderer.send('currentUser-message', v);
},
sendTenantModeEnabled: function (v) {
ipcRenderer.send('set-tenantModeEnabled', v);
}
}
```