electron从零开发

因为项目需要用到客户端,了解了electron和nw.js,最后决定用electron。

先安装node.js,创建项目文件夹,就不一一阐述。

最后项目的结构如下

 里面我使用了electron-builder打包+electron-updater全局升级+压缩包局部更新。

因为需要局部更新,所以我关闭了asar模式。

并且我的项目是需要关联pdf文件,所以添加了后缀是.pdf的打开方式。并且监听了使用pdf打开客户端,客户端获取pdf的路径

packeage.json配置如下

{
  "name": "你的项目名称",
  "version": "1.0.0",
  "description": "Sample to demonstrate integrating WebViewer into an Electron App",
  "main": "src/index.js",
  "scripts": {
    "build": "electron-builder",
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    "postinstall": ""
  },
  "author": "lifan",
  "license": "ISC",
  "config": {
    "forge": {
      "packagerConfig": {},
      "makers": [
        {
          "name": "@electron-forge/maker-squirrel",
          "config": {
            "name": "pdftron_reader"
          }
        },
        {
          "name": "@electron-forge/maker-zip",
          "platforms": [
            "darwin"
          ]
        },
        {
          "name": "@electron-forge/maker-deb",
          "config": {}
        },
        {
          "name": "@electron-forge/maker-rpm",
          "config": {}
        }
      ]
    }
  },
  "build": {
    "productName": "项目中文名称",
    "appId": "",
    "asar": false,
    "directories": {
      "output": "build"
    },
    "fileAssociations": {
      "ext": [
        ".pdf"
      ],
      "name": "pdf",
      "role": "Editor"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowElevation": true,
      "allowToChangeInstallationDirectory": true,
      "installerIcon": "icon.ico",
      "uninstallerIcon": "icon.ico",
      "installerHeaderIcon": "/icon.ico",
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "runAfterFinish": true,
      "shortcutName": "nisi的项目名称"
    },
    "publish": [
      {
        "provider": "generic",
        "url": "你的项目更新路径"
      }
    ],
    "dmg": {
      "contents": [
        {
          "x": 410,
          "y": 150,
          "type": "link",
          "path": "/Applications"
        },
        {
          "x": 130,
          "y": 150,
          "type": "file"
        }
      ]
    },
    "mac": {
      "icon": "icon.png",
      "extendInfo": {
        "LSMultipleInstancesProhibited": true
      }
    },
    "win": {
      "icon": "icon.ico",
      "artifactName": "${productName}_setup_${version}.${ext}",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "ia32"
          ]
        }
      ]
    }
  },
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0-beta.58",
    "@electron-forge/maker-deb": "^6.0.0-beta.54",
    "@electron-forge/maker-rpm": "^6.0.0-beta.54",
    "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
    "@electron-forge/maker-zip": "^6.0.0-beta.54",
    "electron": "11.0.3",
    "electron-builder": "^22.11.7"
  },
  "dependencies": {
    "adm-zip": "^0.5.6",
    "axios": "^0.21.4",
    "crypto-js": "4.1.1",
    "electron-squirrel-startup": "^1.0.0",
    "electron-store": "^8.0.0",
    "electron-updater": "^4.3.9",
    "element-ui": "^2.15.6",
    "fs-extra": "^7.0.1",
    "request": "^2.88.2",
    "vue": "^2.6.14",
    "vue-router": "^3.5.2"
  }
}


下面是入口文件index.js,写一下重要的几点

1.版本号设置,判断是否需要更新

2.使用electron-store判断用户登录状态,从而进入不同页面

3.获取用户打开pdf调起客户端时的路径

const { app, BrowserWindow, globalShortcut, ipcMain, Menu, shell} = require("electron");
const path = require("path");
const { autoUpdater } = require("electron-updater"); 
const Store = require('electron-store');
Store.initRenderer()
const fs = require('fs-extra');
const axios = require('axios');
//自定义版本号,跟服务器请求版本号对比
global.version = 1.000;
//服务器版本号
global.newversion = 0;
var isWinReady = false;
var initOpenFileQueue = [];
let mainWindow
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require("electron-squirrel-startup")) {
  // eslint-disable-line global-require
  app.quit();
}
//这是设置菜单栏的,我没用到所以注释了
// const Menus = [
//     {
//         label:'Files',
//         submenu:[
//             {
//                 label: '网页版',
//                 role: 'help',
//                 submenu: [{
//                     label: '网页版',
//                     click: function () {
//                         shell.openExternal('https://www.jianshu.com/u/1699a0673cfe')
//                     }
//                 }]
//             },
//             {
//                 label: '帮助',
//                 role: 'help',
//                 submenu: [{
//                     label: '帮助文档',
//                     click: function () {
//                         shell.openExternal('https://www.jianshu.com/u/1699a0673cfe')
//                     }
//                 }]
//             }
//         ]
//     }
// ];

