阅读视图

发现新文章,点击刷新页面。

Vben Admin管理系统集成qiankun微服务(二)

继上篇

上篇Vben Admin管理系统集成qiankun微服务(一)遗留的三个问题:

  1. 子应用鉴权使用主应用鉴权,如果系统鉴权过期要跳转到登录页面。
  2. 主应用和子应用保持主题风格一致,主应用调整子应用同步调整。
  3. 支持多个应用动态加载。

下面分步完成以上相关内容

1. 主应用和子应用主题同步

主应用

主应用和子应用的数据传递主要使用props实现,上篇文章已经实现了部分没有详细解释,本篇补充以上内容。 通过props.userInfo和props.token 传递登录信息和授权信息,

vue-vben-admin/apps/web-antd/src/qiankun/config.ts

/**  本地应用测试微服务架构 */
export default {
  subApps: [
    {
      name: 'basic', // 子应用名称,跟package.json一致
      // entry: import.meta.env.VITE_API_BASE_URL, // 子应用入口,本地环境下指定端口
      entry: 'http://localhost:5667', // 子应用入口,本地前端环境下指定端口'http://localhost:5174',发布可以调整为主系统:/app/workflow-app/= /app/插件名称/
      container: '#sub-container', // 挂载子应用的dom
      activeRule: '/app/basic', // 路由匹配规则
      props: {
        userInfo: [],
        token: '',
      }, // 主应用与子应用通信传值
      sandbox: {
        strictStyleIsolation: true, // 启用严格样式隔离
      },
    },
  ],
};

vue-vben-admin/apps/web-antd/src/qiankun/index.ts文件,实现代码主要是在beforeLoad函数

// 参考项目:https://github.com/wstee/qiankun-web
import { useAccessStore, useUserStore } from '@vben/stores';

import { registerMicroApps } from 'qiankun';

import config from './config';

const { subApps } = config;

export async function registerApps() {
  try {
    // 如果子应用是不定的,可以这里定义接口从后台获取赋值给subApps,动态添加

    registerMicroApps(subApps, {
      beforeLoad: [
        (app: any) => {
          // eslint-disable-next-line no-console
          console.log('[主应用] beforeLoad', app.name);
          const useStore = useUserStore();
          const accessStore = useAccessStore();
          app.props.token = accessStore.accessToken;
          app.props.userInfo = useStore.userInfo;
         
        },
      ],
      // 生命周期钩子
      loader: (loading: any) => {
        // 可以在这里处理加载状态
        // eslint-disable-next-line no-console
        console.log('子应用加载状态:', loading);
      },
      beforeMount: [
        (app) => {
          // eslint-disable-next-line no-console
          console.log('[主应用] beforeMount', app.name);
          const container = document.querySelector(app.container);
          if (container) container.innerHTML = '';
        },
      ],
      afterUnmount: [
        (app) => {
          // eslint-disable-next-line no-console
          console.log('count: %s', app);
        },
      ],
    });
  } catch (error) {
    // eslint-disable-next-line no-console
    console.log('count: %s', error);
  }
}

子应用调整

修改代码读取主应用传递的参数,调整mount函数 caipu-vben-admin/apps/app-antd-child/web-demo/src/main.ts

   async mount(props: any) {
      const { container, token, userInfo } = props;
      await initApplication(container);
      const useStore = useUserStore();
      const accessStore = useAccessStore();
      console.log('[子应用]  mounting', props);
      console.log('[子应用]  token:', token);
      console.log('[子应用]  userInfo:', userInfo);

      useStore.setUserInfo(userInfo);
      accessStore.setAccessToken(token);
      // 监听主应用的主题事件
      window.addEventListener('qiankun-theme-update', handleThemeUpdate);
      // 移除并销毁loading
      unmountGlobalLoading();
    }

如果操作子应用时登录信息失效了呢,要让应用跳转到登录,可以修改setupAccessGuard函数,按照如下修改直接跳转到系统登录页。

caipu-vben-admin/apps/app-antd-child/src/router/guard.ts

 // 没有访问权限,跳转登录页面
      if (to.fullPath !== LOGIN_PATH) {
        // return {
        //   path: LOGIN_PATH,
        //   // 如不需要,直接删除 query
        //   query:
        //     to.fullPath === preferences.app.defaultHomePath
        //       ? {}
        //       : { redirect: encodeURIComponent(to.fullPath) },
        //   // 携带当前跳转的页面,登录后重新跳转该页面
        //   replace: true,
        // };
        window.location = 'http://localhost:5666/#/login';
      }

