Electron 笔记

Electron

关尔先生2020-11-30 17:57:10
# 本代码仅适用于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);
  }
}

```

Electron

上一篇:js base64对称性加密:web Api 接口——window.btoa

下一篇:Vue连续点击多次路由报错解决方法

本文链接: http://www.nanshanqiao.com/zz_article/69.html

暂无评论