最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Electron實現(xiàn)多標(biāo)簽頁模式詳解

 更新時間:2024年11月20日 10:24:19   作者:若邪  
Electron 都發(fā)展這么多年了,讓人想不到的是,要實現(xiàn)一個多標(biāo)簽頁的功能居然沒有能用的輪子,本文就來用比較low的方案 - iframe手搓一個吧

上文介紹了 如何在 Electron 中優(yōu)雅的進(jìn)行進(jìn)程間通訊,接下來說說如何在 Electron 實現(xiàn)多標(biāo)簽頁模式,如下圖。

Electron 都發(fā)展這么多年了,讓人想不到的是,要實現(xiàn)一個多標(biāo)簽頁的功能居然沒有能用的輪子。能在 Github 上找到 Star 最多的一個輪子(Tab component for Electron)也已經(jīng)不再更新,而且還是使用 Electron 建議不再使用的 WebView 實現(xiàn)的(Web 嵌入 | Electron)。后面也有人基于 BrowserView 實現(xiàn)了一套,但是現(xiàn)在 Electron 又不推薦使用 BrowserView 了,建議使用 WebContentsView。因為項目比較急,沒有花太多時間去研究了,就用比較 low 的方案 - iframe 自己搓了一個。

直接看 HTML 的結(jié)構(gòu)吧,如下

也就是一個 tab 對應(yīng)一個 iframe。

界面沒啥好說的,稍微有點復(fù)雜的就是主進(jìn)程、渲染進(jìn)程(iframe 所在的頁面)、iframe 之間的通訊。

在實際的業(yè)務(wù)場景中,關(guān)閉窗口的時候需要彈框讓用戶確認(rèn)、用戶確認(rèn)后 iframe 里的頁面需要調(diào)接口進(jìn)行登出,然后通知主進(jìn)程關(guān)閉窗口。整個消息鏈路涉及了主進(jìn)程、渲染進(jìn)程、iframe 頁面,而且還是雙向的。

上文已經(jīng)講了如何封裝主進(jìn)程、渲染進(jìn)程之間的通訊,下面講講渲染進(jìn)程(iframe 所在的頁面)、iframe 之間的通訊。

渲染進(jìn)程監(jiān)聽消息、處理消息:

export const addIframeWebEventListener = () => {
  window.addEventListener("message", async (event) => {
    const message = event.data as {
      iframeWebCmd: string;
      cbid: string;
      code: number;
      data: never;
    };
    if (message.iframeWebCmd) {
      console.log(message);
      if (message.iframeWebCmd !== "postMessageCallback") {
        if (handle[message.iframeWebCmd]) {
          try {
            const res = await handle[message.iframeWebCmd](message.data);
            invokeCallback(message.cbid, res);
          } catch (ex: unknown) {
            invokeErrorCallback(message.cbid, ex);
          }
        } else {
          invokeErrorCallback(
            message.cbid,
            `方法不存在:${message.iframeWebCmd}`,
          );
        }
      } else {
        if (message.code === 200) {
          (callbacks[message.cbid] || function () {})(message.data);
        } else {
          (errorCallbacks[message.cbid] || function () {})(message.data);
        }
        delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
        delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
      }
    }
  });
};

渲染進(jìn)程主動發(fā)送消息:

function postMessage(
  data: { electronWebCmd: string; data?: any },
  cb?: (data: any) => void,
  errorCb?: (data: any) => void,
) {
  const iframe = document.getElementById(
    tabStore.currentTabId.value!,
  ) as HTMLIFrameElement;
  if (cb) {
    const cbid = Date.now();
    callbacks[cbid] = cb;
    iframe?.contentWindow?.postMessage(
      {
        ...data,
        cbid,
      },
      "*",
    );
    if (errorCb) {
      errorCallbacks[cbid] = errorCb;
    }
  } else {
    iframe?.contentWindow?.postMessage(data, "*");
  }
}

export function request<T = unknown>(params: { cmd: string; data?: any }) {
  return new Promise<T>((resolve, reject) => {
    postMessage(
      { electronWebCmd: params.cmd, data: params.data },
      (res) => {
        resolve(res);
      },
      (error) => {
        reject(error);
      },
    );
  });
}

每一個 iframe 都使用了 id 進(jìn)行標(biāo)識,發(fā)送消息就是給當(dāng)前激活的 tab 對應(yīng)的 iframe 發(fā)消息。