这样就实现主应用和子应用的信息同步了。

2. 主应用与子应用主题同步

vben主题相关配置是在'@vben/preferences'包中,要调整的动态配置主要是在preferences.theme当中,所以实现主题同步只要把配置信息同步到子应用即可。

未通过props传递原因是加载子应用之后再调整偏好设置和主题 子应用不生效,所以考虑只能通另外一种方式实现,最终选择 window.dispatchEvent事件监听的方式实现。

image.png

主应用调整

调整 vue-vben-admin/apps/web-antd/src/layouts/basic.vue

# 引用包
import { preferences } from '@vben/preferences';

# 合适位置增加主题监听
watch(
  () => ({
    theme: preferences.theme,
  }),
  async ({ theme }) => {
    alert('handler qiankun-theme  start', theme);
    // 子应用会监听这个事件并更新响应式对象
    window.dispatchEvent(
      new CustomEvent('qiankun-theme-update', {
        detail: preferences,
      }),
    );
  },
  {
    immediate: true,
  },
);

子应用调整

如果细心的话,在上述子应用调整的main.ts,mount函数要已有说明,主要是增加事件监听qiankun-theme-update 和监听处理事件handleThemeUpdate,完整代码如下 caipu-vben-admin/apps/app-antd-child/web-demo/src/main.ts

