配置好了vue2的子应用接入

This commit is contained in:
2026-06-20 23:42:22 +08:00
commit 2499043df4
19 changed files with 2648 additions and 0 deletions

101
src/App.vue Normal file
View File

@@ -0,0 +1,101 @@
<template>
<div id="main-app">
<header class="app-header">
<div class="logo" @click="$router.push('/')">
<h1>MicroApp 主应用</h1>
</div>
<nav class="nav-links">
<router-link to="/home">首页</router-link>
<router-link to="/child-app">Vue2 子应用</router-link>
</nav>
</header>
<main class="app-main">
<router-view />
</main>
</div>
</template>
<script setup lang="ts">
// 根组件 — 提供全局布局(头部导航 + 内容区)
</script>
<style>
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Microsoft YaHei', sans-serif;
color: #333;
background: #f5f6f7;
}
#app {
height: 100%;
}
#main-app {
display: flex;
flex-direction: column;
height: 100%;
}
/* 头部导航 */
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
flex-shrink: 0;
}
.logo {
cursor: pointer;
}
.logo h1 {
font-size: 18px;
font-weight: 600;
letter-spacing: 1px;
}
.nav-links {
display: flex;
gap: 8px;
}
.nav-links a {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
padding: 6px 16px;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s ease;
}
.nav-links a:hover {
color: #fff;
background: rgba(255, 255, 255, 0.15);
}
.nav-links a.router-link-active {
color: #fff;
background: rgba(255, 255, 255, 0.2);
font-weight: 500;
}
/* 主内容区 */
.app-main {
flex: 1;
overflow: auto;
}
</style>

51
src/config/subApps.ts Normal file
View File

@@ -0,0 +1,51 @@
/**
* 子应用配置列表
*
* 后续对接 Vue 2 子应用时,在此处添加/修改配置即可
*
* @see https://jd-opensource.github.io/micro-app/docs.html#/configure
*/
export interface SubAppConfig {
/** 应用名称,全局唯一,字母开头 */
name: string
/** 子应用地址(开发环境填写 devServer 地址) */
url: string
/** 基座分配给子应用的路由前缀 */
baseroute: string
/** 是否使用 iframe 沙箱Vite 子应用必须开启Webpack 子应用可选) */
iframe?: boolean
/** 是否保活子应用,避免重复加载 */
keepAlive?: boolean
/** 路由模式native | native-scope */
routerMode?: 'native' | 'native-scope'
/** 是否禁用样式隔离 */
disableScopecss?: boolean
/** 是否禁用沙箱 */
disableSandbox?: boolean
}
// ============================================================
// 当前对接的子应用列表
// 后续对接 Vue 2 项目时,修改 url 为实际的子应用地址
// ============================================================
export const subApps: SubAppConfig[] = [
{
name: 'vue2-app',
// TODO: 替换为你的 Vue 2 子应用实际地址
url: 'http://localhost:5173/',
baseroute: '/child-app',
// Vite 子应用必须开启 iframe 模式
// with 沙箱的 new Function() 不支持 ES Module 的 import/export 语法)
iframe: true,
keepAlive: true,
routerMode: 'native'
}
]
/**
* 根据名称查找子应用配置
*/
export function getSubAppConfig(name: string): SubAppConfig | undefined {
return subApps.find((app) => app.name === name)
}

36
src/main.ts Normal file
View File

@@ -0,0 +1,36 @@
import { createApp } from 'vue'
import microApp from '@micro-zoe/micro-app'
import App from './App.vue'
import router from './router'
// 启动 micro-app 微前端框架
// https://jd-opensource.github.io/micro-app/docs.html#/start
microApp.start({
// 预加载:当浏览器空闲时预加载子应用,加快首屏速度
preFetchApps: [],
// 当子应用未匹配到路由时的默认行为
// 'default-page' 或自定义地址
// defaultPage: '',
// 全局生命周期
lifeCycles: {
created(_e, appName) {
console.log(`[micro-app] 子应用 ${appName} 被创建`)
},
beforemount(_e, appName) {
console.log(`[micro-app] 子应用 ${appName} 即将挂载`)
},
mounted(_e, appName) {
console.log(`[micro-app] 子应用 ${appName} 挂载完成`)
},
unmount(_e, appName) {
console.log(`[micro-app] 子应用 ${appName} 已卸载`)
},
error(_e, appName) {
console.error(`[micro-app] 子应用 ${appName} 加载错误:`, _e)
}
}
})
const app = createApp(App)
app.use(router)
app.mount('#app')

29
src/router/index.ts Normal file
View File