當(dāng)需要渲染進(jìn)程給 iframe 發(fā)消息的時候,就可以像調(diào)用 HTTP 請求一樣發(fā)送消息,比如讓 iframe 頁面進(jìn)行刷新:

export function refresh() {
  return request({
    cmd: "refresh",
  });
}

完整代碼:

/* eslint-disable no-case-declarations */
/* eslint-disable no-shadow */

import { useTabsStore } from "@/store/tabs";
import handle from "./handle";

/* eslint-disable @typescript-eslint/no-explicit-any */
const callbacks: { [propName: string]: (data: any) => void } = {};
const errorCallbacks: { [propName: string]: (data: any) => void } = {};

const tabStore = useTabsStore();

function postMessage(
  data: { electronWebCmd: string; data?: any },
  cb?: (data: any) => void,
  errorCb?: (data: any) => void,
) {
  const iframe = document.getElementById(
    tabStore.currentTabId.value!,
  ) as HTMLIFrameElement;
  if (cb) {
    const cbid = Date.now();
    callbacks[cbid] = cb;
    iframe?.contentWindow?.postMessage(
      {
        ...data,
        cbid,
      },
      "*",
    );
    if (errorCb) {
      errorCallbacks[cbid] = errorCb;
    }
  } else {
    iframe?.contentWindow?.postMessage(data, "*");
  }
}

export function request<T = unknown>(params: { cmd: string; data?: any }) {
  return new Promise<T>((resolve, reject) => {
    postMessage(
      { electronWebCmd: params.cmd, data: params.data },
      (res) => {
        resolve(res);
      },
      (error) => {
        reject(error);
      },
    );
  });
}

function invokeCallback<T = unknown>(cbid: string, res: T) {
  (
    document.getElementById(tabStore.currentTabId.value!) as HTMLIFrameElement
  )?.contentWindow?.postMessage(
    {
      electronWebCmd: "postMessageCallback",
      cbid,
      data: res,
      code: 200,
    },
    "*",
  );
}

function invokeErrorCallback(cbid: string, res: unknown) {
  (
    document.getElementById(tabStore.currentTabId.value!) as HTMLIFrameElement
  )?.contentWindow?.postMessage(
    {
      electronWebCmd: "postMessageCallback",
      cbid,
      data: res,
      code: 400,
    },
    "*",
  );
}

export const addIframeWebEventListener = () => {
  window.addEventListener("message", async (event) => {
    const message = event.data as {
      iframeWebCmd: string;
      cbid: string;
      code: number;
      data: never;
    };
    if (message.iframeWebCmd) {
      console.log(message);
      if (message.iframeWebCmd !== "postMessageCallback") {
        if (handle[message.iframeWebCmd]) {
          try {
            const res = await handle[message.iframeWebCmd](message.data);
            invokeCallback(message.cbid, res);
          } catch (ex: unknown) {
            invokeErrorCallback(message.cbid, ex);
          }
        } else {
          invokeErrorCallback(
            message.cbid,
            `方法不存在:${message.iframeWebCmd}`,
          );
        }
      } else {
        if (message.code === 200) {
          (callbacks[message.cbid] || function () {})(message.data);
        } else {
          (errorCallbacks[message.cbid] || function () {})(message.data);
        }
        delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
        delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
      }
    }
  });
};

iframe 頁面監(jiān)聽消息、處理消息:

export const addElectronWebWebEventListener = () => {
  window.addEventListener("message", async (event) => {
    const message = event.data as {
      electronWebCmd: string;
      cbid: string;
      code: number;
      data: never;
    };
    if (message.electronWebCmd) {
      if (message.electronWebCmd !== "postMessageCallback") {
        if (handle[message.electronWebCmd]) {
          try {
            const res = await handle[message.electronWebCmd](message.data);
            invokeCallback(message.cbid, res);
          } catch (ex: unknown) {
            invokeErrorCallback(message.cbid, ex);
          }
        } else {
          invokeErrorCallback(
            message.cbid,
            `方法不存在:${message.electronWebCmd}`,
          );
        }
      } else {
        if (message.code === 200) {
          (callbacks[message.cbid] || function () {})(message.data);
        } else {
          (errorCallbacks[message.cbid] || function () {})(message.data);
        }
        delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
        delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
      }
    }
  });
};

