我和electron有个误会(总结篇)

244 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天


背景介绍

公司接到需求,需要使用开发一个桌面程序用于客户自助机使用,因为没有人会搞桌面端开发,身为啥都搞的前端程序员,身披战袍出战


经过了前三篇和electron的误会,今天来做一个项目总结,总结一下在项目中大家可能会用到或者遇到的问题


获取文件系统路径

在桌面程序中,经常会和系统进行交互,选择系统文件这个功能也就会经常出现,有时我们需要记录下文件所在的系统路径,这个时候就需要利用electron来进行交互了(利用网页进行选择文件的话,出于安全原因,浏览器会替换掉文件的原始路径,所以无法使用网页input方式去获取文件)


在渲染进程(vue页面)使用ipcRenderer中的invoke方法和主进程进行通信,invoke会等待一个promise返回,类似调用接口

    window.electron.ipcRenderer.invoke('openFile')
    .then(res => {
        //do some thing
    })
    .catch(e => {
        //do some thing
    })

在主进程监听对应通知,主线程通过handle来监听invoke发布的通知,并用dialog对象来调起文件选择窗口

    const { ipcMain, dialog } = require('electron');
    ipcMain.handle('openFile', (event) => {
      const files = dialog.showOpenDialogSync({
        properties: ['openFile ']
      })
      return {path: files[0]}
    })

最后vue页面就可以接收到electron选择的文件对象,其中就有真实的系统路径了


监听electron实例,防止重复启动

有时候因为业务的特殊原因,electron的窗口关闭会变成最小化到系统托盘,不是真正的退出程序,用户没发现的话,再次点击exe打开会重复启动实例,如果一个实例对应有一个express服务的话,会造成端口冲突,所以我们要防止这种情况的发生就需要监听electron的实例对象


监听一个对象,就是监听对象的关键字,我们可以利用electron提供的方法,给自己的实例应用添加标签,然后我们通过监听这个标签是否已启动来重新聚焦,防止二次启动

    const {} = require('electron')
    // 创建一个标签对象
    const additionalData = {
      myKey: 'xxxJueJin'
    }
    // 利用app对象的requestSingleInstanceLock方法,把标签对象赋予electron
    const gotTheLock = app.requestSingleInstanceLock(additionalData)
    // 监听该实例
    if (!gotTheLock) {
      // 如果无法赋予,证明有问题,直接删除实例,退出程序
      app.quit()
    } else {
      // 赋予成功,监听electron第二次启动的生命周期
      app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
        if (win) {
        // 如果已经存在实例,就在实例上继续操作,不要创建新的实例
          if (win.isMinimized()) {
            win.restore()
          }
          win.show()
          win.focus()
        }
      })
      app.whenReady().then(() => {
        // app运行成功,即electron实例创建
        // 新建窗口↓
        createWindow()
        app.on('activate', function () {
          if (BrowserWindow.getAllWindows().length === 0) createWindow()
        })
      });
    }

我们通过标签来判断同一个实例是否存在,就可以避免二次启动的问题了


实例启动中的过度动画

这份过渡代码直接放在preload文件里,在实例创建时进行过渡用的(代码从别人源码上扣下来的,觉得挺好看就就用着先)

function domReady(condition = ['complete', 'interactive']) {
  return new Promise(resolve => {
    if (condition.includes(document.readyState)) {
      resolve(true)
    } else {
      document.addEventListener('readystatechange', () => {
        if (condition.includes(document.readyState)) {
          resolve(true)
        }
      })
    }
  })
}

const safeDOM = {
  append(parent, child) {
    if (!Array.from(parent.children).find(e => e === child)) {
      return parent.appendChild(child)
    }
  },
  remove(parent, child) {
    if (Array.from(parent.children).find(e => e === child)) {
      return parent.removeChild(child)
    }
  },
}

function useLoading() {
  const className = `loaders-css__square-spin`
  const styleContent = `
@keyframes square-spin {
  25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
  50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
  75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
  100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {
  animation-fill-mode: both;
  width: 50px;
  height: 50px;
  background: #fff;
  animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #282c34;
  z-index: 9;
}
    `
  const oStyle = document.createElement('style')
  const oDiv = document.createElement('div')

  oStyle.id = 'app-loading-style'
  oStyle.innerHTML = styleContent
  oDiv.className = 'app-loading-wrap'
  oDiv.innerHTML = `<div class="${className}"><div></div></div>`

  return {
    appendLoading() {
      safeDOM.append(document.head, oStyle)
      safeDOM.append(document.body, oDiv)
    },
    removeLoading() {
      safeDOM.remove(document.head, oStyle)
      safeDOM.remove(document.body, oDiv)
    },
  }
}

// ----------------------------------------------------------------------

const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)

window.onmessage = ev => {
  ev.data.payload === 'removeLoading' && removeLoading()
}

setTimeout(removeLoading, 4999)

总结

第一次写electron的项目,感觉很有趣,解决坑的过程很难受,解决了就很有快感,可能这就是程序员的通病吧,也再次感叹当时听到的一句话👇

能用javascrip写的东西,最终都会被它淘汰

关于作者

一个工作三年,摆烂躺平的前端攻城狮~~~🦁

往期链接

我和electron有个误会(三)

我和electron有个误会(二)

我和electron有个误会