您现在的位置是:首页 >其他 >electron+vue3全家桶+vite项目搭建【14】electron多窗口,多语言切换不同步更新问题网站首页其他

electron+vue3全家桶+vite项目搭建【14】electron多窗口,多语言切换不同步更新问题

编程小龙 2023-06-13 00:00:03
简介electron+vue3全家桶+vite项目搭建【14】electron多窗口,多语言切换不同步更新问题

引入

我们之前在这篇文章中集成了 多语言切换,但随着我们项目越来越复杂,单页面已经无法满足我们的需求,我们需要多个窗口去进行页面展示,此时会暴露很多问题,例如多窗口时,某个窗口切换了语言,其他窗口并不会同步切换

demo项目地址

问题演示

我们在srccomponentsdemoIndex.vue页面中也显示一个多语言文本:

<template>
	<h1>{{ $t(langMap.app_title) }}</h1>
</template>
<script setup lang="ts">
	import langMap from "@/locales/langMap";
</script>

问题如下:

请添加图片描述

补充逻辑

注意

这里主要是对之前多语言文章的补充,之前写的是个极简的整合,没有考虑很多,如只需要解决多窗口同步问题可直接跳过!!

封装缓存工具类

首先为了记住我们当前所选语言,避免每次重启重新选择,我们将语言持久化在本地,这里参考这篇博客,封装一个缓存工具类:

  • srcutilscacheUtils.ts
/* localstorage封装,缓存工具类  参考:https://blog.csdn.net/w544924116/article/details/120906411

/** key前缀 */
const keyPrefix = 'app_cahce$_';

/**
 * @param value 内容
 * @param addTime 存入时间
 * @param expires 有效时间
 */
interface valObjParams {
  value: any;
  addTime: number;
  expires: number;
}

interface StorageInterface {
  /**
   * 设置localStorage
   * @param value 内容
   * @param expires 有效时间 单位 s
   */
  set: (key: string, value: any, expires?: number) => void;
  /** 获取localStorage,会自动转json */
  get: (key: string) => any;
  /** 是否含有key */
  has: (key: string) => boolean;
  /** 移除 */
  remove: (key: string) => void;
  /** 移除全部缓存 */
  clear: () => void;
  /** 移除自己前缀的全部缓存 */
  clearSelf: () => void;
}

const storage: StorageInterface = {
  set: () => {},
  get: () => '',
  has: () => false,
  remove: () => {},
  clear: () => {},
  clearSelf: () => {}
};

/**
 * 是否过期
 */
const isFresh = (valObj: valObjParams) => {
  const now = new Date().getTime();
  return valObj.addTime + valObj.expires >= now;
};

/* 给key值添加前缀 */
const addPrefix = (key: string) => {
  return `${keyPrefix}${key}`;
};

/* 加方法 */
const extend = (s: Storage) => {
  return {
    set(key: string, value: any, expires?: number) {
      const skey = addPrefix(key);
      if (expires) {
        expires *= 1000; // 将过期时间从微秒转为秒
        s.setItem(
          skey,
          JSON.stringify({
            value,
            addTime: new Date().getTime(),
            expires
          })
        );
      } else {
        const val = JSON.stringify(value);
        s.setItem(skey, val);
      }
      if (value === undefined || value === null) {
        s.removeItem(skey);
      }
    },
    get(key: string) {
      const skey = addPrefix(key);
      const item = JSON.parse(s.getItem(skey) as any);
      // 如果有addTime的值,说明设置了失效时间
      if (item && item.addTime) {
        if (isFresh(item)) {
          return item.value;
        }
        /* 缓存过期,清除缓存,返回null */
        s.removeItem(skey);
        return null;
      }
      return item;
    },
    has(key: string) {
      const skey = addPrefix(key);
      return !!s.getItem(skey);
    },
    remove: (key: string) => {
      const skey = addPrefix(key);
      s.removeItem(skey);
    },
    clear: () => {
      s.clear();
    },
    clearSelf: () => {
      const arr = Array.from({ length: s.length }, (_, i) => s.key(i)).filter(
        str => str?.startsWith(keyPrefix)
      );
      arr.forEach(str => s.removeItem(str as string));
    }
  };
};