iframe 發(fā)送消息:

function postMessage(
  data: { iframeWebCmd: string; data?: unknown },
  cb?: (data: unknown) => void,
  errorCb?: (data: unknown) => void,
) {
  if (cb) {
    const cbid = Date.now();
    callbacks[cbid] = cb;
    window.parent?.postMessage(
      {
        ...data,
        cbid,
      },
      "*",
    );
    if (errorCb) {
      errorCallbacks[cbid] = errorCb;
    }
  } else {
    window.parent?.postMessage(data, "*");
  }
}

export function request<T = unknown>(params: { cmd: string; data?: unknown }) {
  return new Promise<T>((resolve, reject) => {
    postMessage(
      { iframeWebCmd: params.cmd, data: params.data },
      (res) => {
        resolve(res as T);
      },
      (error) => {
        reject(error);
      },
    );
  });
}

如此一來 iframe 頁面發(fā)消息的時候也很簡單:

/**
 * @description 獲取 mac 地址
 * @returns
 */
export const getMac = () => {
  return request<string>({
    cmd: "getMac",
  });
};

獲取 mac 地址,消息的傳遞過程是:iframe 頁面 -> 渲染進(jìn)程 -> 主進(jìn)程,主進(jìn)程 -> 渲染進(jìn)程 -> iframe 頁面,屬于雙向通訊。如果沒有做好通訊的封裝,處理起來想想都麻煩,而現(xiàn)在只需要關(guān)注業(yè)務(wù)代碼就好了。

完整代碼:

import handle from "./handle";

/* eslint-disable no-shadow */
const callbacks: { [propName: string]: (data: unknown) => void } = {};
const errorCallbacks: { [propName: string]: (data: unknown) => void } = {};
function postMessage(
  data: { iframeWebCmd: string; data?: unknown },
  cb?: (data: unknown) => void,
  errorCb?: (data: unknown) => void,
) {
  if (cb) {
    const cbid = Date.now();
    callbacks[cbid] = cb;
    window.parent?.postMessage(
      {
        ...data,
        cbid,
      },
      "*",
    );
    if (errorCb) {
      errorCallbacks[cbid] = errorCb;
    }
  } else {
    window.parent?.postMessage(data, "*");
  }
}

export function request<T = unknown>(params: { cmd: string; data?: unknown }) {
  return new Promise<T>((resolve, reject) => {
    postMessage(
      { iframeWebCmd: params.cmd, data: params.data },
      (res) => {
        resolve(res as T);
      },
      (error) => {
        reject(error);
      },
    );
  });
}

function invokeCallback<T = unknown>(cbid: string, res: T) {
  window.parent?.postMessage(
    {
      iframeWebCmd: "postMessageCallback",
      cbid,
      data: res,
      code: 200,
    },
    "*",
  );
}

function invokeErrorCallback(cbid: string, res: unknown) {
  window.parent?.postMessage(
    {
      iframeWebCmd: "postMessageCallback",
      cbid,
      data: res,
      code: 400,
    },
    "*",
  );
}
export const addElectronWebWebEventListener = () => {
  window.addEventListener("message", async (event) => {
    const message = event.data as {
      electronWebCmd: string;
      cbid: string;
      code: number;
      data: never;
    };
    if (message.electronWebCmd) {
      if (message.electronWebCmd !== "postMessageCallback") {
        if (handle[message.electronWebCmd]) {
          try {
            const res = await handle[message.electronWebCmd](message.data);
            invokeCallback(message.cbid, res);
          } catch (ex: unknown) {
            invokeErrorCallback(message.cbid, ex);
          }
        } else {
          invokeErrorCallback(
            message.cbid,
            `方法不存在:${message.electronWebCmd}`,
          );
        }
      } else {
        if (message.code === 200) {
          (callbacks[message.cbid] || function () {})(message.data);
        } else {
          (errorCallbacks[message.cbid] || function () {})(message.data);
        }
        delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
        delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除
      }
    }
  });
};

在 Electron 里基于 iframe 的方案實現(xiàn)多標(biāo)簽頁,有一個致命的缺陷就是,如果 iframe 里的頁面屬于第三方,那么就無法與里面的頁面進(jìn)行同通訊,比如我在實現(xiàn)刷新標(biāo)簽頁的時候,是給 iframe 里的頁面發(fā)送消息,頁面收到消息后執(zhí)行下面的代碼:

refresh: () => {
    const iframeID = getIframeId();
    if (iframeID) {
      let href = location.href;
      if (href.indexOf("?") === -1) {
        href = href + `?iframeId=${iframeID}`;
      } else {
        if (href.indexOf("iframeId") === -1) {
          href = href + `&iframeId=${iframeID}`;
        }
      }
      location.href = href;
      setTimeout(() => {
        location.reload();
      }, 500);
    } else {
      location.reload();
    }
  }

到此這篇關(guān)于Electron實現(xiàn)多標(biāo)簽頁模式詳解的文章就介紹到這了,更多相關(guān)Electron多標(biāo)簽頁模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • javascript驗證郵件地址和MX記錄的方法

    javascript驗證郵件地址和MX記錄的方法

    這篇文章主要介紹了javascript驗證郵件地址和MX記錄的方法,涉及javascript正則驗證的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • JavaScript中的this指向問題詳解

    JavaScript中的this指向問題詳解

    這篇文章主要給大家介紹了關(guān)于JavaScript中this指向問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • js中g(shù)etter和setter用法實例分析

    js中g(shù)etter和setter用法實例分析

    這篇文章主要介紹了js中g(shù)etter和setter用法,結(jié)合實例形式分析了javascript中g(shù)etter和setter的功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下
    2018-08-08
  • JavaScript中具名函數(shù)的多種調(diào)用方式總結(jié)

    JavaScript中具名函數(shù)的多種調(diào)用方式總結(jié)

    這篇文章主要介紹了JavaScript中具名函數(shù)的多種調(diào)用方式總結(jié),本文總結(jié)了4種方法,需要的朋友可以參考下
    2014-11-11
  • JavaScript實現(xiàn)H5接金幣功能(實例代碼)

    JavaScript實現(xiàn)H5接金幣功能(實例代碼)

    這篇文章主要介紹了JavaScript實現(xiàn)H5接金幣功能,本文分步驟通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • js實現(xiàn)HashTable(哈希表)的實例分析

    js實現(xiàn)HashTable(哈希表)的實例分析

    本文詳細(xì)介紹javascript哈希表的實例分析及用法。下面就跟小編一起來學(xué)習(xí)下吧
    2016-11-11
  • 淺談javascript屬性onresize

    淺談javascript屬性onresize

    這篇文章主要介紹了淺談javascript屬性onresize的詳細(xì)使用方法,十分的實用,這里推薦給大家,有需要的小伙伴可以參考下。
    2015-04-04
  • js實現(xiàn)點擊左右按鈕輪播圖片效果實例

    js實現(xiàn)點擊左右按鈕輪播圖片效果實例

    這篇文章主要介紹了js實現(xiàn)點擊左右按鈕輪播圖片效果的方法,涉及click事件相應(yīng)、animate方法等使用技巧,需要的朋友可以參考下
    2015-01-01
  • 用js+iframe形成頁面的一種遮罩效果的具體實現(xiàn)

    用js+iframe形成頁面的一種遮罩效果的具體實現(xiàn)

    用js形成頁面的一種遮罩效果,選擇想要進(jìn)行遮罩的窗口,在這里想要遮罩的是一個iframe窗口,具體的實現(xiàn)如下,感興趣的朋友可以參參考下
    2013-12-12
  • Javascript跨域請求的4種解決方式

    Javascript跨域請求的4種解決方式

    如果所請求的域名跟這個域名不致,這種情況就是跨域,由于跨域存在漏洞,所以一般來說正常的跨域請求方式是請求不到的,所以有了本文的出現(xiàn),感興趣的你可以參考下哈,希望可以幫助到你
    2013-03-03

最新評論

武夷山市| 皋兰县| 平凉市| 巴东县| 日土县| 隆安县| 遂川县| 广安市| 大姚县| 龙游县| 磐安县| 沙湾县| 若尔盖县| 碌曲县| 定州市| 循化| 松桃| 临漳县| 靖州| 诏安县| 岱山县| 绿春县| 平南县| 安溪县| 茌平县| 阿城市| 广南县| 左权县| 沙洋县| 兴国县| 西华县| 霍林郭勒市| 万全县| 鄄城县| 高雄市| 鄂托克前旗| 江安县| 壤塘县| 称多县| 东乌| 冀州市|