为了账号安全,请及时绑定邮箱和手机立即绑定

微前端模式下子应用最常访问页面最佳实现

标签:
Html5

一、前言

最近一直在做tob项目,一条业务线多个tob工程

之前有文章写过实现微前端 👉tob系统微前端实践总结

也写过多工程之间都存在的公共模块如何处理 👉vue多工程间公共模块处理最佳实践

最近遇到一个比较有意思、且通用的需求🧐 本着总结为最佳实践的初心,写下本篇文章,欢迎大家讨论🤩

二、项目需求

在微前端的基座系统“工作台“页面实现一个”最常访问页面“功能,根据用户使用各子应用页面的频率,记录出该用户最常访问的子应用top4页面🤖

(注意:top4页面属于各个不同的子应用页面,基座系统的页面不在记录范围内,因为基座系统的入口相较子应用页面入口浅,可能记录在最常访问页面模块,同时工作台上有其他快捷入口)

问题关键点:

  1. 需要至少记录页面的url地址,及页面对应的菜单名称

  • 我们的系统因为之前做过统一面包屑处理,所以每个页面router-mate路由元信息里面都有对应菜单名称

  • 如果没有的话可以加上,顺便把统一面包屑给实现了,或者加判断过滤掉没有菜单名称的页面(兜底),即没有菜单名称的页面不再记录范围内

  • url地址:好说,有页面就有路由,只是需要注意,有些页面是动态路由、带有参数,所以需要记录的是完成的url地址,比如/lego/#/cms-page-manage/template-instance/view?pageId=555&authCode=lego_page_555

  • 菜单名称:一般在一级、二级菜单上的页面都会有对应的菜单名称,而非菜单上的页面,比如详情页等,除非在router-mate路由元信息里面有对应菜单名称

服务端记录还是前端记录,考虑到SPA时代、服务端记录实在太重,前端用localstorage记录实现

  • 需要记录页面的访问次数,根据次数取出最常访问top4

  • 记录页面最后一次访问的时间戳,比如访问次数最多的6个页面访问的次数依次是23(页面1),17(页面2),14(页面3),9(页面4),9(页面5),9(页面6);那么top4的第四个页是页面几,应该根据时间戳来,取时间戳大的那个页面(也就是最近访问的那个页面)

在哪个时刻将页面访问记录存到localstorage也很关键,是否可以不侵入子应用,在基座系统就实现?下面技术方案实现具体说明

三、技术实现

我们的技术栈都是vue,所以下面的实现方案都是基于vue,但是思路是通用的~
在全局前置守卫,打印子应用路由信息

import VueRouter from "vue-router";

const router = new VueRouter({ ... })


router.beforeEach((to, from, next) => {

    console.log(to)

}


子应用打印出如下:https://img1.sycdn.imooc.com//6107676c0001c79008740978.jpg基座系统打印出如下:https://img1.sycdn.imooc.com//6107676d00015d9513960326.jpg可以看到没有name,没有meta,针对问题关键点1, 在基座系统通过路由可以拿到url,但是拿不到菜单名称

为什么没有name,没有mate?
基座系统的路由跟子应用的路由不是同一个实例。因为在基座系统中,子应用的路由是/*,不清楚这块的指路tob系统微前端实践总结qiankun

方案一:微前端提供事件,在子应用的全局前置守卫触发事件存localstorage

基座系统:注册全局事件中心,监听用户打开子应用页面行为

import Event from 'eventemitter3';

import { setMenuUrl } from "@/utils/menu-url";

const eventCenter = new Event();


// 监听用户打开子应用页面行为

window.eventCenter.on('GET_SUPAPP_MENU', ({ path, name }) => {

     // 存localstorage操作

     setMenuUrl(name, path)

 })


子应用系统:在全局前置守卫触发用户打开页面事件

import VueRouter from "vue-router";

const router = new VueRouter({ ... })


router.beforeEach((to, from, next) => {

     if (window.__POWERED_BY_MICRO__) { // 微前端模式下

         // 触发用户打开页面事件

         window.eventCenter.emit("GET_SUPAPP_MENU", {path: `/${process.env.VUE_APP_NAME}#${to.fullPath}`, name: to.meta.menu.title});

     }

}


缺点:侵入子应用,需要对n个子应用都做处理

那有没有可以不侵入子应用去实现呢,见方案二

方案二:在基座系统的子应用容器组件中通过获取document.title存localstorage

其实就是想,还有没有办法可以拿到页面的菜单名称,document.title是可以的,也就是浏览器的标题,如下图https://img1.sycdn.imooc.com//6107679b0001c98006260146.jpg
上面说了,我们的系统每个页面都有名称,document.title也都根据页面名称设置了,没有设置的话,可以设置上,这样更规范~

基座系统是可以拿到子系统的url的,所以可以在基座系统的子应用容器组件(可参考:tob系统微前端实践总结)中去处理,如下代码

// 子应用容器.vue

import { setMenuUrl } from "@/utils/menu-url";

<script>

export default {

  data() {

    return {

      documentTitle: document.title

    };

  },

  watch: {

    $route(val) {

      this.handleRouteChange(val);

    },

  },

  beforeRouteEnter(to, from, next) {

    next((vm) => {

      const targetNode = document.getElementsByTagName("title")[0];

      const config = { attributes: true, childList: true, subtree: true };

      const callback = function () {

        vm.documentTitle = document.title

      };

      // 监听dom节点 titie变化

      const observer = new MutationObserver(callback);

      observer.observe(targetNode, config);

      vm.handleRouteChange.apply(vm, [to]);

    });

  },

   methods: {

    // 监听路由变化

    handleRouteChange(val) {

      if(this.documentTitle != process.env.VUE_APP_INDEX_TITLE && val.fullPath.split("/").length>3){

          // 存localstorage操作

          setMenuUrl(this.documentTitle, val.fullPath)

      }

    }

   }

}

</scripe>


针对问题关键点2,如何存localstorage

\\ src/utils/menu-url.js

import { storage } from './storage'


export const setMenuUrl = (name, url) => {

  if(!storage.getItem('menuUrl')){

    // 默认常用访问模块

    const initMenuUrl = {

      url: {

        name: "xxx",

        count: 1,

        time: new Date().getTime()

      }

    }

    storage.setItem('menuUrl', JSON.stringify(initMenuUrl))

  }

  const menuUrl = JSON.parse(storage.getItem('menuUrl'))

  if(menuUrl[url]){

    menuUrl[url].count++,

    menuUrl[url].time = new Date().getTime()

  } else {

    menuUrl[url] = {

      name,

      count: 1,

      time: new Date().getTime()

    }

  }

  storage.setItem('menuUrl', JSON.stringify(menuUrl))

}


浏览器localstorage截图 

https://img1.sycdn.imooc.com//610767ea0001ed0913620724.jpg


作者:jjjona0215
链接:https://juejin.cn/post/6991396113576099870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消