const createWindow = () => {
  // Create the browser window.
  //我设置了打开默认最大化
  mainWindow = new BrowserWindow({
    show: false,
    minWidth: 650,
    minHeight: 500,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true,
      contextIsolation: false
    }
  });
  axios.get('你的服务器版本号,例:1.0001').then(res => {
    global.newversion = parseFloat(res.data);
    //如果当前版本号大于上面配置的global.version,那么就更新
    if (global.newversion && global.newversion > version) {
      mainWindow.loadFile(path.join(__dirname, "start.html"));
      //检测版本更新
      updateHandle();
    }else{
      //这边是用了electron-store存储用户数据,判断进入页面
      const store = new Store();
      if(store.get('user_info') && store.get('scane_login') && store.get('user_register') && store.get('user_setting')){
        // and load the index.html of the app.
        mainWindow.loadFile(path.join(__dirname, "list.html"));
      }else{
        mainWindow.loadFile(path.join(__dirname, "login.html"));
      }   
    }
  }).catch(error => { console.log(error) }) 

  // Open the DevTools.
  // mainWindow.webContents.openDevTools();
  
  mainWindow.on('ready-to-show', () => {
    isWinReady = true;
  })

  mainWindow.webContents.on('dom-ready', () => {
    sendFileList(initOpenFileQueue)
  })
};

// 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.
const gotTheLock = app.requestSingleInstanceLock();
if (gotTheLock) {
  app.on("second-instance", (event, commandLine) => {
    // 监听是否有第二个实例,向渲染进程发送第二个实例的本地路径
    sendFileList(`${commandLine[commandLine.length - 1]}`);
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
    }
  });

  app.on("ready", async () => {
    // Register a 'CommandOrControl+Y' shortcut listener.
    // globalShortcut.register('CommandOrControl+R', () => {
    //   return false;
    // })
    // const mainMenu = Menu.buildFromTemplate(Menus);
    // Menu.setApplicationMenu(mainMenu);
    createWindow();
    mainWindow.maximize()
    mainWindow.show()
  });
} else {
  app.quit();
}

// 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", () => {
  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('will-finish-launching', () => {

  if (process.platform == 'win32') {
    const argv = process.argv
    if (argv) {
      argv.forEach(filePath => {
        if (filePath.indexOf('.pdf') >= 0) {
          initOpenFileQueue.push(filePath);
        }
      })
    }
  } else {
    // Event fired When someone drags files onto the icon while your app is running
    // for macOs https://www.electronjs.org/docs/api/app#%E4%BA%8B%E4%BB%B6-open-file-macos
    app.on("open-file", (event, file) => {
      if (!isWinReady) {
        initOpenFileQueue.push(file);
      } else {
        sendFileList(file)
      };
      event.preventDefault();
    });
  }
});

function sendFileList(fileList) {
  BrowserWindow.getAllWindows().forEach(win => {
    win.webContents.send("open-file-list", fileList)
  })
}

function updateHandle() {
    var name = app.getName();
    var feedUrl = 'https://xxx.xxx.com/download/';
    let message = {
        error: '检查更新出错',
        checking: '正在检查更新……',
        updateAva: '检测到新版本,正在下载……',
        updateNotAva: '现在使用的就是最新版本,不用更新',
    };
    //设置更新包的地址
    autoUpdater.setFeedURL(feedUrl);
    //监听升级失败事件
    autoUpdater.on('error', function (error) {
        sendUpdateMessage({
            cmd: 'error',
            message: error
        })
    });
    //监听开始检测更新事件
    autoUpdater.on('checking-for-update', function (message) {
        const updatePendingPath = require('path').join(autoUpdater.app.baseCachePath, name+'-updater')
        fs.emptyDir(updatePendingPath)
        sendUpdateMessage({
            cmd: 'checking-for-update',
            message: message
        })
    });
    //监听发现可用更新事件
    autoUpdater.on('update-available', function (message) {
        sendUpdateMessage({
            cmd: 'update-available',
            message: message
        })
    });
    //监听没有可用更新事件
    autoUpdater.on('update-not-available', function (message) {
        sendUpdateMessage({
            cmd: 'update-not-available',
            message: message
        })
    });
 
    // 更新下载进度事件
    autoUpdater.on('download-progress', function (progressObj) {
        sendUpdateMessage({
            cmd: 'download-progress',
            message: progressObj
        })
    });
    //监听下载完成事件
    autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
        //some code here to handle event
        autoUpdater.quitAndInstall();   
        sendUpdateMessage({
            cmd: 'update-downloaded',
            message: {
                releaseNotes,
                releaseName,
                releaseDate,
                updateUrl
            }
        })
    });
    //接收渲染进程消息,开始检查更新
    ipcMain.on("checkForUpdate", (e, arg) => {
        //执行自动更新检查
        // sendUpdateMessage({cmd:'checkForUpdate',message:arg})
        autoUpdater.checkForUpdates();
    })
    ipcMain.on('update-relaunch',function() {
        app.relaunch();
        app.quit();
    })  
}
ipcMain.on('new-window', (e, arg) => {
  if (arg) {
    var page = arg;
  } else {
    var page = 'list.html';
  }
  mainWindow.loadFile(path.join(__dirname, page));
})
//给渲染进程发送消息
function sendUpdateMessage(text) {
    mainWindow.webContents.send('message', text)
}
// 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.

