大屏嵌入到其他系统
把积木大屏以 iframe 形式嵌进任意系统,宿主和大屏之间双向联动——宿主点按钮可以改大屏图表的数据。
一、能解决什么问题
- 已经做好的大屏,挂到运营平台、管理后台、门户首页里展示;
- 大屏图表和宿主 UI 并存(两侧大屏 + 中间宿主操作区,或者任意布局);
- 宿主点按钮 → 改大屏内某个图表的数据(不走接口,直接重渲);
- 不需要复制大屏代码、不需要二次开发——只需要一个 URL + 一段桥代码。
二、快速上手
前置准备:先在大屏设计器里准备好一个大屏,两侧布置图表组件,中间留空给宿主交互区;并把"大屏配置"面板里的背景图去掉、背景色透明度设为 0(详见 §三 规则 1、规则 2)。示例代码就是按下面这个大屏跑的。
第 1 步:拼接 iframe URL
http://<大屏地址>/drag/share/view/<pageId>?token=<token>&embedMode=1&channel=<通道名>
| 参数 | 必填 | 说明 |
|---|---|---|
pageId | 是 | 大屏页面 ID(URL path 或 query 上) |
embedMode=1 | 是 | 进入嵌入模式:背景透明 + 启用 postMessage 通信 |
channel | 否 | 通道名,多 iframe 共存时用来区分消息,默认 default |
token | 否 | 透传给后端接口的鉴权 token |
第 2 步:复制两个文件到你的项目
① 桥工具 useBigScreenBridge.js
import { ref, onBeforeUnmount } from 'vue';
const PROTOCOL = 'bigscreen-bridge';
const VERSION = 1;
export function useBigScreenBridge(iframeRef, options = {}) {
const channel = options.channel ?? 'default';
const ready = ref(false);
const components = ref([]);
const designSize = ref(null);
const handlers = new Map();
function on(type, fn) {
if (!handlers.has(type)) handlers.set(type, new Set());
handlers.get(type).add(fn);
return () => handlers.get(type)?.delete(fn);
}
function onMessage(e) {
const msg = e.data;
if (!msg || msg.protocol !== PROTOCOL) return;
if (msg.channel !== channel) return;
if (msg.source !== 'bridge') return;
if (msg.type === 'bridge:ready') {
const p = msg.payload || {};
designSize.value = p.designSize || null;
components.value = Array.isArray(p.components) ? p.components : [];
ready.value = true;
}
handlers.get(msg.type)?.forEach((fn) => fn(msg.payload, msg));
handlers.get('*')?.forEach((fn) => fn(msg.payload, msg));
}
function send(type, payload) {
const win = iframeRef.value?.contentWindow;
if (!win) return;
win.postMessage({ protocol: PROTOCOL, version: VERSION, channel, source: 'host', type, payload, ts: Date.now() }, '*');
}
window.addEventListener('message', onMessage);
onBeforeUnmount(() => {
window.removeEventListener('message', onMessage);
handlers.clear();
});
return {
ready,
components,
designSize,
on,
send,
sendComponentUpdate: (componentId, data) => send('host:component-update', { componentId, data }),
};
}
② 宿主页面 index.vue —— 直接可跑的完整示例
<template>
<div class="root">
<!-- 第 1 层:背景 -->
<div class="bg-layer"></div>
<!-- 第 2 层:大屏 iframe -->
<iframe ref="iframeRef" class="bigscreen-iframe" :src="iframeSrc" allowtransparency="true" frameborder="0"></iframe>
<!-- 第 3 层:宿主交互层 -->
<div class="host-overlay">
<main class="host-main">
<section class="panel">
<div class="panel-title">控制大屏(host:component-update → queryData)</div>
<div class="panel-body">
<div v-if="!components.length" class="empty">等大屏 ready 后这里会出现组件按钮...</div>
<div v-for="c in components" :key="c.id" class="comp-row">
<span class="comp-type">{{ c.type }}</span>
<span class="comp-id">#{{ c.id.slice(0, 8) }}</span>
<button class="btn" @click="pushSampleData(c)">塞示例数据</button>
<button class="btn ghost" @click="resetData(c)">重新查询</button>
</div>
</div>
</section>
</main>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useBigScreenBridge } from './useBigScreenBridge';
const iframeRef = ref();
const CHANNEL = 'demo';
// 改成你的大屏地址 + pageId
const iframeSrc = computed(() => `http://127.0.0.1:8000/?pageType=view&embedMode=1&channel=${CHANNEL}&pageId=xxxx`);
const { components, on, sendComponentUpdate } = useBigScreenBridge(iframeRef, { channel: CHANNEL });
on('bridge:ready', (p) => console.log('大屏 ready,组件数', p?.components?.length));
on('bridge:click', (p) => console.log('大屏被点击', p));
on('bridge:component-updated', (p) => console.log('数据更新回执', p));
function pushSampleData(c) {
// 注意:data 是 [{ name, value }] 形态,所有图表通用
sendComponentUpdate(c.id, [
{ name: '苹果', value: 1000 },
{ name: '三星', value: 5400 },
{ name: '小米', value: 800 },
]);
}
function resetData(c) {
sendComponentUpdate(c.id, null); // 传 null 回到原数据集
}
</script>
<style lang="less" scoped>
.root { position: relative; width: 100%; height: 100%; overflow: hidden; }
/* z=0 背景 */
.bg-layer { position: absolute; inset: 0; z-index: 0; background: #0c1326; }
/* z=1 大屏 iframe(背景透明) */
.bigscreen-iframe { position: absolute; inset: 0; width: 100%; height: 100%; background: transparent; z-index: 1; border: 0; }
/* z=2 宿主交互层:容器自身 PE:none,子元素 PE:auto */
.host-overlay {
position: absolute; inset: 0; z-index: 2;
pointer-events: none;
display: grid; grid-template-columns: 26% 1fr 26%;
}
.host-main { grid-column: 2 / 3; padding: 16px; }
.panel {
pointer-events: auto;
background: rgba(7, 16, 32, 0.82);
border: 1px solid rgba(110, 180, 255, 0.22);
border-radius: 6px; color: #e6f0ff;
}
.panel-title { padding: 8px 12px; font-size: 13px; font-weight: 600; border-bottom: 1px solid rgba(110, 180, 255, 0.18); }
.panel-body { padding: 10px 12px; }
.comp-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 12px; }
.comp-type { color: #91d5ff; font-weight: 600; }
.comp-id { opacity: 0.55; font-family: monospace; flex: 1; }
.btn {
border: 1px solid rgba(110, 180, 255, 0.3);
background: rgba(24, 144, 255, 0.2);
color: #fff; border-radius: 3px; padding: 3px 10px; cursor: pointer;
&:hover { background: #1890ff; }
&.ghost { background: transparent; }
}
.empty { font-size: 12px; opacity: 0.6; }
</style>
第 3 步:看效果
把 pageId 改成你的大屏 ID,访问宿主页面,效果如下:
三、三条必须知道的规则
规则 1:大屏画布尺寸要和 iframe 可见区域一致
大屏在设计器里设置的画布尺寸(
width × height,如 1920×1080)必须与 iframe 可见区域尺寸一致。否则大屏会被等比缩放或裁剪,导致组件位置错位、字体大小不对、宿主上层 UI 的避让区对不齐。最稳的做法:宿主把 iframe 固定到某个尺寸的容器里,并按这个尺寸创建大屏。
规则 2:大屏自身背景要透明
设计大屏时,在「大屏配置」面板里:
- 去掉背景图(不设置背景图片)
- 把背景色的透明度(A)设为 0
否则即使 iframe 用了 embedMode=1 让 html/body 透明,大屏自身的背景色/图还会盖在宿主背景之上,宿主元素就透不出来了。

规则 3:宿主用哪个 id 改图表
sendComponentUpdate(id, data) 的 id 必须是大屏内图表的唯一 id。两种拿法:
运行时拿(推荐) —— 桥工具暴露的 components 数组里就有:
const { components } = useBigScreenBridge(iframeRef, { channel: CHANNEL });
// 大屏 ready 后
components.value.forEach((c) => console.log(c.id, c.type, c.name));
调试时对照 —— 浏览器开发者工具进"元素"面板,选中图表,看 root 元素的 id 属性:

id 是 UUID 形态(如
2ef97458-7493-41c5-ba4a-f741bbed82c4)。示例 UI 上只显示了前 8 位,调 API 时必须用完整 id。
四、API 速查
宿主能收到的事件(bridge → host)
| 事件 | payload | 时机 |
|---|---|---|
bridge:ready | { pageId, designSize, components[] } | 大屏数据加载完毕、组件挂载后 |
bridge:click | { componentId, dataItem } | 用户点击大屏内某组件 |
bridge:component-updated | { componentId, ok, error? } | 对 host:component-update 的回执 |
components[] 每项:{ id: string, type: string, name: string }
宿主能调用的方法(host → bridge)
| 方法 | 协议事件 | 说明 |
|---|---|---|
sendComponentUpdate(id, data) | host:component-update | 核心:把数据塞给某个图表,不走接口直接重渲。data 形态:[{ name, value }];传 null = 回到原数据集 |
send(type, payload) | 自定义 | 业务扩展用,自己定义 type |
底层消息帧格式
{
protocol: 'bigscreen-bridge', // 协议名(固定)
version: 1,
channel: 'default', // 与 URL 参数对齐
source: 'host' | 'bridge',
type: 'bridge:ready', // 或其他事件
payload: any,
ts: 1717000000000,
}
接收侧的过滤逻辑:
if (msg?.protocol !== 'bigscreen-bridge') return;
if (msg.channel !== ownChannel) return;
if (msg.source === ownSide) return; // 避免回环
五、它是怎么工作的(原理)
三层 DOM 分层
┌─────────────────────────────────────┐
│ z=0 宿主背景(图片 / 装饰) │ ← 不接收事件
│ z=1 大屏 iframe(透明背景) │ ← 大屏图表可见可点
│ z=2 宿主交互层(按钮 / 卡片) │ ← 容器 PE:none,子元素 PE:auto
└─────────────────────────────────────┘
- iframe 加
embedMode=1→ 大屏自动把 html/body 背景设为透明; - z=2 容器自身
pointer-events: none,子元素再auto—— 避免大面积盖住下层 iframe; - 宿主上层 UI 摆放时避开大屏图表位置(按布局而非裁剪);
- 不用
clip-path、不用动态切pointer-events,纯 z-index + 透明 iframe。
sendComponentUpdate 的执行链路
sendComponentUpdate(id, data)
↓ postMessage
host:component-update
↓ 大屏端
container.$refs[prefix + id].queryData({ overrideData: data })
↓
useDataSource.queryData 命中 overrideData 分支
↓
initOptions(overrideData) → 图表 setOption → 立即重渲
所有走 useDataSource 的图表(柱状图 / 饼图 / 折线图 / 动态柱 / 3D 柱 / 双折线柱 / 雷达 / 漏斗 / …)零改动自动支持。
六、常见问题
iframe 整张盖住下层宿主元素,看不到背景
检查 iframe src 是否带了 embedMode=1 参数。没有就不会触发背景透明。
收到 bridge:ready 但 components 是空数组
channel两端是否一致(URL 上和useBigScreenBridge选项里);pageId是否正确,能否单独打开大屏看到图表。
调 sendComponentUpdate 后图表没反应
看事件日志里 bridge:component-updated 的回执:
error | 原因 | 解决 |
|---|---|---|
no-instance | 大屏侧按 id 没找到图表实例 | 检查 id 是否来自 bridge:ready.components[].id;图表可能还没 mount 完,稍后再试 |
no-queryData | 该图表组件没有 queryData 方法 | 该图表未走 useDataSource(少数特殊组件),需手动适配 |
图表位置错位 / 字体大小不对 / 宿主避让区对不齐
大屏画布尺寸和 iframe 可见区域尺寸不一致——见 §三 规则 1。
多个大屏 iframe 同时嵌入
每个 iframe 用不同的 channel,宿主对应 useBigScreenBridge 也用对应的 channel,消息按 channel 隔离不会串。
想把宿主 UI 摆在大屏图表的位置上面(覆盖图表)
直接放在 .host-overlay 内即可,z=2 总是盖住 z=1。记得给这个元素加 pointer-events: auto,不然事件会穿透。
七、生产部署的安全建议
- 把
postMessage的targetOrigin由'*'改为大屏真实域名; - 宿主接收
message事件时校验event.origin,配白名单; overrideData不要包含可执行字符串,仅传 JSON 可序列化的纯数据。