京东微前端应用MicroApp,主应用vite-vue3,子应用vite-vue3+pinia

micro-app 官方地址
micro-app 官方demo地址
这篇文章主要是为了记录,本人在使用中遇到的一些问题,供参考

  1. 资源找不到 -> 本地使用代理,显示nginx转发
  2. 子应用使用组件插槽或者pinia,路由懒加载报错问题 ->小项目几个路由不要懒加载,大项目中懒加载的时候不要使用pinia或者组件中不适用

网上的个人文章和官网的都差不多,都没有我遇到的问题,这篇文章只着重把我遇到的问题地方贴出来

资源找不到问题

用vite,图片资源放在public下面,使用绝对地址引用图片

  • 子应用在public文件夹中新建assets文件夹,在assets文件夹下建一个vite1 文件夹,静态资源放置于此
  • 主应用开发环境使用proxy,线上环境使用nginx转发
  • image.png

子应用使用组件插槽或者pinia,路由懒加载报错问题

  • 小项目几个路由加载页面不使用懒加载,
  • 大项目中懒加载的时候不要使用pinia或者组件中不适用

主应用为了美观重写菜单

image.png

HeaderNav 组件

export const menuList = [
  {
    
    
    title: 'home',
    appName: 'home',
    route: '/',
    children: []
  },
  {
    
    
    title: 'vite1',
    appName: 'appname-vite1',
    route: '/app-vite1',
    children: [
      {
    
    
        title: 'home',
        route: '/app-vite1/home',
        hashName: 'home',
        isShow: true
      },
      {
    
    
        title: 'page2',
        route: '/app-vite1/page2',
        hashName: 'page2',
        isShow: true
      }
    ]
  }
]
export const routeNameList = menuList.map((item) => item.route)
export const hashNameList = menuList.reduce((total, cur) => {
    
    
  const arr = cur.children.map((item) => item.hashName) || []
  return (total = [...total, ...arr])
}, [])

<!--
 * @Author: 
 * @Date: 2023-06-13 16:23:35
 * @LastEditors: 
 * @LastEditTime: 2023-06-17 14:18:29
 * @FilePath: \microApp_demo\vite_vue3_main\src\components\HeaderNav.vue
 * @Description: HeaderNav
