將electron桌面應(yīng)用改造為Android平板和ipad平臺(tái)可運(yùn)行項(xiàng)目實(shí)踐


原electron桌面應(yīng)用技術(shù)棧背景:

  • electron:跨端桌面應(yīng)用支持,調(diào)用底層api等
  • vue:編寫(xiě)純web界面,在electron殼子上展示
  • better-sqlite3:緩存支持
  • electron-vue:腳手架

把electron桌面應(yīng)用改造為在安卓或者ios上面運(yùn)行(即純web),網(wǎng)上幾乎搜不到這種改造方案,為了做到最小改動(dòng)量,只改造出入口邏輯,也就是修改底層調(diào)用,遵循業(yè)務(wù)邏輯一概不動(dòng)的原則。

一、需要的改造點(diǎn):

主要分為三種:修改底層通信方式、修改底層存儲(chǔ)方式、其他細(xì)節(jié)

二、進(jìn)程通信方法改造:

electron的主進(jìn)程和渲染進(jìn)程通信是基于 node事件系統(tǒng)events 進(jìn)行改造的。因此我們可以通過(guò)使用events來(lái)模擬通信。

改造方案:

首先先使用events模擬electron的主、渲進(jìn)程通信。生成ipcRender、ipcMain兩個(gè)實(shí)例。

// myEvents.js文件
import EventEmitter from 'events'

class ipcMainEmitter extends EventEmitter {}
class ipcRenderEmitter extends EventEmitter {
  constructor() {
    super()
  }

  emit (event, payload) {
    super.emit(event, event, payload)  //這個(gè)方法是我為了兼容以前傳參需要的
  }
}

const ipcRenderMy = new ipcRenderEmitter()
const ipcMainMy = new ipcMainEmitter()

export {ipcRenderMy, ipcMainMy}

1、electron的異步通信 ---雙向

原先使用雙向通信的圖例,我整理了一張:

2022.10.12_e9d46b6ebb66d87de2f36012118dc0cc.png

原先代碼結(jié)構(gòu)如下:

//在主進(jìn)程中.
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.reply('asynchronous-reply', 'pong')
})

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

//在渲染器進(jìn)程 (網(wǎng)頁(yè)) 中。
const { ipcRenderer } = require('electron')

ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')

改造后的方案如下:

我整理了一張改造的雙向通信的示例圖:

2022.10.12_&f33e21173898cad0ece90f88310457d5.png

如上圖所示,這樣就能保證到在異步通信上我們不用改到原先的代碼,只需要修改引入的文件即可。

// const { ipcRenderer } = require('electron')
// const { ipcMain } = require('electron')

// 改為如下
import {ipcMain, ipcRender} from './myEvents'

2、electron的同步通信

//在主進(jìn)程中
ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.returnValue = 'pong'
})

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

//在渲染器進(jìn)程 (網(wǎng)頁(yè)) 中
const { ipcRenderer } = require('electron')
cosnt data =  ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(data ) // prints "pong"

可以看到同步的話是用event.returnValue返回?cái)?shù)據(jù)(我這里項(xiàng)目是用來(lái)等待接口請(qǐng)求返回?cái)?shù)據(jù)),所以我們可以用es6的async await來(lái)模擬這個(gè)功能(可能這個(gè)需要在原來(lái)的方法前面加上async并且await數(shù)據(jù)返回,暫時(shí)沒(méi)有其他更好的做法)

改造為:

    // 例子,直接在sendSync時(shí)候發(fā)請(qǐng)求返回?cái)?shù)據(jù)
async sendSync(methodName, payload = {}) {
  if (methodName) {
    return await fetchData(payload)
  }
}

三、sqlite3改造為瀏覽器的 idnexedDB

(這需要根據(jù)實(shí)際業(yè)務(wù)情況來(lái),即你業(yè)務(wù)層調(diào)用的方式)

以下是數(shù)據(jù)庫(kù)增刪改查的對(duì)應(yīng)4種辦法,值得注意的是,因?yàn)閕ndexedDB是異步操作所以需要使用promise,來(lái)等待異步完成,以及將對(duì)應(yīng)的sql語(yǔ)句替換為indexdb的寫(xiě)法。