import { initPreferences, updatePreferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import { unmountGlobalLoading } from '@vben/utils';

import {
  qiankunWindow,
  renderWithQiankun,
} from 'vite-plugin-qiankun/dist/helper';

import { bootstrap } from './bootstrap';
import { overridesPreferences } from './preferences';

let app: any = null;
/**
 * 应用初始化完成之后再进行页面加载渲染
 */
async function initApplication(container: any = null) {
  // name用于指定项目唯一标识
  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
  const env = import.meta.env.PROD ? 'prod' : 'dev';
  const appVersion = import.meta.env.VITE_APP_VERSION;
  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;

  // app偏好设置初始化
  await initPreferences({
    namespace,
    overrides: overridesPreferences,
  });
  // 启动应用并挂载
  // vue应用主要逻辑及视图
  app = await bootstrap(namespace, container);
  // 移除并销毁loading
  unmountGlobalLoading();
}

const initQianKun = async () => {
  renderWithQiankun({
    async mount(props: any) {
      const { container, token, userInfo } = props;
      await initApplication(container);
      const useStore = useUserStore();
      const accessStore = useAccessStore();
      console.log('[子应用]  mounting', props);
      console.log('[子应用]  token:', token);
      console.log('[子应用]  userInfo:', userInfo);

      useStore.setUserInfo(userInfo);
      accessStore.setAccessToken(token);

      window.addEventListener('qiankun-theme-update', handleThemeUpdate);
      // 移除并销毁loading
      unmountGlobalLoading();
    },
    bootstrap() {
      return new Promise((resolve, reject) => {
        // eslint-disable-next-line no-console
        console.log('[qiankun] app bootstrap');
        resolve();
      });
    },
    update(props: any) {
      // eslint-disable-next-line no-console
      console.log('[子应用]  update');
      const { container } = props;
      initApplication(container);
    },
    unmount(props) {
      // 移除事件监听
      if (handleThemeUpdate) {
        // eslint-disable-next-line no-console
        console.log('remove sub apps theme handle:', app.name);
        window.removeEventListener('qiankun-theme-update', handleThemeUpdate);
      }
      // eslint-disable-next-line no-console
      console.log('[子应用] unmount', props);
      app?.unmount();
      app = null;
    },
  });
};
// 判断是否为乾坤环境,否则会报错iqiankun]: Target container with #subAppContainerVue3 not existed while subAppVue3 mounting!
qiankunWindow.__POWERED_BY_QIANKUN__
  ? await initQianKun()
  : await initApplication();

const handleThemeUpdate = (event: any) => {
  const newTheme = event.detail;
  if (newTheme) {
    // 更新响应式对象,由于是响应式的,Vue 会自动更新视图
    console.log('子应用主题已更新(通过 props + 事件):', newTheme);
    updatePreferences(newTheme);
  }
};

3. 支持多个应用动态加载

子应用如果不是固定subApps,要从后台加载那如何实现呢,比如我的程序实现子应用动态插拔,后台安装子应用之后前台就要支持展示。 代码逻辑是:本地调试从config.ts获取固定配置,发布环境读取后台配置。主要看registerApps()。 核心代码是下面这段:

 if (import.meta.env.PROD) {
      const data = await GetMicroApp();
      // 将获取的子应用数据转换为qiankun需要的格式
      subApps = data.map((app: MicroApp) => ({
        name: app.name, // 子应用名称
        entry: app.entry, // 子应用入口地址
        container: '#sub-container', // 子应用挂载节点
        activeRule: app.activeRule, // 子应用激活规则
        props: {
          userInfo: [],
          token: '',
        }, // 主应用与子应用通信传值
        sandbox: {
          strictStyleIsolation: true, // 启用严格样式隔离
        },
      }));
    }

完整文件代码是:

import type { MicroApp } from '#/api/apps/model';

import { useAccessStore, useUserStore } from '@vben/stores';

// 参考项目:https://github.com/wstee/qiankun-web
import { registerMicroApps } from 'qiankun';

import { GetMicroApp } from '#/api/apps';

import config from './config';

let { subApps } = config;

export async function registerApps() {
  try {
    // 判断是否是发布环境,发布环境从后台获p取subApps
    if (import.meta.env.PROD) {
      const data = await GetMicroApp();
      // 将获取的子应用数据转换为qiankun需要的格式
      subApps = data.map((app: MicroApp) => ({
        name: app.name, // 子应用名称
        entry: app.entry, // 子应用入口地址
        container: '#sub-container', // 子应用挂载节点
        activeRule: app.activeRule, // 子应用激活规则
        props: {
          userInfo: [],
          token: '',
        }, // 主应用与子应用通信传值
        sandbox: {
          strictStyleIsolation: true, // 启用严格样式隔离
        },
      }));
    }

    registerMicroApps(subApps, {
      beforeLoad: [
        (app: any) => {
          // eslint-disable-next-line no-console
          console.log('[主应用] beforeLoad', app.name);
          const useStore = useUserStore();
          const accessStore = useAccessStore();
          app.props.token = accessStore.accessToken;
          app.props.userInfo = useStore.userInfo;
          // app.props.publicKey = import.meta.env.VITE_PUBLIC_KEY;
        },
      ],
      // 生命周期钩子
      loader: (loading: any) => {
        // 可以在这里处理加载状态
        // eslint-disable-next-line no-console
        console.log('子应用加载状态:', loading);
      },
      beforeMount: [
        (app) => {
          // eslint-disable-next-line no-console
          console.log('[主应用] beforeMount', app.name);
          // const container = document.querySelector(app.container);
          // if (container) container.innerHTML = '';
          // 仅隐藏容器,不删除 DOM
          if (app.container.style) {
            app.container.style.display = 'none';
          }
        },
      ],
      beforeUnmount: (app) => {
        // 重新显示容器
        if (app.container.style) {
          app.container.style.display = 'none';
        }
      },
      afterUnmount: [
        (app) => {
          // eslint-disable-next-line no-console
          console.log('count: %s', app);
        },
      ],
    });
  } catch (error) {
    // eslint-disable-next-line no-console
    console.log('count: %s', error);
  }
}

GetMicroApp()返回数据结构json结果如下,主要是data的内容:

{
    "code": 200,
    "data": [
        {
            "name": "caipu-site",
            "entry": "/app/caipu-site/",
            "activeRule": "/app/caipu-site"
        },
        {
            "name": "email",
            "entry": "/app/email/",
            "activeRule": "/app/email"
        },
        {
            "name": "ip2region",
            "entry": "/app/ip2region/",
            "activeRule": "/app/ip2region"
        },
        {
            "name": "testdata",
            "entry": "/app/testdata/",
            "activeRule": "/app/testdata"
        }
    ],
    "msg": "",
    "success": true,
    "timestamp": 1768140865000
}

最后

  1. 上文有小伙伴回复是否可以支持主应用多页签切换不同子应用的页面状态保持,抱歉多次尝试未在vben实现此功能,作为一名后端人员技术有限如您有实现方案,请不吝指教。

  2. 抽时间也会尝试下wujie微前端方案完善相关功能,基于以上浅显内容,欢迎大积极尝试和分享。 如你有更好的建议内容分享请给评论。

如有幸被转载请注明出处: go-caipu

❌