一、前言
最近一直在做tob项目,一条业务线多个tob工程
之前有文章写过实现微前端 👉tob系统微前端实践总结
也写过多工程之间都存在的公共模块如何处理 👉vue多工程间公共模块处理最佳实践
最近遇到一个比较有意思、且通用的需求🧐 本着总结为最佳实践的初心,写下本篇文章,欢迎大家讨论🤩
二、项目需求
在微前端的基座系统“工作台“页面实现一个”最常访问页面“功能,根据用户使用各子应用页面的频率,记录出该用户最常访问的子应用top4页面🤖
(注意:top4页面属于各个不同的子应用页面,基座系统的页面不在记录范围内,因为基座系统的入口相较子应用页面入口浅,可能记录在最常访问页面模块,同时工作台上有其他快捷入口)
问题关键点:
需要至少记录页面的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)
}
子应用打印出如下:基座系统打印出如下:可以看到没有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是可以的,也就是浏览器的标题,如下图
上面说了,我们的系统每个页面都有名称,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截图
作者:jjjona0215
链接:https://juejin.cn/post/6991396113576099870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。