let IDBOpenRequest = null   //打開(kāi)數(shù)據(jù)庫(kù)連接對(duì)象
let IDBDatabase = null   //連接的數(shù)據(jù)庫(kù),后面對(duì)數(shù)據(jù)庫(kù)操作都通過(guò)這個(gè)對(duì)象完成
const indexPrimaryKeys = 'KEY_TYPE'

/**
 * 在創(chuàng)建好的數(shù)據(jù)庫(kù)上新建倉(cāng)庫(kù)(即新建表)
 */
const createTable = ({}) => {
  if (!IDBDatabase.objectStoreNames.contains('cache_records')) {
    let objectStore = IDBDatabase.createObjectStore('cache_records', { autoIncrement: true })
    //新建索引
    // objectStore.createIndex(indexName, keyPath, objectParameters)  語(yǔ)法
    objectStore.createIndex('key', 'key', { unique: false })
    objectStore.createIndex('type', 'type', { unique: false })
    objectStore.createIndex('last_modify', 'last_modify', { unique: false })
    objectStore.createIndex(indexPrimaryKeys, ['key', 'type'], { unique: false })
  }
  if (!IDBDatabase.objectStoreNames.contains('common_configs')) {
    const objectStore = IDBDatabase.createObjectStore('common_configs', { autoIncrement: true })
    objectStore.createIndex('setting_value', 'setting_value', { unique: false })
    objectStore.createIndex('setting_key', 'setting_key', { unique: true })
  }
}

/**
 * 打開(kāi)數(shù)據(jù)庫(kù)連接,并創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)對(duì)象
 * @param databaseName  數(shù)據(jù)庫(kù)名稱
 * @param version 版本號(hào)
 */
function initIDB({ databaseName = 'myDatabase', version = 1 }) {
  console.log('|||||||||||---數(shù)據(jù)庫(kù)初始化程序開(kāi)始---||||||||||||')
  if (!indexedDB) {
    alert('indexdb does not support in this browser!')
    return
  }
  IDBOpenRequest = indexedDB.open(databaseName, version)
  //數(shù)據(jù)庫(kù)的success觸發(fā)事件
  IDBOpenRequest.onsuccess = function (event) {
    IDBDatabase = IDBOpenRequest.result //拿到數(shù)據(jù)庫(kù)實(shí)例
    console.log('數(shù)據(jù)庫(kù)打開(kāi)成功')
  }
  //錯(cuò)誤時(shí)候事件
  IDBOpenRequest.onerror = function (event) {
    console.log('數(shù)據(jù)庫(kù)打開(kāi)出錯(cuò):', event)
  }
  //指定版本號(hào)大于實(shí)際版本號(hào)時(shí)候觸發(fā),它會(huì)優(yōu)先執(zhí)行
  IDBOpenRequest.onupgradeneeded = function (event) {
    IDBDatabase = event.target.result //若升級(jí),此時(shí)通過(guò)這里拿到數(shù)據(jù)庫(kù)實(shí)例
    createTable({})
  }
}


initIDB({})


class Sqlite {

  /**
   * 處理不同字段情況下查詢數(shù)據(jù)返回IDBRequest
   * @param objectStore
   * @param search
   * @returns {IDBRequest<IDBCursorWithValue | null>}
   */
  static returnIDBCursorForGetPrimaryKey({ objectStore, search }) {
    try {
      const getIndexKeyBySearchKey = Object.keys(search)
      const getIndexValuesBySearchValues = Object.values(search) || ['']
      const singleParams = getIndexKeyBySearchKey.length === 1
      const index = objectStore.index(singleParams ? getIndexKeyBySearchKey[ 0 ] : indexPrimaryKeys)
      return index.openCursor(IDBKeyRange.only(singleParams ? getIndexValuesBySearchValues[ 0 ] : [search.key, search.type]))
    } catch (e) {
      throw `openCursor查找方式出錯(cuò)?。?!${e}`
    }
  }


  /**
   * 新增
   * @param {String} tableName 表名
   * @param {Object} value 插入的值 例如: {字段名: 值}
   */
  insert({ tableName, value = {} }) {
    const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
    let valueEdit = value.key ? value : { ...value, key: null }
    // objectStore.add(value, key)  語(yǔ)法
    const objectStoreRequest = objectStore.add(valueEdit)
    objectStoreRequest.onsuccess = function (event) {
      console.log('=====insert=====:數(shù)據(jù)寫(xiě)入成功', { tableName, value })
    }
    objectStoreRequest.onerror = function (event) {
      console.warn('數(shù)據(jù)寫(xiě)入失敗!!!', event.target.error.message)
    }
  }

