演示效果
子应用的项目
基应用嵌入子应用效果图
前言
博主在项目中由于多个团队开发项目,需要相互嵌套,从而了解到微前端micro-app嵌套项目,从而简单记录一下使用过程
一、微前端是什么?
微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
它主要解决了两个问题:
- 1、随着项目迭代应用越来越庞大,难以维护。
- 2、跨团队或跨部门协作开发项目导致效率低下的问题
二、使用步骤
1.安装依赖
npm i @micro-zoe/micro-app --save
2.在入口处引入
main.js
import microApp from "@micro-zoe/micro-app";
microApp.start();
if (window.__MICRO_APP_ENVIRONMENT__) console.log('我在微前端环境中')
else console.log('我不在微前端环境中')
if (window.__MICRO_APP_BASE_APPLICATION__) {
console.log('我是基座应用')
}
3.子应用的路由()
4.分配一个路由给子应用(重要)(基应用的路由)
/**
* 注意:
* 权限是通过路由中的 name 属性来进行定位,所以 name 必须填写,且唯一
*
* isMenu false表示该路由仅仅是路由,而不生成菜单
* 默认所有路由都生成菜单,不想生成菜单直接添加属性 isMenu 为 false 即可
* 注意:1.不需要每个路由都添加 isMenu 属性,只需要给不需要生成菜单的路由添加该属性,需要生成菜单的路由不需要加该属性
* 2.二级菜单 isMenu 必须写在 children 里的每条路由上,一级菜单写在 children 外面(当前路由即可)
* 3.权限路由不需要添加 isMenu ,因为权限路由是有后端返回的,所以不需要添加 isMenu 属性
* 特别注意: 后台返回的权限菜单数据
* path值与name的值(后台返回的权限菜单中的path值与name值),必须对应组件目录中的组件所在目录
* 如:角色路由组件 roles 目录必须与后台返回的数据中的角色路由 name 值一致,否则加载不到组件
* path:'/组件目录名称',
* name:'组件目录名称'
* path 与 name 必须对应组件目录名称
*/
// 所有人都可以访问
const routes = [
{
path: "/login",
//name:'login',
isMenu: false,
component: () =>
import(/* webpackChunkName: "login" */ "@/views/login.vue"),
},
{
path: "/",
name: "dashboard",
redirect: "/home",
isMenu: false,
children: [
{
path: "/home",
name: "home",
meta: { title: "主页", icon: false },
component: () => import(/* webpackChunkName: "home" */ "@/views"),
},
],
},
{
path: "/test",
name: "test",
isMenu: true,
meta: { title: "测试管理", icon: "el-icon-s-tools" },
component: () => import(/* webpackChunkName: "test" */ "@/views/test"),
},
{
path: "/versiontwotable",
name: "表格",
isMenu: true,
meta: { title: "表格2", icon: "el-icon-s-tools" },
component: () =>
import(
/* webpackChunkName: "test" */ "@/views/versiontwo/pages/makecodeIndex.vue"
),
},
{
path: "/my-page/*",
name: "子应用",
isMenu: true,
meta: { title: "子应用", icon: "el-icon-s-tools" },
children: [
// 其他的路由都写到这里
{
name: "/",
path: "/",
redirect: "/index",
meta: { title: "index" },
isMenu: true,
children: [
{
name: "index",
path: "/index",
meta: { title: "index" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
{
name: "/form",
path: "/form",
redirect: "/form/index",
meta: { title: "form" },
isMenu: true,
children: [
{
name: "/form/index",
path: "/form/index",
meta: { title: "form" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
{
name: "/example",
path: "/example",
redirect: "/example/tree",
meta: { title: "example" },
isMenu: true,
children: [
{
name: "/tree",
path: "/example/tree",
meta: { title: "tree" },
component: () => import("@/views/microapp/pages/index.vue"),
},
{
name: "/copy",
path: "/example/copy",
meta: { title: "copy" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
{
name: "/table",
path: "/table",
meta: { title: "table" },
redirect: "/table/index",
isMenu: true,
children: [
{
name: "/table/index",
path: "/table/index",
meta: { title: "table" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
{
name: "/admin",
path: "/admin",
meta: { title: "admin" },
redirect: "/admin/index",
isMenu: true,
children: [
{
name: "/admin/index",
path: "/admin/index",
meta: { title: "admin" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
{
name: "/people",
path: "/people",
meta: { title: "people" },
redirect: "/people/index",
isMenu: true,
children: [
{
name: "/people/index",
path: "/people/index",
meta: { title: "people" },
component: () => import("@/views/microapp/pages/index.vue"),
},
],
},
],
},
{
path: "/test1",
name: "test1",
isMenu: true,
meta: { title: "角色管理", icon: "el-icon-s-tools" },
children: [
{
path: "/roles3-2",
name: "roles93-2",
meta: { title: "角色管理3-2", icon: "el-icon-s-data" },
component: () => import(/* webpackChunkName: "test1" */ "@/views/test"),
},
{
path: "/roles3-3",
name: "roles93-3",
isMenu: true,
meta: { title: "角色管理3-3", icon: "el-icon-s-data" },
component: () => import(/* webpackChunkName: "tes3" */ "@/views/test"),
},
{
path: "/roles3-1",
name: "roles93-1",
isMenu: true,
meta: { title: "角色管理3-1", icon: "el-icon-s-data" },
children: [
{
path: "/a",
name: "a",
meta: { title: "角色管理1-1", icon: "el-icon-s-data" },
component: () =>
import(/* webpackChunkName: "test5555" */ "@/views"),
},
],
},
],
},
{
path: "/401",
name: "401",
isMenu: false,
component: () =>
import(/* webpackChunkName: "401" */ "@/views/error/401.vue"),
},
];
5.在基应用的MyPage
页面中嵌入子应用
基座下发跳转指令
<!-- -->
<template>
<div>
<h1>子应用</h1>
<!--
name(必传):应用名称
url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
-->
<micro-app
name="app1"
url="http://localhost:8888/"
baseroute="/my-page"
keep-alive
@datachange="handleDataChange"
></micro-app>
</div>
</template>
<script>
import microApp from "@micro-zoe/micro-app";
export default {
data() {
//这里存放数据
return {};
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {
"$route.path": {
handler(newValue, oldValue) {
console.log("当前的数据", newValue);
microApp.setData("app1", { path: newValue }); //向子应用发送路由数据
},
immediate: true,
},
},
//方法集合
methods: {
AppTo() {},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
// const childData = microApp.getData("app1"); // 返回子应用的data数据
// console.log("childData", childData);
},
methods: {
handleDataChange(e) {
console.log("来自子应用的数据", e.detail);
},
},
};
</script>
<style lang="scss" scoped></style>
三、子应用的操作
1、设置基础路由(如果基座应用是history路由,子应用是hash路由,这一步可以省略)
/router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
const router = new VueRouter({
// 👇 设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,如果没有设置baseroute属性,则此值默认为空字符串
base: window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL,
routes,
});
main.js
let app = new Vue({
router,
render: h => h(App),
}).$mount('#app')
2、接收基应用传过来的数据,进行路由跳转
在APP.vue这里 <template> <div id="app"> <router-view /> </div> </template> <script> export default { data() { //这里存放数据 return {}; }, //生命周期 - 创建完成(可以访问当前this实例) created() {}, //生命周期 - 挂载完成(可以访问DOM元素) mounted() { //第一种跳转 // if (window.__MICRO_APP_BASE_ROUTE__) { // window.microApp.addDataListener((data) => { // console.log("data的数据在这里冲冲冲", data); // this.$router.push({ path: data.path }); // }); // } }, }; </script> <style> html, body { width: 100%; height: 100%; margin: 0; padding: 0; } </style> 在main.js这里 // 与基座进行数据交互 //第二种跳转 if (window.__MICRO_APP_ENVIRONMENT__) { console.log("我在微前端环境中"); if (window.__MICRO_APP_BASE_ROUTE__) { // 监听基座下发的数据变化 window.microApp.addDataListener((data) => { console.log("data的数据在这里冲冲冲", data); console.log("router.currentRoute.value.path", router.currentRoute.path); // 当基座下发path时进行跳转 if (data.path && data.path !== router.currentRoute.path) { router.push({ path: data.path }); } }); } // 向基座发送数据 setTimeout(() => { window.microApp.dispatch({ myname: "tenant-app" }); }, 3000); }
3.子应用隐藏左边侧边栏和上边头部栏
第一种方法:判断是否在微前端环境中,如果是,则隐藏,反之显示
<template>
<el-container>
<!-- undefined取反是true -->
<el-header v-if="!isShow">
<header-temp></header-temp>
</el-header>
<el-container>
<el-aside v-if="!isShow" width="200px"
><sidebar class="sidebar-container"></sidebar
></el-aside>
<el-main><app-main></app-main></el-main>
</el-container>
</el-container>
</template>
<script>
import AppMain from "./appMain"; // 页面布局的右侧区域
import sidebar from "./sideBar"; // 页面布局的左侧菜单
import headerTemp from "./headerTemp"; // 页面布局的header菜单
export default {
name: "layout",
components: { sidebar, AppMain, headerTemp },
data() {
return {
isShow: window.__MICRO_APP_ENVIRONMENT__, //判断当前是否在微前端环境,如果在则是true 如果不在是undefined
};
},
created() {
console.log("isShow", this.isShow);
},
};
</script>
<style>
.el-header {
padding: 0 !important;
margin-left: 180px;
}
</style>
第二种方法:子应用中添加一个非layout布局的空页面,当子应用单独运行时,指向layout布局页面,如果是在微服务中使用,则指向空页面,在src/layout下创建EmptyLayout.vue
<template>
<div class="emptyLayout">
<router-view> </router-view>
</div>
</template>
<style lang="scss" scoped>
.emptyLayout {
height: 100%;
}
</style>
在src\router\index.js下
//尝试微前端获取layout
function getComponent() {
if (window.__MICRO_APP_ENVIRONMENT__) {
console.log("进入了这里了嘛 微前端");
return () => import("@/components/layout/EmptyLayout.vue");
} else {
return Layout;
}
}
component: getComponent(), //尝试微前端
总结
这里博主只是通过简单的两个项目进行微前端的搭建,具体项目不便编写,若有遇到的可以咨询博主,有用的的铁子可以一键三联,谢谢