vue3 主应用

main

  • main.ts
import { createApp } from "vue"
import App from "./App"
import router from "./router"
import "@/micro"
import { store } from "@/store"

createApp(App).use(router).use(store).mount("#app")
  • micro/app.ts
import actions from "@/shared/action"
import { store } from "@/store"

const getActiveRule = (hash: string) => (location: { hash: string }) =>
  location.hash.startsWith(hash)

const microApps: {
  name: string
  entry: string
  activeRule: string
}[] = [
  {
    name: "subapp",
    entry: "http://127.0.0.1:8082",
    activeRule: "#/subapp",
  },
]

export const apps = microApps.map((item) => {
  return {
    ...item,
    container: "#subapp", // 子应用挂载的div
    activeRule: getActiveRule(item.activeRule),
    props: {
      routerBase: item.activeRule, // 下发基础路由
      // getGlobalState: actions.getGlobalState, // 下发getGlobalState方法
      store,
    },
  }
})
  • 注意上面的name 和activeRule 必须和 router 里的一级路由的path 和name 保持一致
 {
    name: "subapp",
    entry: "http://127.0.0.1:8082",
    activeRule: "#/subapp",
  },

正确用法


// 主应用
{
path: "/subapp", // path 和 activeRule 保持一致
name: "subapp", // name 和上面的name保持一致
children: [{
  {
    path: "/subapp/a",
    ...
  }
}]
}

错误用法

{
path: "/test", // path 和 activeRule 不一样
name: "test", // name 和上面的name不一样
children: [{
  {
    path: "/test/a",
    ...
  }
}]
}

  • micro/index.ts
import {
  addGlobalUncaughtErrorHandler,
  registerMicroApps,
  start,
} from "qiankun"
import Nprogress from "nprogress"
import "nprogress/nprogress.css"
import { ElLoading } from "element-plus"
import { apps } from "./app"
let loadingInstance: ReturnType<typeof ElLoading.service>

registerMicroApps(apps, {
  beforeLoad: (app) => {
    Nprogress.start()
    loadingInstance = ElLoading.service()
    console.log("before load", app.name)
    return Promise.resolve()
  },
  afterMount: (app) => {
    Nprogress.done()
    loadingInstance.close()
    console.log("after mount", app.name)
    return Promise.resolve()
  },
})

addGlobalUncaughtErrorHandler(() => {
  loadingInstance.close()
  console.log("子应用加载失败,请检查应用是否可运行")
})

export { start }
  • router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';

export const routes: RouteRecordRaw[] = [
  // 基座应用自己的路由
  {
    path: '/login',
    component: () => import('../views/Login'),
  },
  {
    path: "",
    component: () => import('../views/Layout'),
    redirect: "/home",
    children: [
      {
        path: "/home",
        name: "首页",
        component: () => import('../views/Home'),
      },
      {
        path: "/subapp",
        component: () => import('../views/SubApp'),
        name: "子应用",
        children: [
          {
            path: "/subapp/child1",
            name: "child1",
            // 这里的二级菜单组件是没有意义的,但是 ts 必须要传一个 component,所以随便写一个就行,为了解决 ts 报错
            component: import("../views/Portal")
          },
          {
            path: "/subapp/child2",
            name: "child2",
            component: import("../views/Portal")
          }
        ]
      }
    ]
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;
  • store/index.ts
import actions from "@/shared/action"
import { createStore } from "vuex"

export const store = createStore({
  state() {
    return {
      token: "111",
      name: "",
    }
  },
  mutations: {
    setToken(state, token) {
      state.token = token
      actions.setGlobalState({ ...state, token })
      actions.offGlobalStateChange()
    },
    setName(state, name) {
      state.name = name
    },
  },
})
  • App.tsx
import { defineComponent } from "vue"
import "element-plus/dist/index.css"
import "./App.less"
import { useStore } from "vuex"
const App = defineComponent({
  setup: (props, context) => {
    const store = useStore()

    return () => (
      <>
        <div>token: {store.state.token}</div>
        <router-view />
      </>
    )
  },
})
export default App

这里需要注意如果你的子应用是二级菜单那么你的容器 id,必须在根 Layout 组件或者一级路由组件里声明,而且容器id和子应用调用必须写在同一个组件里,否则打包后无效,因为我们可能有多个子应用,所以我直接在 Layout 里写,这样就避免我每次都要写一遍容器id和调用一次start

  • views/Layout
import { defineComponent, onMounted } from "vue"
import { ElContainer, ElAside, ElMain, ElHeader } from "element-plus"
import s from "./index.module.less"
import { SlideBar } from "../../components/SliderBar"
import { start } from "qiankun"
import { store } from "@/store"
const Layout = defineComponent({
  setup: (props, context) => {
    const handleClick = () => {
      store.commit("setToken", "2222")
    }
    onMounted(() => {
      if (!window.qiankunStarted) {
        window.qiankunStarted = true
        start({
          sandbox: {
            experimentalStyleIsolation: true,
          },
        })
      }
    })
    return () => (
      <div class={[s.layoutWrapper, "common-layout"]} onClick={handleClick}>
        <ElContainer>
          <ElHeader class={s.header}>header</ElHeader>
          <ElContainer>
            <ElAside width='240px' class={s.aside}>
              <SlideBar />
            </ElAside>
            <ElMain class={s.main}>
                <div id="subapp" />
              <router-view />
            </ElMain>
          </ElContainer>
        </ElContainer>
      </div>
    )
  },
})
export default Layout
  • SlideBar/index.tsx
import { ElMenu, ElMenuItem, ElSubMenu } from 'element-plus';
import { defineComponent } from 'vue';
import { RouteRecordRaw } from 'vue-router';
import { routes } from '../../router';
import s from './index.module.less'
export const SlideBar = defineComponent({
  setup: (props, context) => {
    const filterRoutes = routes.filter((route)=> route.path === "")[0]?.children

    const renderItem = (route: RouteRecordRaw) => {
      return (
        <>
          {route.path ? (
            <ElMenuItem index={route.path}>{route.name}</ElMenuItem>
          ) : null} 
        </>
      )
    }

    return () => (
      <div class={s.slideBarWrapper}>
        <ElMenu
          activeTextColor='#ffd04b' 
          backgroundColor='#545c64'
          class={"el-menu-vertical-demo"}
          textColor="#fff"
          router
        >
          {
            filterRoutes!.map(route => (
              <>
                {
                  route.children && !!route.children.length ? (
                    <ElSubMenu index={route.path}
                      v-slots={
                        {
                          title: () => (<span>{route.name}</span>)
                        }
                      } 
                    >
                        {
                          route.children.map(child => renderItem(child))
                        }
                      </ElSubMenu>
                  ) : renderItem(route)
                } 
              </>
            ))
          }
        </ElMenu>
      </div>
    )
  }
});
  • views/Portal/index.tsx
import { start } from "@/micro"
import { defineComponent, onMounted } from "vue"

const Portal = defineComponent({
  setup: (props, context) => {
    return () => <div  />
  },
})
export default Portal
  • views/SubApp/index.tsx
import { start } from 'qiankun';
import { defineComponent, onMounted } from 'vue';
const SubApp = defineComponent({
  setup: (props, context) => {
    return () => (
      <>
      <router-view />
      </>
    )
  }
});
export default SubApp;
  • vite.config.ts
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import vueJsx from "@vitejs/plugin-vue-jsx"
import { resolve } from "path"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      transformOn: true,
      mergeProps: true,
    }),
  ],
  resolve: {
    extensions: [".ts", ".tsx", ".json", ".js", ".vue"],
    alias: {
      "@": resolve("src"),
    },
  },
})