Object.assign(storage, extend(window.localStorage));

export default storage;

补充状态管理

我们可以创建一个appStore用来保存整个应用的全局状态:

import { defineStore } from "pinia";
import cacheUtils from "@/utils/cacheUtils";

/**应用相关状态管理 */
export const useAppStore = defineStore("appStore", {
  state() {
    return {
      lang: cacheUtils.get("lang") || "zhCn", // app的语言
    };
  },
});

调整多语言初始化

  • 补充从缓存中取初始值
  • srclocalesindex.ts
import { createI18n } from "vue-i18n";
import en from "./packages/en";
import zhCn from "./packages/zh-cn";
import cacheUtils from "@/utils/cacheUtils";

// 初始化i18n
const i18n = createI18n({
  legacy: false, // 解决Not available in legacy mode报错
  globalInjection: true, // 全局模式,可以直接使用 $t
  locale: cacheUtils.get("lang") || "zhCn", // 从本地缓存中取语言,如果没有 默认为中文
  fallbackLocale: "en", // set fallback locale
  messages: {
    en,
    zhCn,
  },
});

export default i18n;

调整多语言切换组件

  • 我们在语言切换的时候补充状态更新、缓存设置
import cacheUtils from "@/utils/cacheUtils";
import { useAppStore } from "@/store/modules/appStore";

const appStore = useAppStore();

// 切换语言
function handleCommand(lang: string) {
  i18n.locale.value = lang;
  // 设置缓存的值
  cacheUtils.set("lang", lang);
  // 更新全局状态
  appStore.lang = lang;
}

解决方案

思路整理

我们可以在多语言初始化的时候,让渲染进程监听多语言改变消息,然后主进程创建一个多语言改变handle,然后在语言切换组件中当语言切换时告知主进程语言切换了,并传参当前的语言,接着主进程的handle遍历所有窗口,除通知主进程的窗口外的其他窗口都触发 多语言改变通知,然后窗口自行更新即可。

渲染进程监听语言切换

1.我们在多语言初始化时监听多语言切换通知:

  • 调整srclocalesindex.ts代码:
import { useAppStore } from "@/store/modules/appStore";
import { ipcRenderer } from "electron";

// ......

// 注意,因为 pinia还没初始化就进行取值会有问题,所以这里我们单独暴露一个方法,在 src/main.ts中的 app.mount("#app").$nextTick 中调用
// 初始化语言监听
export function initLangListener() {
  const appStore = useAppStore();
  // 监听语言切换时,同步本窗口更新
  ipcRenderer.on("lang:change", (event, lang: string) => {
    i18n.global.locale.value = lang;
    appStore.lang = lang;
  });
}

2.在src/main.ts中执行初始化监听:

import { initLangListener } from "@/locales";
// .....

app.mount("#app").$nextTick(() => {
  postMessage({ payload: "removeLoading" }, "*");
  // 初始化多语言切换监听
  initLangListener();
});

主进程创建多语言切换处理

主进程中创建多语言处理监听,我们在electronmainindex.ts中补充代码:

/**语言修改同步 */
ipcMain.handle("lang:change", (event, lang) => {
  // 通知所有窗口同步更改语言
  for (const currentWin of BrowserWindow.getAllWindows()) {
    const webContentsId = currentWin.webContents.id;
    // 这里排除掉发送通知的窗口
    if (webContentsId !== event.sender.id) {
      currentWin.webContents.send("lang:change", lang);
    }
  }
});

语言切换组件通知主进程语言切换

当语言切换时,我们需要通知主进程告诉其他窗口同步修改,所以我们调整 多语言切换组件:

import { ipcRenderer } from 'electron';

// 多语言切换时
const handleCommand = (lang: string) => {
  // ...
  // 主进程通知其他窗口同步修改语言
  ipcRenderer.invoke('lang:change', lang);
};

请添加图片描述

最终实现效果演示

请添加图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。