贴一下src文件下的所有文件

先上更新页面代码,老规矩重要的几点说一下

1.我判断了大更新和小更新的区别,1.000-1.999是小更新,2是大更新,比如我现在版本是1.000我发布了新的版本1.001,那么就会执行小更新。但是如果我发布了2.000,那么就会执行批量更新。

2局部更新就是把src下的所有文件压缩成zip,上传到服务器或者oss,下载下来之后用adm-zip解压替换掉现有的src,就完成了局部更新,不需要更新几百M的安装包了

3.全局更新的地址在package.json和index.js里面都要设置,为什么我也不知道,我也是百度的。地址只要目录就行了,不需要对应包的路径。

4.贴一下oss的需要内容

5.src.zip的内容 ,不是压缩src,是压缩src下面的内容 

 

6.记得在根目录新建一个update文件夹,并且里面放入一个src.zip。不然因为权限问题,mac无法更新成功。

下面贴start.html的代码 

<html>
  <head>
    <link rel="stylesheet" href="index.css">
    <link rel="stylesheet" href="../node_modules/element-ui/lib/theme-chalk/index.css">
  </head>

  <body >
    <div id="app">
      <el-progress type="circle" :percentage="percentage"></el-progress>
    </div>
    
  </body>
  <script>
    const { ipcRenderer } = require('electron');
    const remote = require('electron').remote;
    const fs = require('fs-extra');
    const adm_zip = require('adm-zip');
    const path = require("path");
    var request = require('request');
    var newversion = remote.getGlobal('newversion');
    var version = remote.getGlobal('version');
    var Vue = require('vue/dist/vue.min.js');
    var ElementUI = require('element-ui');
    Vue.use(ElementUI);
    new Vue({
        el:"#app",
        data() {
          return {
            percentage: 0,
            fileUrl: "https://xx.xx.com/download/src.zip",
            baseUrl: path.join(__dirname, '../update', 'src.zip'),
            localUrl: path.join(__dirname, '../src/'),
          };
        },
        created () {  
          this.checkVersion()
        },
        methods: {
          goIndex(){
            ipcRenderer.send('new-window','login.html');
          },
          checkVersion(){
            var newversion_z = parseInt(newversion);
            var version_z = parseInt(version);
            //小更新
            if (version_z == newversion_z) {
              this.downLoad();
            }else{
              ipcRenderer.send('checkForUpdate');
              ipcRenderer.on('message', (event, arg) => {
                console.log(arg)
                if ("update-available" == arg.cmd) {
                  //显示升级对话框
                  console.log('需要升级');
                } else if ('update-not-available' == arg.cmd) {
                  this.goIndex();
                }else if ("download-progress" == arg.cmd) {
                  console.log(arg.message.percent);
                  this.percentage = Math.round(parseFloat(arg.message.percent));
                } else if ("error" == arg.cmd) {
                  console.log('升级失败');
                  setTimeout(()=>{
                    this.goIndex();
                  }, 2000 )
                } 
              })
            }
          },
          downLoad(){
            // Save variable to know progress
            var received_bytes = 0;
            var total_bytes = 0;
            var req = request({
                method: 'GET', uri: this.fileUrl
            });
            var out = fs.createWriteStream(this.baseUrl);
            req.pipe(out);

            req.on('response', function ( data ) {
                // Change the total bytes value to get progress later.
                total_bytes = parseInt(data.headers['content-length']);
            });
            var that = this;
            req.on('data', function(chunk) {
                // Update the received bytes
                received_bytes += chunk.length;

                that.showProgress(received_bytes, total_bytes);
            });

            req.on('end', function() {
              const unzip = new adm_zip(that.baseUrl);   //下载压缩更新包
              unzip.extractAllTo(that.localUrl, /*overwrite*/true);   //解压替换本地文件
              ipcRenderer.send('update-relaunch');
            })
          },
          showProgress(received, total){
            this.percentage = Math.round((received * 100) / total);
          }
        }
    })
  </script>
</html>

最后右键pdf选择打开方式,打开我的客户端之后的获取路径贴一下

//在你的页面js下面引入
const { ipcRenderer } = require('electron');
const Stores = require('electron-store');
const store = new Stores();
ipcRenderer.on('open-file-list', (event, args) => {
  console.log(args)
  if (typeof(args)=='string') {
    if (args.indexOf("pdf") != -1) {
      store.set('filelist', args);
    } 
  } else {
    if (args[0]) {
      store.set('filelist', args[0]);
    }
  }
  console.log(store.get('filelist'))
})

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值