-->
<template>
  <div id="HeaderNav">
    <div class="menu_list">
      <div
        v-for="(menu, index) in menuList"
        :key="index"
        class="menu_item_box"
        @mouseenter="mouseenter(menu.children, index)"
        @mouseleave="mouseleave"
      >
        <div v-show="mouseenterIndex === index" class="subMenu_list">
          <div
            v-for="(subMenu, subIdx) in formatChildrenShowMenu(menu.children)"
            :key="subIdx"
            :class="{
              subMenu_item: true,
              subMenu_item_active: activeSubMenuIndex === subIdx && activeMenuIndex === index
            }"
            @click="checkSubMenu(menu, subMenu, subIdx)"
          >
            <div>{
   
   { subMenu.title }}</div>
          </div>
        </div>
        <div
          :class="{
            menu_item: true,
            menu_item_active: activeMenuIndex === index
          }"
          @click="checkMenu(menu, index)"
        >
          <div>{
   
   { menu.title }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import microApp, { getActiveApps } from '@micro-zoe/micro-app'
import { menuList, routeNameList, hashNameList } from './data'

const router = useRouter()

let activeRouter = 0 // 当前激活菜单的 index

let activeMenuIndex = ref(0)
let activeSubMenuIndex = ref(null)
let mouseenterIndex = ref(null)

const formatChildrenShowMenu = (subMenu) => {
  return subMenu.filter((item) => item.isShow)
}

const checkSubMenu = (menu, subMenu, index) => {
  activeSubMenuIndex.value = index
  const { appName } = menu
  const { route } = subMenu
  select(route, subMenu, appName)
}

const checkMenu = (item, index) => {
  activeMenuIndex.value = index
  activeSubMenuIndex.value = null
  const { appName, route } = item
  const subMenu = item.children
  select(route, subMenu, appName)
}

const mouseenter = (subMenu, index) => {
  let show = subMenu.filter((item) => item.isShow).length > 0
  if (show) {
    mouseenterIndex.value = index
  }
}

const mouseleave = () => {
  mouseenterIndex.value = null
}

// 根据url地址获取选中菜单
const getActiveIndex = () => {
  // location.pathname的值通常为:/main/vite1/page2,我们只取`/vite1/page2`
  const pathArr = location.pathname.match(/\/app-.+/)
  activeRouter = pathArr ? pathArr[0].replace(/\/$/, '') : '/'
  activeMenuIndex.value = menuList.findIndex((item) => item.route === activeRouter)

  let hash = ''
  if (location.hash) {
    hash = location.hash.split('?')[0]
  }
  console.log('pathArr', location.pathname, hash, activeRouter)
  // 兼容 vite 子应用,因为它们是hash路由
  if (routeNameList.includes(activeRouter) && hashNameList.includes(hash.split('/')[1])) {
    activeRouter += hash.replace(/^#/, '')
    let subMenu = menuList[activeMenuIndex.value].children
    activeSubMenuIndex.value = subMenu.findIndex((item) => item.route === activeRouter)
  }

  // 去除斜线后缀,如:/app-x/ 转换为 /app-x
  if (activeRouter !== '/') {
    activeRouter = activeRouter.replace(/\/$/, '')
  }

  return activeRouter
}

// 用户点击菜单时控制基座应用跳转
const select = (index, subMenu, appName) => {
  // 因为 vite 子应用是hash路由,所以需要传递hash值
  console.log('index', index, appName)
  let hash = null
  let path = index
  if (path.split('/').length > 2) {
    const pathArr = index.split('/')
    path = '/' + pathArr[1]
    hash = '/' + pathArr[2]
  } else {
    let show = subMenu.filter((item) => item.isShow).length > 0
    if (show) {
      activeSubMenuIndex.value = 0
    }
  }
  // 获取子应用appName
  // 控制基座跳转页面,并渲染子应用
  /**
   * 当子应用还未渲染,通过基座控制路由跳转,子应用在初始化时会自己根据url渲染对应的页面
   * 当子应用已经渲染,则直接控制子应用进行内部跳转
   *
   * getActiveApps: 用于获取正在运行的子应用
   */
  if (!getActiveApps().includes(appName)) {
    // child-vite 子应用为hash路由,这里拼接一下hash值
    hash && (path += `/#${hash}`)
    // 主应用跳转
    router.push(path)
  } else {
    let childPath = null
    // child-vite 子应用是hash路由,hash值就是它的页面地址,这里单独处理
    if (hash) {
      childPath = hash
    } else {
      // path的值形式如:/app-xxx/page2,这里/app-xxx/page2才是页面地址,所以我们需要将/app-xxx部分删除
      childPath = path.replace(/^\/app-[^/]+/, '')
      !childPath && (childPath = '/') // 防止地址为空
    }
    // 主应用通过下发data数据控制子应用跳转
    microApp.setData(appName, { path: childPath })
  }
}

onMounted(() => {
  getActiveIndex()
  microApp.addGlobalDataListener((data) => {
    // console.log("全局数据:", data);
    getActiveIndex()
  })
  window.addEventListener('popstate', () => getActiveIndex())
})
</script>

<style lang="scss" scoped>
#HeaderNav {
  width: 100%;
  height: 60px;
  display: flex;
  align-items: center;
}
.menu_item,
.subMenu_item {
  width: 166px;
  height: 60px;
  font-size: 20px;
  font-family: MiSans-Demibold, MiSans;
  font-weight: 600;
  background: rgba(29, 29, 31, 0.05);
  border-radius: 4px 4px 4px 4px;
  color: rgba(29, 29, 31, 0.66);
  letter-spacing: 1px;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: all 0.2s;
  backdrop-filter: blur(9px);
  cursor: pointer;
}
.menu_list {
  position: relative;
  display: flex;
  align-items: center;
  .menu_item_box {
    position: relative;
    display: flex;
    align-items: flex-end;
  }

  .menu_item {
    position: relative;
    margin: 0 10px;
  }
  .menu_item_active,
  .subMenu_item_active {
    background: #00a8ff;
    color: #fafafb;
  }
  .subMenu_list {
    position: absolute;
    top: 60px;
    left: 50%;
    transform: translate(-50%);
    width: auto;
    height: 60px;
    display: flex;
    .subMenu_item {
      width: 166px;
      height: 60px;
      margin: 0 10px;
    }
  }
}
</style>

路由改变菜单高亮改变

  • 子应用中全局监听路由改变事件
const route = useRoute();

const onRouteChange = () => {
    
    
  if (window.eventCenterForAppNameVite) {
    
    
    // 发送全局数据,通知侧边栏修改菜单展示
    window.eventCenterForAppNameVite.setGlobalData({
    
     name: "child-vite1" });
  }
};

watch(
  () => route.name,
  () => {
    
    
    onRouteChange();
  }
);

demo地址

项目安装启动

  • 分别进入 vite_vue3_main 和 vite_vue3_vite1 两个文件夹
  • 执行 yarn 或者 npm install 安装依赖
  • 执行 yarn dev 或者 npm run dev 运行项目
  • 执行 yarn build 或者 npm run build 打包

打包部署

  • 主应用生成的打包文件是main文件夹,放到 nginx 的html文件夹中
  • 子应用生成的打包文件是vite1文件夹,放到 nginx 的child文件夹中

nginx启动

  • 双击 nginx.exe
  • 打开项目127.0.0.1/main/
  • 配置文件是 conf 文件夹中的 nginx.conf 文件
  • 修改完配置文件,执行 ./nginx -s reload 重启nginx

作者:白马不是马

猜你喜欢

转载自blog.csdn.net/ekcchina/article/details/131299601