  /**
   * 查詢表=>查詢索引
   * @param {String} tableName 表名
   * @param {Object} search 查詢條件 例如: {字段名1: 值, 字段名2: 值}
   */
  queryOne({ tableName, search }) {
    if (!search) return
    try {
      const objectStore = IDBDatabase.transaction([tableName]).objectStore(tableName)
      return new Promise(((resolve, reject) => {
        const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
        request.onsuccess = function (event) {
          console.log('=====queryOne=====:成功', { tableName, search }, request.result && request.result.value)
          resolve(request.result && request.result.value)
        }
        request.onerror = function (event) {
          console.warn('查詢事務(wù)失敗!', event.target)
          reject({})
        }
      }))
    } catch (e) {
      throw `查詢出錯(cuò)了:${e}`
    }
  }

  /**
   * 查詢多條
   * @param {String} tableName 表名
   * @param {Array} field 查詢的字段 例如: [字段名1, 字段名2]
   * @param {Object} search 查詢條件 例如: {字段名1: 值, 字段名2: 值}
   */
  query({ tableName, field, search }) {
    const data = []
    const list = []
    const objectStore = IDBDatabase.transaction([tableName]).objectStore(tableName)
    if (field && field.length > 0) {  //向上兼容,保持調(diào)用方法不變
      const index = objectStore.index('type')
      return new Promise((resolve) => {
        index.openCursor().onsuccess = function (event) {
          let cursor = event.target.result;
          if (cursor) {
            list.push(cursor.value)
            cursor.continue();
          } else {
            list.filter(item => item.type === search.type).forEach(item => {
              if (item && item.key) data.push({ key: item.key, last_modify: item.last_modify })
            })
            console.log('======query=====:=查詢所有=', { tableName, field, search }, data)
            resolve(data)
          }
        }
      })
    } else {
      return new Promise((resolve, reject) => {
        objectStore.openCursor().onsuccess = function (event) {
          const cursor = event.target.result;
          if (cursor) {
            data.push(cursor.value)
            cursor.continue();
          } else {
            console.log('======query=====:=查詢所有=', { tableName, field, search }, data)
            resolve(data)
          }
        };
      })
    }
  }

  /**
   * 刪除
   * @param {String} tableName 表名
   * @param {Object} search 刪除條件 例如: {字段名: 值}
   */
  async delete({ tableName, search }) {
    const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
    const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
    request.onsuccess = function (event) {
      const objectStoreRequest = objectStore.delete(event.target.result && event.target.result.primaryKey)
      objectStoreRequest.onsuccess = function (event) {
        console.log('=====delete=====:刪除成功??', { tableName, search }, event.target.result)
      }
    }
  }

  /**
   * 修改
   * @param {String} tableName 表名
   * @param {Object} field 更新的字段 例如: {字段名: 值}
   * @param {Object} search 查詢條件 例如: {字段名: 值}
   */
  async update({ tableName, field, search }) {
    try {
      const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
      const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
      request.onsuccess = function (event) {
        // objectStore.put(item, key) 語(yǔ)法
        const objectStoreRequest = objectStore.put(field, event.target.result && event.target.result.primaryKey)
        objectStoreRequest.onsuccess = function (event) {
          console.log('=====update=====:成功', { tableName, search })
        }
      }
    } catch (e) {
      console.warn('update失敗!')
    }
  }


}

export default Sqlite

四、其余需要改造功能點(diǎn):

  • 下載功能:原本的文件下載,改為直接瀏覽器原生下載
  • 緩存存儲(chǔ)功能:sqlite數(shù)據(jù)庫(kù)緩存改為瀏覽器的indexedDB
  • 請(qǐng)求功能改造:原有在主進(jìn)程里面的請(qǐng)求改為web請(qǐng)求
  • 新開(kāi)窗口:electron新開(kāi)彈窗改為web的彈窗

以上算是完成了基本改造,已經(jīng)把界面從electron上面移植出來(lái),并且可以在瀏覽器上面跑的通了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容