基于codeWave平台开发的应用集成wujie微前端总结

151 阅读4分钟

背景说明

  • 基座mainApp,子应用cust,contact全部都是基于codeWave搭建

  • 所有子应用都开启无界的保活模式

具体场景

基座首次进入子应用

// 由于子应用都开启了保活模式所以需要采用如下方式处理跳转(具体看下图)
// 主应用路由实例进行跳转
 this.$router.push(`${prefix}`/`${subAppcode}`?`${subAppcode}`=`${fullPath}`)

image.png

子应用内部跳转(子应用已经实例化)

//内部跳转直接路由方法跳转
this.$router.push(url)

注意:vueRouter3中导航重复路由path会报警告,我们需要特殊处理一下;特别是项目中不支持动态路由的情况。

跨应用跳转

custcontact跳转(/m/cust/list/m/contact/detail?id=1&title=张三

  • contact应用是否已经实例化,
    • 如果已经实例化则再判断contact应用的curentRoute元信息的path和要去的页面的path是否一样,如果一样的话需要特殊处理
    • contact应用没有实例化,直接使用通信的方式告诉主应使用路由跳转(类比基座首次进入子应用场景)

记录多页签(类似于浏览器的多页签)

基于通信告诉主应用,主应用维护一个tagList,定义好Dto,然后使用LRU算法移除最早未使用的页签等,可参考如下页签管理类

浏览器回退相关场景

  • 开启keep-alive后,回退后需要销毁from页对应的vue页面实例
//思路1、思路是缓存的必须得是每个.vue页面的default导出内容,通过调用vue的destroy方法销毁对应实例(需要对导出的源码操作)
 // 思路2、如下
                <template>
                  <!-- 通过 ref 获取 keep-alive 实例 -->
                  <keep-alive ref="keepAliveRef">
                    <!-- 路由视图,通过 $route.fullPath 确保路由切换时触发组件重建 -->
                    <router-view :key="$route.fullPath"></router-view>
                  </keep-alive>
                </template>
            <script>
            export default {
              methods: {
                clearCacheByKey(key) {
                  // 获取 keep-alive 实例的缓存对象和键数组
                  const { cache, keys } = this.$refs.keepAliveRef;
                  if (cache[key]) {
                    // 销毁组件实例,清理缓存
                    cache[key].componentInstance.$destroy();
                    delete cache[key];
                    const index = keys.indexOf(key);
                    if (index > -1) keys.splice(index, 1);
                  }
                }
              }
            }
            </script>
  • 回退后需要拿到当前激活的路由信息
//实现思路:
 1、首先需要记录当前激活的应用唯一标识
 2、每个应用首次进入时需要将自身的路由实例告诉基座
 3、基座维护一个路由实例的map
 4、基座通过子应用标识去获取子应用路由实例,并取到对应的路由元信息
  • 回退后刷新to页面的列表等操作(一般来说只要不keep-alive就都会刷新,主要解决keep-alive后)
//实现思路1:
1、列表页面约定一个标识`flag`标记标记此页面是否已经挂载过
2、基座维护一个是否需要刷新的标识`isRefrsh`
3、利用`activeted`钩子,在页面激活的时候触发约定的`refresh`方法
//实现思路2:
1、保证缓存的`<component>`的对应的`.vue`页面的默认导出,可用node.jsrequire语法导入页面
2、页面有唯一的标识通过`ref`调用页面的方法实现刷新

路由跳转实现思路(伪代码)

  1. 基座定义一个注册子应用路由的方法并挂载到基座的window对象上通过props传递给子应用,用来判断子应用是否已经实例化
  2. 子应用注册监听事件
window.$wujie.bus.$on('routeChange',(urlObj)=>{})
  1. 基座定义jump方法
jump(urlObj){
  const findCurRoute = window.VueRouterInstance.currentRoute
  const find = window.routeManager.subAppRouterInstances.get(urlObj.code)
  if (find) {
    // 要去页面对应的应用已经实例化 
    window.$wujie.bus.$emit('routeChange', urlObj)
  } else {
    // 要去页面对应应用没有实例化 
    if (findCurRoute && findCurRoute.path === urlObj.resourcesValue) {
      // 出现此种情况是有问题的,一般不会出现       
    } else {
      //直接调用           
      this.$router.push(urlObj.fullPath)
    }
  }
}

其他技巧

  • 路由后置守卫触发wujieeventbus事件
  • 路由前置守卫做用户页面权限处理

多页前管理类实现(仅供参考)

// 多页签管理类
class SimpleTabManager {
    constructor(options = {}) {
        this.options = {
            maxTabs: 10,
            onTabChange: () => { },
            ...options
        };
        // 初始状态包含首页
        this.state = {
            tabs: [],
            activeTabId: '',
            tabHistory: []
        };
    }
    // -------------------- 核心API --------------------
    addTab(RouterTagDto) {
        const onlyKey = RouterTagDto.onlyKey;
        // 存在性检查
        const existingTab = this.getTab(onlyKey);
        if (existingTab) {
            this.switchTab(onlyKey);
            return existingTab;
        }
        // 容量控制
        if (this.state.tabs.length >= this.options.maxTabs) {
            this.removeOldestTab();
        }
        // 创建新页签
        const newTab = this.createTabObject(RouterTagDto);
        this.state.tabs = [...this.state.tabs, newTab];
        this.switchTab(onlyKey);
        return newTab;
    }
    removeTab(onlyKey, options = { force: false }) {
        const closedTab = this.getTab(onlyKey);
        if (!closedTab) return null;
        // 保护最后一个页签
        if (!options.force && this.state.tabs.length === 1) {
            return null;
        }
        const wasActive = this.isTabActive(onlyKey);
        // 执行删除
        this.state.tabs = this.state.tabs.filter(t => t.onlyKey !== onlyKey);
        this.purgeHistory(onlyKey);
        let activatedTab = null;
        if (wasActive) {
            activatedTab = this.activateFallbackTab();
        }
        // 触发回调
        this.options.onTabChange(
            'remove',
            closedTab,
            this.state.tabs,
            wasActive ? { newActiveTab: activatedTab } : null
        );
        return wasActive ? activatedTab : null;
    }
    updateTag(onlyKey, RouterTagDto) {
        if (RouterTagDto && RouterTagDto.onlyKey) {
            const tab = this.getTab(onlyKey)
            const oldTab = { ...tab }
            Object.assign(tab, RouterTagDto)
            this.state.activeTabId = tab.onlyKey
            this.triggerTabChange('update', RouterTagDto, oldTab)
        } else {
            console.warn('Cannot update the onlyKey of a tab.');
        }
    }
    /**
 * 删除除指定onlyKey外的所有页签
 * @param {string} onlyKeyToKeep 需要保留的页签的唯一标识
 */
    removeAllExcept(onlyKeyToKeep) {
        // 检查要保留的页签是否存在
        const tabToKeep = this.getTab(onlyKeyToKeep);
        if (!tabToKeep) {
            console.warn(`Tab with onlyKey ${onlyKeyToKeep} not found`);
            return null;
        }
        // 筛选出需要删除的页签
        const tabsToRemove = this.state.tabs.filter(tab => tab.onlyKey !== onlyKeyToKeep);
        // 更新状态:只保留指定的页签
        this.state.tabs = [tabToKeep];
        // 清理历史记录:只保留指定页签的记录
        this.state.tabHistory = this.state.tabHistory.filter(id => id === onlyKeyToKeep);
        // 如果当前活动页签被删除,则激活保留的页签
        if (!this.isTabActive(onlyKeyToKeep)) {
            this.state.activeTabId = onlyKeyToKeep;
            tabToKeep.lastActive = Date.now();
        }
        // 触发通知
        this.options.onTabChange(
            'removeAllExcept',
            tabToKeep,
            this.state.tabs,
            {
                removedTabs: tabsToRemove,
                keptTab: tabToKeep
            }
        );
        return tabToKeep;
    }
    removeByOnlyKey(onlyKey) {
        return this.removeTab(onlyKey)
    }
    removeActiveTab() {
        return this.removeTab(this.state.activeTabId)
    }
    switchTab(onlyKey) {
        const oldTab = this.getActiveTab();
        // 处理空值情况
        if (!onlyKey || !this.hasTab(onlyKey)) {
            this.state.activeTabId = null;
            this.triggerTabChange('switch', null, oldTab);
            return { to: null, from: oldTab };
        }
        const newTab = this.getTab(onlyKey);
        // 更新激活状态
        newTab.lastActive = Date.now();
        this.state.activeTabId = onlyKey;
        // 维护历史记录
        this.updateHistory(onlyKey);
        this.triggerTabChange('switch', newTab, oldTab);
        return { to: newTab, from: oldTab };
    }
    // -------------------- 工具方法 --------------------
    createTabObject(RouterTagDto) {
        return {
            code: RouterTagDto.code,
            onlyKey: RouterTagDto.onlyKey,
            title: RouterTagDto.title,
            resourcesValue: RouterTagDto.resourcesValue,
            fullPath: RouterTagDto.fullPath,
            lastActive: Date.now(),
            busAppCode: RouterTagDto.busAppCode,
            activeMenuId: RouterTagDto.activeMenuId
        };
    }
    setDeafautTab(RouterTagDto) {
        const newTab = this.createTabObject(RouterTagDto);
        this.state.tabs = [...this.state.tabs, newTab];
        this.state.tabHistory = [RouterTagDto.onlyKey]
        this.state.activeTabId = RouterTagDto.onlyKey
        this.options.onTabChange('init', newTab, this.state.tabs, {
            to: newTab,
            from: null
        });
    }
    getTab(onlyKey) {
        return this.state.tabs.find(t => t.onlyKey === onlyKey);
    }
    hasTab(onlyKey) {
        return this.state.tabs.some(t => t.onlyKey === onlyKey);
    }
    isTabActive(onlyKey) {
        return this.state.activeTabId === onlyKey;
    }
    getActiveTab() {
        return this.getTab(this.state.activeTabId);
    }
    // -------------------- 历史记录管理 --------------------
    updateHistory(onlyKey) {
        this.state.tabHistory = [
            ...this.state.tabHistory.filter(id => id !== onlyKey),
            onlyKey
        ].slice(-this.options.maxTabs);
    }
    purgeHistory(onlyKey) {
        this.state.tabHistory = this.state.tabHistory.filter(
            id => id !== onlyKey
        );
    }
    // -------------------- 辅助操作 --------------------
    removeOldestTab() {
        const oldest = [...this.state.tabs]
            .sort((a, b) => a.lastActive - b.lastActive)[0];
        if (oldest) this.removeTab(oldest.onlyKey);
    }
    activateFallbackTab() {
        // 获取有效历史记录
        const validHistory = this.state.tabHistory
            .filter(id => this.hasTab(id));

        // 尝试获取最近可用页签
        const lastActiveId = validHistory.length > 0
            ? validHistory[validHistory.length - 1]
            : this.state.tabs[0]?.onlyKey;

        if (lastActiveId) {
            return this.switchTab(lastActiveId).to;
        }
        return null;
    }
    triggerTabChange(action, newTab, oldTab) {
        this.options.onTabChange(action, newTab, this.state.tabs, {
            to: newTab,
            from: oldTab
        });
    }
}
// 初始化实例
const tagManager = new SimpleTabManager({
    maxTabs: 10,
    onTabChange: (action, tab, tabList, changeObj) => {
        console.log('页签状态变更:', action, tab?.onlyKey, tabList);
        console.log('@@@@当前所有页签', _this.$global.frontendVariables.routerTagList)
    }
});

window.tagManager = tagManager;