子应用

  • main.ts
import { createPinia } from "pinia"
import {
  renderWithQiankun,
  qiankunWindow,
  QiankunProps,
} from "vite-plugin-qiankun/dist/helper"
import { createApp, App as VueApp } from "vue"
import App from "./App"
import { router } from "./router"
import { useUserStore } from "./store/user"

let app: VueApp<Element>

const render = (props: QiankunProps | { container: string }) => {
  const { container } = props
  app = createApp(App)
  app
    .use(router)
    .use(createPinia())
    .mount(
      container
        ? container instanceof HTMLElement
          ? container.querySelector("#app")!
          : container
        : document.getElementById("app")!
    )
}

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  render({ container: "#app" })
} else {
  renderWithQiankun({
    mount(props) {
      console.log(props, "sss")
      const { store: parentStore, onGlobalStateChange, setGlobalState } = props
      console.log(1)
      onGlobalStateChange((state, prev) => {
        console.log(state, prev)
        store.setToken(state.token)
      })
      console.log("--mount")
      render(props)
      const store = useUserStore()
      store.setToken(parentStore.state.token)
    },
    bootstrap() {
      console.log("--bootstrap")
    },
    update() {
      console.log("--update")
    },
    unmount() {
      console.log("--unmount")
      app?.unmount()
    },
  })
}
  • vite.config.ts
iimport vueJsx from "@vitejs/plugin-vue-jsx"
import { defineConfig, loadEnv } from "vite"
import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
import qiankun from "vite-plugin-qiankun"

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  // base: "http://localhost:3002/",
  const root = process.cwd()
  const env = loadEnv(mode, root)
  const { VITE_PUBLIC_PATH, VITE_DROP_CONSOLE } = env
  return {
    base: mode !== "production" ? "/" : "http://127.0.0.1:8082/", // 生产环境需要指定域名
    server: {
      port: 8082,
      cors: true,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      hmr: true
    },
    resolve: {
      extensions: [".ts", ".tsx", ".json", ".js", ".vue"],
      alias: {
        "@": resolve("src"),
      },
    },
    plugins: [
      vue(),
      vueJsx({
        transformOn: true,
        mergeProps: true,
      }),
      qiankun("subapp", {
        useDevMode: mode !== "production",
      }),
    ],
  }
})
  • router
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper"
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"

const routes: RouteRecordRaw[] = [
  {
    path: "/subApp",
    component: () => import("@/views/Layout"),
    name: "Layout",
    children: [
      {
        path: "a",
        component: () => import("@/views/A"),
      },
    ],
  },
]

export const router = createRouter({
  history: createWebHashHistory(
    qiankunWindow.__POWERED_BY_QIANKUN__ ? "/subapp" : "/"
  ),
  routes,
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容