当前位置: 首页 > news >正文

RuoYi-Vue+WuJie整合傻瓜式教程-解决keepAlive问题

一、主应用整改步骤

1、引入插件

npm i wujie wujie-vue2

2、在 vue.config.js 文件配置 devServer

devServer: {host: "0.0.0.0",port: port,open: true,proxy: {// detail: https://cli.vuejs.org/config/#devserver-proxy'/dev-api': {target: 'http://localhost:8080/',changeOrigin: true,pathRewrite: {"^/dev-api": "",},},// 微应用1'/ffdg': {target: 'http://localhost:9999',ws: true,changeOrigin: true,},'/ffdg-api': {target: 'http://localhost:8888',ws: true,changeOrigin: true,pathRewrite: {"^/ffdg-api": "",},},// 微应用2},disableHostCheck: true,},

3、在 src/plugins 目录创建wujie.js,内容如下

// TODO 无界微前端引入
import WujieVue from "wujie-vue2";
import router from "@/router";const {setupApp, preloadApp, bus} = WujieVue;// 传入子应用的数据 tag v1.0
let isLocalhost = process.env.NODE_ENV === "development";
let localhost = "//localhost";const degrade = window.localStorage.getItem("degrade") === "true" || !window.Proxy || !window.CustomElementRegistry;
const props = {jump: (name) => {router.push({name});},
};/*** 大部分业务无需设置 attrs* 此处修正 iframe 的 src,是防止github pages csp报错* 因为默认是只有 host+port,没有携带路径*/
const attrs = {};const lifecycles = {beforeLoad: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeLoad 生命周期`),beforeMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeMount 生命周期`),afterMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterMount 生命周期`),beforeUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeUnmount 生命周期`),afterUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterUnmount 生命周期`),activated: (appWindow) => console.log(`${appWindow.__WUJIE.id} activated 生命周期`),deactivated: (appWindow) => console.log(`${appWindow.__WUJIE.id} deactivated 生命周期`),loadError: (url, e) => console.log(`${url} 加载失败`, e),
};// 子应用信息列表,
// name 标识对应子应用
// entry 表示目标 URL
// activeRule 表示拦截规则,所有路由以次前缀开头的都会使用子应用启用
const subApps = [{name: "ffem",entry: 'http://localhost:3000/ffem',activeRule: ["/basicinfo/","/firetopic/","/equipment/", "/alarmcenter/"],},// 其他子应用
];// 注册子应用
function register() {for (let i = 0; i < subApps.length; i++) {setupApp({name: subApps[i].name,url: (isLocalhost ? "" : window.location.origin) + subApps[i].entry + "/",attrs,exec: false,props,// fetch: credentialsFetch,degrade,...lifecycles,});console.log(subApps[i]);}
}// 判断是否是子应用,其实就是判断是否以对应前缀开头的 URL
function checkSubAppPath(path) {if (!path) return null;for (let i = 0; i < subApps.length; i++) {let activeRule = subApps[i].activeRule;for (let j = 0; j < activeRule.length; j++) {let rule = activeRule[j];if (rule.slice(-1) === '/') {rule = rule.substring(0, rule.length - 1);}if (path.startsWith(rule)) {return {...subApps[i],pref: (isLocalhost ? "" : window.location.origin) + subApps[i].entry};}}}return null;
}export {register, checkSubAppPath};

4、在main.js初始化无界

// TODO 无界微前端引入
import WujieVue from "wujie-vue2";// TODO 无界微前端引入
Vue.use(WujieVue);// TODO 无界微前端引入
import {register} from "@/plugins/wujie";// TODO 无界微前端引入
if (window.__POWERED_BY_WUJIE__) {let instance;window.__WUJIE_MOUNT = () => {// 注册微应用register();instance = new Vue({router,store,render: (h) => h(App)}).$mount("#ffsm_app");};window.__WUJIE_UNMOUNT = () => {instance.$destroy();};
} else {new Vue({router,store,render: (h) => h(App)}).$mount("#ffsm_app");
}

5、创建 src/views/components/MicroApp/micro-app.vue 组件,用于承接页面

  • 注意,这里使用了 wujie 的 alive=true 模式。
  • 子应用就会只初始化一次,但是切换路由时,子应用无法响应
  • 这里使用 bus 事件通知子应用跳转对应路由
  • 文档:https://wujie-micro.github.io/doc/api/startApp.html#alive
<!-- // TODO 无界微前端引入 -->
<template><WujieVue width="100%" height="100%":name="microObj.name":sync="true":alive="true":props="{token, jump, updateTitle}":url="microUrl"></WujieVue>
</template><script>
import {bus} from "wujie";
import {checkSubAppPath} from "@/plugins/wujie";
import {getToken} from "@/utils/auth";
import {tansParams} from "@/utils/ruoyi";export default {name: "micro-app",data() {return {token: getToken()}},computed: {// 依据路由变更子应用的URLmicroObj() {const path = this.$route.path;return checkSubAppPath(path) || {name: '404'};},// 依据路由变更子应用的URLmicroUrl() {let url = this.microObj.pref + this.$route.path;if (this.$route?.query && Object.keys(this.$route?.query).length) {url = url + "?" + tansParams(this.$route?.query);}return url;}},mounted() {console.log("micro-app", this.microObj, this.microUrl, this.$route);},created() {// 子应用跳转, :alive="true" 缓存子应用实例,子应用依据 name 只会创建一次对象,后续跳转需要使用 bus 事件通知子应用,子应用内部实现跳转// 目的,解决keepAlive 的问题bus.$emit("to-page", this.$route);},methods: {jump(location) {// console.log(this.$router.getRoutes());this.$router.push(location);}}
};
</script>

6、修改 src/store/modelues/permission.js 文件,修改 filterAsyncRouter 方法,内容如下

  • 动态配置路由时,判断符合 子应用 的路由,指向到无界所在组件页面
import MicroApp from "@/views/components/MicroApp/micro-app";function filterAsyncRouter(asyncRouterList, lastRouter = false, type = false, rootPath) {return asyncRouterList.filter(route => {if (type && route.children) {route.children = filterChildren(route.children)}if (route.component) {// Layout ParentView 组件特殊处理if (route.component === 'Layout') {route.component = Layout} else if (route.component === 'ParentView') {route.component = ParentView} else if (route.component === 'InnerLink') {route.component = InnerLink}// TODO 无界微前端引入else if (checkSubAppPath(rootPath)) {route.component = MicroApp}else {route.component = loadView(route.component)}}if (route.children != null && route.children && route.children.length) {route.children = filterAsyncRouter(route.children, route, type, (rootPath ? rootPath + "/" : "") + route.path)} else {delete route['children']delete route['redirect']}return true})
}

7、修改 src/plugins/tab.js,内容如下。目的解决 keepAlive的问题

  • 启动了子应用的缓存后,主引用的 tab 发生变化,同步需要修改 子应用 的tab
  • 使用 Proxy 同步触发每个方法到子应用对应操作
import store from '@/store'
import router from '@/router';
import {bus} from "wujie";const target = {// 刷新当前tab页签refreshPage(obj) {const {path, query, matched} = router.currentRoute;if (obj === undefined) {matched.forEach((m) => {if (m.components && m.components.default && m.components.default.name) {if (!['Layout', 'ParentView'].includes(m.components.default.name)) {obj = {name: m.components.default.name, path: path, query: query};}}});}return store.dispatch('tagsView/delCachedView', obj).then(() => {const {path, query} = objrouter.replace({path: '/redirect' + path,query: query})})},// 关闭当前tab页签,打开新页签closeOpenPage(obj) {store.dispatch("tagsView/delView", router.currentRoute);if (obj !== undefined) {return router.push(obj);}},// 关闭指定tab页签closePage(obj) {if (obj === undefined) {return store.dispatch('tagsView/delView', router.currentRoute).then(({lastPath}) => {return router.push(lastPath || '/');});}return store.dispatch('tagsView/delView', obj);},// 关闭所有tab页签closeAllPage() {return store.dispatch('tagsView/delAllViews');},// 关闭左侧tab页签closeLeftPage(obj) {return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);},// 关闭右侧tab页签closeRightPage(obj) {return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);},// 关闭其他tab页签closeOtherPage(obj) {return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);},// 添加tab页签openPage(title, url, params) {var obj = {path: url, meta: {title: title}}store.dispatch('tagsView/addView', obj);return router.push({path: url, query: params});},// 修改tab页签updatePage(obj) {return store.dispatch('tagsView/updateVisitedView', obj);}
}// TODO 无界微前端引入
Object.keys(target).forEach(key => {target[key] = new Proxy(target[key], {apply(target, thisArg, argumentsList) {console.log('tab action target', target.name);// 子应用跳转, :alive="true" 缓存子应用实例,子应用依据 name 只会创建一次对象,后续跳转需要使用 bus 事件通知子应用,子应用内部实现跳转bus.$emit("tab-action", target.name, argumentsList);return target.apply(thisArg, argumentsList);}});
});export default target;

二、子应用整改步骤

1、在 public/index.html 加入如下脚本

  • 解决子应用样式和主应用样式重叠问题
      <script>// TODO 无界微前端if (window.__POWERED_BY_WUJIE__) {document.querySelector("body").classList.add('micro-wujie');}</script>

2、在 src/assets/styles 目录创建 wujie.scss。内容如下

.micro-wujie {position: relative !important;.fixed-header {display: none !important;}.sidebar-container {display: none !important;}.main-container {margin-left: 0 !important;.fixed-header {display: none !important;}.main-right {padding-top: 0 !important;.breadcrumb-container {display: none !important;}}}// 解决element的下拉框样式异常问题.el-popper {position: absolute !important;}.vxe-table--tooltip-wrapper {position: fixed !important;}
}

3、在 src/assets/styles/index.scss 引入上面样式

// TODO 无界微前端引入
@import './wujie.scss';

4、在 main.js 修改 vue 初始化的方式,内容如下

// TODO 无界微前端引入
if (window.__POWERED_BY_WUJIE__) {let instance;window.__WUJIE_MOUNT = () => {// 创建VUE实例instance = new Vue({router,store,render: (h) => h(App),}).$mount("#ffem_app");// 监听无界主应用的路由跳转window.$wujie?.bus.$on("to-page", (route) => {let curRoute = router.getRoutes().find(item => item.name === route.name);console.log("FFEM-子应用监听:" + new Date().getTime(), curRoute);// 必须使用 replace 方式,否则导致 history 出现多次(主应用一次,子应用一次)跳转if(curRoute){router.replace({...curRoute,query: route.query,params: route.params,});}});// 监听无界主应用tab页操作window.$wujie?.bus.$on("tab-action", (action, args) => {console.log("FFEM-子应用监听:" + new Date().getTime(), action, args)let route = null;if (args && args.length) {route = router.getRoutes().find(item => item.name === args[0]?.name);route = {...route,query: args[0].query,params: args[0].params,}}instance.$tab[action](route);});};window.__WUJIE_UNMOUNT = () => {instance.$destroy();};
} else {new Vue({router,store,render: (h) => h(App),}).$mount("#ffem_app");
}

5、修改 src/ruoter/index.js 的内容

// TODO 无界微前端引入
const router = new Router({base: "/ffem/",mode: 'history', // 去掉url中的#scrollBehavior: () => ({y: 0}),routes: constantRoutes
});/*** 路由跳转,子应用操作主应用跳转* @param location 跳转消息*/
export function routePush(location) {if (window.__POWERED_BY_WUJIE__) {// router.push(location);console.log('跳转路由', location)window.$wujie?.props.jump(location);} else {router.push(location);}
}export default router;

6、修改 src/layout/components/AppMain.vue

<template><section class="app-main"><breadcrumb id="breadcrumb-container" class="breadcrumb-container" /><!-- 主要解决作为子应用时,不需要过渡动画,主应用已经有了动画效果  --><transition :name="!isSubApp?'fade-transform':''" mode="out-in"><keep-alive :include="cachedViews"><router-view :key="key" /></keep-alive></transition></section>
</template><script>
import Breadcrumb from "@/components/Breadcrumb";export default {name: "AppMain",components: { Breadcrumb },computed: {cachedViews() {return this.$store.state.tagsView.cachedViews;},key() {return this.$route.path;}},data(){return {// TODO 无界微前端引入isSubApp: window.__POWERED_BY_WUJIE__,}}
};
</script>

7、修改 src/utils/auth.js 文件

  • 这里比较关键,我的主应用和子应用的token是通用的。所以这里直接引用了父应用的token
  • 但是看自己的设计方式,主应用的token 从 window.$wujie?.props.token 获取,自行解析
import Cookies from 'js-cookie'const TokenKey = 'Ffem-Token'export function getToken() {// TODO 无界微前端引入if (window.__POWERED_BY_WUJIE__) {const props = window.$wujie?.props;if (props && props.token) {setToken(props.token);}}return Cookies.get(TokenKey)
}export function setToken(token) {return Cookies.set(TokenKey, token)
}export function removeToken() {return Cookies.remove(TokenKey)
}

相关文章:

  • 2025五一杯数学建模A题:支路车流量推测问题,思路分析+模型代码
  • 【FreeRTOS-列表和列表项】
  • 机器学习Day15 LightGBM算法
  • 基于机器学习的舆情分析算法研究
  • python如何word转pdf
  • 机器学习实战,天猫双十一销量与中国人寿保费预测,使用多项式回归,梯度下降,EDA数据探索,弹性网络等技术
  • PostgreSQL 数据库下载和安装
  • Ubuntu 安装 Cursor
  • 设计模式简述(十五)观察者模式
  • 【Redis分布式】主从复制
  • 【网络服务器】——回声服务器(echo)
  • Linux 环境下 Mysql 5.7 数据定期备份
  • PCA主成分分析法(最大投影方差,最小重构距离,SVD角度)
  • 生物化学笔记:神经生物学概论06 听觉系统 结构与功能 声强范围的检测(外毛细胞动态调节)
  • 第二章 日志分析-apache日志分析(玄机系列)
  • STM32移植U8G2
  • WPF使用SQLSugar和Nlog
  • QT —— 信号和槽(带参数的信号和槽函数)
  • C++漫溯键值的长河:map set
  • IPD研学:76页页基于IPD思想-华为需求管理培训方案【附全文阅读】
  • 李强签署国务院令,公布修订后的《中华人民共和国植物新品种保护条例》
  • 当农民跨进流动的世界|劳动者的书信①
  • 中国空间站多项太空实验已取得成果,未来将陆续开展千余项研究
  • 摩天大楼天津117大厦复工背后:停工近十年,未知挑战和压力仍在
  • 夜读丨春天要去动物园
  • 赵乐际主持十四届全国人大常委会第十五次会议闭幕会并作讲话