3.1 安装依赖
javascript
cd ../vue-app
npm install vite-plugin-qiankun
3.2 配置 Vite vite.config.js
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入 qiankun 插件,用于支持 qiankun 微前端
import qiankun from 'vite-plugin-qiankun'
export default defineConfig({
plugins: [
vue(),
// 配置 qiankun 插件
// 参数说明:
// - 'vue-app': 子应用名称,必须与主应用注册时的 name 一致
// - useDevMode: 开发模式,默认为 false,开发时设为 true
qiankun('vue-app', { useDevMode: true }),
],
server: {
port: 8081, // 子应用端口,必须与主应用注册时的 entry 端口一致
headers: {
// 允许跨域,主应用需要加载子应用的资源
'Access-Control-Allow-Origin': '*',
},
},
})
3.3 创建子应用入口 src/main.ts
javascript
/**
* Vue 子应用入口文件
* 负责:导出 qiankun 生命周期钩子、渲染应用
*/
import { createApp, h } from 'vue'
import App from './App.vue'
import './style.css'
// 保存 Vue 应用实例,用于卸载时清理
let app: ReturnType<typeof createApp> | null = null
/**
* qiankun 生命周期钩子:bootstrap
* 在子应用第一次加载时调用,只会调用一次
*/
export async function bootstrap() {
console.log('[Vue子应用] bootstrap 生命周期')
}
/**
* qiankun 生命周期钩子:mount
* 在子应用挂载时调用
* @param props qiankun 传递的参数
*/
export async function mount(props: any) {
console.log('[Vue子应用] mount 生命周期', props)
// 获取挂载容器
// 如果在 qiankun 环境下,使用 props.container
// 如果独立运行,使用 document.getElementById('app')
const mountTarget = props?.container
? props.container.querySelector('#vue-app-root') || props.container
: document.getElementById('app')
// 确保挂载容器存在
if (!mountTarget) {
console.error('[Vue子应用] 挂载容器不存在')
return
}
// 在 qiankun 环境下,清空容器内容,避免重复挂载
if (props?.container) {
props.container.innerHTML = '<div id="vue-app-root"></div>'
}
// 创建并挂载 Vue 应用
app = createApp({
// 使用 h 函数渲染根组件
render: () => h(App),
})
// 挂载到目标容器
app.mount(mountTarget)
console.log('[Vue子应用] 已挂载')
}
/**
* qiankun 生命周期钩子:unmount
* 在子应用卸载时调用
*/
export async function unmount() {
console.log('[Vue子应用] unmount 生命周期')
// 卸载 Vue 应用,避免内存泄漏
if (app) {
app.unmount()
app = null
}
console.log('[Vue子应用] 已卸载')
}
/**
* 如果不是在 qiankun 环境下,独立运行子应用
*/
if (!(window as any).__POWERED_BY_QIANKUN__) {
// 直接创建并挂载应用
createApp(App).mount('#app')
}
3.4 创建 Vue 子应用组件 src/App.vue
javascript
<template>
<div class="vue-app">
<h2>🟢 Vue 3 子应用</h2>
<p>这是一个基于 Vue 3 的微前端子应用</p>
<!-- 导航按钮 -->
<div class="navigation">
<button @click="goToMain">🏠 返回主应用</button>
<button @click="goToReact">🎯 跳转到 React 子应用</button>
</div>
<!-- 全局状态展示 -->
<div class="state-panel">
<h3>📊 全局状态(来自主应用)</h3>
<div class="state-item">
<span class="label">当前用户:</span>
<span class="value">{{ globalState.user?.name || '未登录' }}</span>
</div>
<div class="state-item">
<span class="label">用户角色:</span>
<span class="value">{{ globalState.user?.role || '普通用户' }}</span>
</div>
<div class="state-item">
<span class="label">计数器:</span>
<span class="value highlight">{{ globalState.count }}</span>
</div>
<div class="state-item">
<span class="label">子应用消息:</span>
<span class="value">{{ globalState.message || '暂无消息' }}</span>
</div>
</div>
<!-- 子应用操作区 -->
<div class="action-panel">
<h3>🎮 Vue 子应用操作(通信演示)</h3>
<div class="action-item">
<label>发送消息给其他应用:</label>
<input
v-model="message"
placeholder="输入消息"
/>
<button @click="sendMessage">发送消息</button>
</div>
<div class="action-buttons">
<button @click="incrementCount">增加计数器(同步到主应用)</button>
<button @click="resetCount">重置计数器</button>
<button @click="showNotify">调用主应用通知</button>
</div>
</div>
<div class="features">
<div class="feature-card">
<h3>📦 技术栈</h3>
<ul>
<li>Vue 3 + Composition API</li>
<li>Vite 构建工具</li>
<li>TypeScript 支持</li>
</ul>
</div>
<div class="feature-card">
<h3>🔧 特性</h3>
<ul>
<li>独立开发和部署</li>
<li>样式隔离</li>
<li>全局状态共享</li>
</ul>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
/**
* 从 window 对象获取主应用的 actions
* 解决 props 传递失败的问题
*/
const getActions = () => {
return (window as any).__MICRO_APP_ACTIONS__
}
const actions = getActions()
console.log('[Vue子应用] actions:', actions)
// 全局状态
const globalState = ref(actions?.getGlobalState?.() || {})
const message = ref('')
let unsubscribe: (() => void) | undefined
/**
* 返回主应用
*/
const goToMain = () => {
if (actions?.push) {
actions.push('/')
} else {
window.location.href = '/'
}
}
/**
* 跳转到 React 子应用
*/
const goToReact = () => {
if (actions?.push) {
actions.push('/react-app')
} else {
window.location.href = '/react-app'
}
}
/**
* 发送消息给其他应用
*/
const sendMessage = () => {
if (actions?.setGlobalState && message.value) {
actions.setGlobalState({ message: `[Vue] ${message.value}` })
actions.showNotification('消息已发送', 'success')
message.value = ''
}
}
/**
* 增加计数器
*/
const incrementCount = () => {
if (actions?.incrementCount) {
actions.incrementCount()
}
}
/**
* 重置计数器
*/
const resetCount = () => {
if (actions?.resetCount) {
actions.resetCount()
}
}
/**
* 调用主应用通知
*/
const showNotify = () => {
if (actions?.showNotification) {
actions.showNotification('这是 Vue 子应用发起的通知!', 'info')
}
}
onMounted(() => {
console.log('[Vue子应用] 组件已挂载')
// 初始化全局状态
if (actions?.getGlobalState) {
globalState.value = actions.getGlobalState()
}
// 订阅全局状态变更
if (actions?.onGlobalStateChange) {
unsubscribe = actions.onGlobalStateChange((state) => {
globalState.value = state
})
}
})
onUnmounted(() => {
console.log('[Vue子应用] 组件已卸载')
// 取消订阅,避免内存泄漏
if (unsubscribe) {
unsubscribe()
}
})
</script>
<style scoped>
.vue-app {
padding: 24px;
}
.navigation {
display: flex;
gap: 12px;
margin-bottom: 24px;
}
.navigation button {
padding: 10px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.navigation button:hover {
background: #40a9ff;
}
.state-panel, .action-panel {
background: #f5f5f5;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
}
.state-item {
display: flex;
margin-bottom: 12px;
}
.label {
font-weight: bold;
width: 120px;
}
.value {
color: #1890ff;
}
.value.highlight {
font-size: 18px;
font-weight: bold;
}
.action-item {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.action-item input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.action-item button {
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.action-buttons {
display: flex;
gap: 12px;
}
.action-buttons button {
padding: 10px 20px;
background: #52c41a;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.action-buttons button:hover {
background: #73d13d;
}
.features {
display: flex;
gap: 24px;
}
.feature-card {
flex: 1;
background: #f5f5f5;
border-radius: 8px;
padding: 24px;
}
.feature-card ul {
list-style: none;
padding: 0;
}
.feature-card li {
padding: 8px 0;
border-bottom: 1px solid #ddd;
}
</style>