主应用子应用globalData通信
This commit is contained in:
37
src/main.ts
37
src/main.ts
@@ -2,6 +2,7 @@ import { createApp } from 'vue'
|
||||
import microApp from '@micro-zoe/micro-app'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { sharedCount, addLog } from './stores/counterStore'
|
||||
|
||||
// 启动 micro-app 微前端框架
|
||||
// https://jd-opensource.github.io/micro-app/docs.html#/start
|
||||
@@ -21,15 +22,51 @@ microApp.start({
|
||||
},
|
||||
mounted(_e, appName) {
|
||||
console.log(`[micro-app] 子应用 ${appName} 挂载完成`)
|
||||
// 子应用挂载后会通过 addGlobalDataListener(autoTrigger=true) 自动拿到当前值
|
||||
addLog(`子应用 ${appName} 挂载完成(将通过 globalData 自动同步)`, 'main')
|
||||
},
|
||||
beforeshow(_e, appName) {
|
||||
console.log(`[micro-app] 子应用 ${appName} 即将显示(keepAlive 恢复)`)
|
||||
addLog(`子应用 ${appName} 恢复显示(globalData 自动同步)`, 'main')
|
||||
},
|
||||
unmount(_e, appName) {
|
||||
console.log(`[micro-app] 子应用 ${appName} 已卸载`)
|
||||
},
|
||||
error(_e, appName) {
|
||||
console.error(`[micro-app] 子应用 ${appName} 加载错误:`, _e)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// 全局通信(globalData 方案)
|
||||
//
|
||||
// globalData 是 micro-app 内置的全局状态池:
|
||||
// - 主应用 / 任何子应用 都可以 setGlobalData / getGlobalData
|
||||
// - 任何一方调用 setGlobalData → 其他所有方的 addGlobalDataListener 都触发
|
||||
// - 子→子通信不再需要主应用中转!
|
||||
//
|
||||
// 架构变化:
|
||||
// 之前:setData(per-app) + addDataListener(per-app) + 主应用中转
|
||||
// 现在:setGlobalData(broadcast) + addGlobalDataListener(global)
|
||||
// ============================================================
|
||||
|
||||
// 初始化全局数据
|
||||
microApp.setGlobalData({ count: 0, from: 'main' })
|
||||
|
||||
// 单一监听器 — 接收任何子应用发来的 globalData 更新
|
||||
microApp.addGlobalDataListener((data: Record<string, any>) => {
|
||||
if (data.count !== undefined) {
|
||||
const prev = sharedCount.value
|
||||
sharedCount.value = data.count
|
||||
const from = data.from || 'unknown'
|
||||
if (from !== 'main') {
|
||||
// 子应用发起的更新
|
||||
addLog(`${from} → globalData: 计数 ${prev} → ${data.count}`, 'child')
|
||||
}
|
||||
}
|
||||
})
|
||||
// 注意:不需要 autoTrigger,main.ts 是入口文件,sharedCount 默认就是 0
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
|
||||
50
src/stores/counterStore.ts
Normal file
50
src/stores/counterStore.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 共享计数器 Store
|
||||
*
|
||||
* 使用模块级 ref,状态绑定到模块单例而不是 Vue 组件实例。
|
||||
* 即使 Home.vue 被卸载(路由切走),计数器也不会重置。
|
||||
*
|
||||
* 通信流程:
|
||||
* 主应用 ←→ 子应用(双向,主应用为消息中转站)
|
||||
* 子←→子:子A → dispatch → 主应用 → relay setData → 子B
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface LogEntry {
|
||||
time: string
|
||||
msg: string
|
||||
type: 'main' | 'child' | 'relay'
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 模块级状态 — 不随组件卸载而销毁
|
||||
// ============================================================
|
||||
|
||||
/** 共享计数器 */
|
||||
export const sharedCount = ref(0)
|
||||
|
||||
/** 通信日志 */
|
||||
export const counterLogs = ref<LogEntry[]>([])
|
||||
|
||||
// ============================================================
|
||||
// 工具函数
|
||||
// ============================================================
|
||||
|
||||
/** 添加一条通信日志 */
|
||||
export function addLog(msg: string, type: 'main' | 'child' | 'relay' = 'main') {
|
||||
const now = new Date()
|
||||
const time = [
|
||||
String(now.getHours()).padStart(2, '0'),
|
||||
String(now.getMinutes()).padStart(2, '0'),
|
||||
String(now.getSeconds()).padStart(2, '0'),
|
||||
].join(':')
|
||||
counterLogs.value.push({ time, msg, type })
|
||||
if (counterLogs.value.length > 30) {
|
||||
counterLogs.value.shift()
|
||||
}
|
||||
}
|
||||
|
||||
// AddLog function but without reactivity side effects - for use in main.ts
|
||||
export function addSystemLog(msg: string, type: 'main' | 'child' | 'relay' = 'main') {
|
||||
addLog(msg, type)
|
||||
}
|
||||
@@ -74,11 +74,110 @@
|
||||
<p class="hint">请在 <code>src/config/subApps.ts</code> 中配置子应用信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================
|
||||
跨应用通信演示
|
||||
============================================ -->
|
||||
<div class="communication-demo">
|
||||
<div class="comm-header">
|
||||
📡 跨应用通信演示
|
||||
</div>
|
||||
<div class="comm-body">
|
||||
<div class="comm-counter">
|
||||
<span class="counter-label">共享计数器</span>
|
||||
<span class="counter-value" :class="{ changed: counterFlash }">{{ sharedCount }}</span>
|
||||
</div>
|
||||
<div class="comm-controls">
|
||||
<button class="btn-dec" @click="decrement">− 1</button>
|
||||
<button class="btn-inc" @click="increment">+ 1</button>
|
||||
<button class="btn-broadcast" @click="broadcast">
|
||||
📡 广播到所有子应用
|
||||
</button>
|
||||
</div>
|
||||
<div class="comm-log">
|
||||
<div class="log-title">📋 通信日志(主应用视角)</div>
|
||||
<div class="log-list" ref="logListRef">
|
||||
<p v-for="(log, i) in counterLogs" :key="i" class="log-item" :class="log.type">
|
||||
<span class="log-time">{{ log.time }}</span>
|
||||
<span class="log-msg">{{ log.msg }}</span>
|
||||
</p>
|
||||
<p v-if="counterLogs.length === 0" class="log-empty">暂无通信记录,点击按钮开始测试 👆</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, watch } from 'vue'
|
||||
import microApp from '@micro-zoe/micro-app'
|
||||
import { subApps } from '@/config/subApps'
|
||||
import { sharedCount, counterLogs, addLog } from '@/stores/counterStore'
|
||||
|
||||
// ============================================================
|
||||
// 计数器状态来自 counterStore(模块级单例,切换路由不丢失)
|
||||
// ============================================================
|
||||
|
||||
const counterFlash = ref(false)
|
||||
const logListRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// 本地管理的滚动同步函数,包装 store 的 addLog
|
||||
function localAddLog(msg: string, type: 'main' | 'child' | 'relay' = 'main') {
|
||||
addLog(msg, type)
|
||||
nextTick(() => {
|
||||
if (logListRef.value) {
|
||||
logListRef.value.scrollTop = logListRef.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function flashCounter() {
|
||||
counterFlash.value = true
|
||||
setTimeout(() => (counterFlash.value = false), 300)
|
||||
}
|
||||
|
||||
function doBroadcast(action: string) {
|
||||
// globalData 自动广播给所有子应用,不再需要指定 appName
|
||||
microApp.setGlobalData({ count: sharedCount.value, from: 'main' })
|
||||
localAddLog(`主 → globalData: ${action} → ${sharedCount.value}(广播到全部子应用)`, 'main')
|
||||
}
|
||||
|
||||
function increment() {
|
||||
sharedCount.value++
|
||||
flashCounter()
|
||||
doBroadcast(`+1`)
|
||||
}
|
||||
|
||||
function decrement() {
|
||||
sharedCount.value--
|
||||
flashCounter()
|
||||
doBroadcast(`−1`)
|
||||
}
|
||||
|
||||
function broadcast() {
|
||||
flashCounter()
|
||||
doBroadcast(`手动同步`)
|
||||
}
|
||||
|
||||
// addDataListener 已移至 main.ts 全局注册,避免组件重复挂载时重复监听
|
||||
|
||||
// 监听外部(main.ts / 子应用)对 sharedCount 的修改,触发 UI 动画
|
||||
watch(sharedCount, () => {
|
||||
flashCounter()
|
||||
})
|
||||
|
||||
// 监听外部(main.ts)新增的日志,自动滚动到底部
|
||||
watch(
|
||||
() => counterLogs.value.length,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
if (logListRef.value) {
|
||||
logListRef.value.scrollTop = logListRef.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -363,4 +462,179 @@ import { subApps } from '@/config/subApps'
|
||||
line-height: 1.8 !important;
|
||||
border: 1px dashed #ffcc80;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
跨应用通信演示区块样式
|
||||
============================================ */
|
||||
.communication-demo {
|
||||
margin-top: 32px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 2px solid #667eea;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.comm-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
padding: 12px 20px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.comm-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 计数器区域 */
|
||||
.comm-counter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: #f8f9ff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0f0;
|
||||
}
|
||||
|
||||
.counter-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.counter-value {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
color: #667eea;
|
||||
transition: transform 0.3s, color 0.3s;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.counter-value.changed {
|
||||
transform: scale(1.2);
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
/* 增减按钮 */
|
||||
.comm-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn-dec,
|
||||
.btn-inc,
|
||||
.btn-broadcast {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-dec {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.btn-dec:hover {
|
||||
background: #fca5a5;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.btn-inc {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.btn-inc:hover {
|
||||
background: #86efac;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 广播按钮 */
|
||||
.btn-broadcast {
|
||||
padding: 12px;
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-broadcast:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 12px rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
|
||||
/* 通信日志 */
|
||||
.comm-log {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.log-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.log-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.log-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-item.main .log-msg {
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-item.child .log-msg {
|
||||
color: #16a34a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-item.relay .log-msg {
|
||||
color: #f59e0b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-empty {
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user