@@ -0,0 +1,29 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: () => import('@/views/Home.vue')
},
{
// 子应用路由 — `:page*` 通配符匹配子应用内部所有路由
// 例如:/child-app、/child-app/page1、/child-app/page2/xxx
path: '/child-app/:page*',
name: 'childApp',
component: () => import('@/views/ChildApp.vue')
}
]
const router = createRouter({
// history 模式 — 主应用和子应用都使用 history 模式
// 通过 baseroute 区分路由归属
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router

140
src/views/ChildApp.vue Normal file
View File

@@ -0,0 +1,140 @@
<template>
<div class="child-app-wrapper">
<!--
micro-app 子应用容器
使用 key 绑定 url url 变化时重新加载子应用
-->
<micro-app
:key="currentApp.url"
:name="currentApp.name"
:url="currentApp.url"
:baseroute="currentApp.baseroute"
:iframe="currentApp.iframe ?? false"
:keep-alive="currentApp.keepAlive ?? true"
:router-mode="currentApp.routerMode ?? 'native'"
:disable-scopecss="currentApp.disableScopecss ?? false"
:disable-sandbox="currentApp.disableSandbox ?? false"
@created="onCreated"
@beforemount="onBeforeMount"
@mounted="onMounted"
@unmount="onUnmount"
@error="onError"
>
<!-- 子应用加载中的占位内容 -->
<div class="loading-placeholder">
<div class="spinner"></div>
<p>正在加载子应用 {{ currentApp.name }}...</p>
<p class="loading-hint">
请确保子应用已启动<code>{{ currentApp.url }}</code>
</p>
</div>
</micro-app>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { subApps } from '@/config/subApps'
const route = useRoute()
/**
* 当前正在使用的子应用配置
*
* 这里根据路由前缀匹配对应的子应用。
* 如果只有一个子应用,直接使用第一个配置;
* 多子应用时,需要根据 route.path 匹配 baseroute。
*/
const currentApp = computed(() => {
// 根据当前路径匹配子应用
const matched = subApps.find((app) =>
route.path.startsWith(app.baseroute)
)
return matched || subApps[0] || { name: 'unknown', url: '', baseroute: '/' }
})
// ============================================
// micro-app 生命周期回调
// ============================================
function onCreated() {
console.log(`[ChildApp] 子应用 ${currentApp.value.name} 被创建`)
}
function onBeforeMount() {
console.log(`[ChildApp] 子应用 ${currentApp.value.name} 即将挂载`)
}
function onMounted() {
console.log(`[ChildApp] 子应用 ${currentApp.value.name} 挂载完成`)
}
function onUnmount() {
console.log(`[ChildApp] 子应用 ${currentApp.value.name} 已卸载`)
}
function onError(error: Error) {
console.error(`[ChildApp] 子应用 ${currentApp.value.name} 加载出错:`, error)
}
</script>
<style scoped>
.child-app-wrapper {
width: 100%;
height: 100%;
position: relative;
}
/* 确保 micro-app 标签占满容器 */
.child-app-wrapper :deep(micro-app) {
width: 100%;
height: 100%;
display: block;
}
/* 加载占位 */
.loading-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 400px;
color: #999;
}
.spinner {
width: 36px;
height: 36px;
border: 3px solid #e0e0e0;
border-top-color: #667eea;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-placeholder p {
font-size: 14px;
margin-top: 4px;
}
.loading-hint {
font-size: 12px !important;
color: #bbb;
margin-top: 8px !important;
}
.loading-hint code {
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
}
</style>

167
src/views/Home.vue Normal file
View File

@@ -0,0 +1,167 @@
<template>
<div class="home-page">
<div class="hero">
<h2>欢迎使用 MicroApp 微前端主应用</h2>
<p class="desc">
基于 <code>@micro-zoe/micro-app</code> + Vue 3 + Vite 搭建
</p>
</div>
<div class="sub-app-list">
<h3>已接入的子应用</h3>
<div class="cards">
<div
v-for="app in subApps"
:key="app.name"
class="sub-app-card"
@click="$router.push(app.baseroute)"
>
<div class="card-icon">📦</div>
<div class="card-info">
<h4>{{ app.name }}</h4>
<p class="card-url">{{ app.url }}</p>
<p class="card-route">路由前缀{{ app.baseroute }}</p>
</div>
<div class="card-arrow"></div>
</div>
</div>
<div v-if="subApps.length === 0" class="empty">
<p>暂未接入任何子应用</p>
<p class="hint">请在 <code>src/config/subApps.ts</code> 中配置子应用信息</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { subApps } from '@/config/subApps'
</script>
<style scoped>
.home-page {
max-width: 960px;
margin: 0 auto;
padding: 40px 24px;
}
.hero {
text-align: center;
padding: 48px 0 40px;
}
.hero h2 {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero .desc {
margin-top: 12px;
color: #666;
font-size: 15px;
}
.hero code {
background: #e8e8e8;
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
}
.sub-app-list {
margin-top: 24px;
}
.sub-app-list h3 {
font-size: 16px;
color: #444;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.cards {
display: flex;
flex-direction: column;
gap: 12px;
}
.sub-app-card {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.sub-app-card:hover {
border-color: #667eea;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.15);
transform: translateY(-1px);
}
.card-icon {
font-size: 36px;
flex-shrink: 0;
}
.card-info {
flex: 1;
}
.card-info h4 {
font-size: 16px;
font-weight: 600;
color: #333;
}
.card-url {
font-size: 13px;
color: #999;
margin-top: 4px;
}
.card-route {
font-size: 12px;
color: #667eea;
margin-top: 2px;
}
.card-arrow {
font-size: 20px;
color: #ccc;
flex-shrink: 0;
transition: color 0.2s;
}
.sub-app-card:hover .card-arrow {
color: #667eea;
}
.empty {
text-align: center;
padding: 48px 0;
color: #999;
}
.empty .hint {
margin-top: 8px;
font-size: 13px;
}
.empty code {
background: #e8e8e8;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
}
</style>

19
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// micro-app 全局变量声明
declare global {
interface Window {
__MICRO_APP_ENVIRONMENT__?: boolean
__MICRO_APP_NAME__?: string
__MICRO_APP_BASE_ROUTE__?: string
microApp?: any
}
}
export {}