宿主事件与方法调用
嵌入报表与宿主页面之间的 postMessage 协议:接收加载、错误、查询、单元格点击等事件,向报表发送 invoke 调用参数、刷新、导出。
概述
报表嵌入 iframe 后,可以和宿主页面双向通信:
- 事件(报表到宿主):报表加载完成、出错、查询、导出,以及设计者在报表里配置的自定义事件(点击单元格、点击图表数据点等)。
- 方法调用(宿主到报表):宿主向 iframe 发送
invoke消息,改变查询参数、重新查询、读取状态、导出等。
所有消息都通过 window.postMessage 传递,消息体中带 protocol: 'sight-report'。宿主接收时必须校验 event.origin(报表系统的部署地址)和 data.protocol,避免误收页面上其他组件的消息。
功能入口
- 自定义事件的配置位置(在设计器中):
- 网格报表:单元格 > 链接 > 发送事件。
- 单据:组件 > 定位与操作 > 点击链接。
- 仪表盘:区块 > 点击数据点 / 点整行 / 按列配置 > 发送事件到宿主页面,见 仪表盘的联动与钻取。
- 「三方集成」页面的「接入说明」弹窗中有「接收报表事件」代码示例。
事件(报表到宿主)
消息格式
{
"protocol": "sight-report",
"name": "report:loaded",
"payload": { "elapsedMs": 812 },
"source": { "reportId": "rpt_sales" },
"context": { "parameters": { "year": 2025 } },
"timestamp": 1790658966000
}
| 字段 | 说明 |
|---|---|
protocol |
固定为 sight-report |
name |
事件名。系统事件以 report: 开头,自定义事件为设计者填写的名称 |
payload |
事件数据 |
source |
事件来源定位,reportId 必有;网格有 cellName、cid;单据有 componentId、componentType、rowIndex;仪表盘有 blockId、blockType、tabId。按事件名分发后只读需要的字段即可 |
context |
仅自定义事件带:当前生效的查询参数 { parameters }(仪表盘为过滤栏的生效值,含日期范围展开与联动叠加) |
timestamp |
毫秒时间戳 |
系统事件
| 事件名 | 发送条件 | payload |
|---|---|---|
report:loaded |
首次加载渲染完成,只发一次;直接发送 | { elapsedMs };仪表盘在首屏区块取数完成时才发,并多带 blockCount、failedCount |
report:error |
加载、查询失败;直接发送 | { phase: 'load' | 'query', message } |
report:query |
发起查询;需订阅 | { parameters };仪表盘另带 trigger: 'user' | 'auto' |
report:query-done |
每次查询渲染完成(含首次);需订阅 | { parameters, elapsedMs };仪表盘另带 trigger: 'user' | 'auto'(定时刷新也会发)和 failedCount |
report:export |
用户触发导出;需订阅 | { format },取值 excel、pdf、word、csv、ofd |
report:print |
用户触发打印;需订阅 | { command } |
report:resize |
内容高度变化,用于 iframe 高度自适应;需订阅 | { height } |
report:print-progress、report:print-continued、report:print-cancelled |
续打相关;需订阅 | 续打状态与位置 |
「直接发送」的事件(report:loaded、report:error 和自定义事件)宿主监听即可收到;「需订阅」的过程性事件需要先向 iframe 发一条 subscribe 消息。
订阅过程性事件
const iframe = document.getElementById('report');
iframe.contentWindow.postMessage(
{ protocol: 'sight-report', type: 'subscribe', events: ['report:query-done', 'report:resize'] },
'https://your-domain'
);
// 订阅全部:events: ['*']
订阅同时会锁定宿主 origin,此后报表发送的事件使用精确的 origin,安全性更高。未订阅时,报表用 document.referrer 的 origin 作为目标;referrer 也为空时以 * 发送。
接收事件
window.addEventListener('message', (event) => {
if (event.origin !== 'https://your-domain') return; // 报表系统的部署地址
const data = event.data;
if (!data || data.protocol !== 'sight-report') return;
if (data.name === 'report:loaded') {
console.log('报表已就绪', data.payload);
} else if (data.name === 'patient-selected') { // 设计者配置的自定义事件
console.log('选中患者', data.payload, '当前参数', data.context && data.context.parameters);
}
});
注意:自定义事件名不能以
report:开头,该前缀保留给系统事件;使用了该前缀的事件在运行时会被丢弃。
方法调用(宿主到报表)
消息格式
请求:
{ "protocol": "sight-report", "type": "invoke", "id": "req-1", "method": "setParameters", "args": [{ "year": 2025 }] }
响应(回送给发起方窗口):
{ "protocol": "sight-report", "type": "invoke-result", "id": "req-1", "ok": true, "data": { "queried": true, "success": true } }
失败时为 { ..., "ok": false, "error": "..." }。id 必须是非空字符串,用于匹配请求与响应。invoke 只在报表运行于 iframe 内时响应,直接打开报表页不响应。
方法清单
| 方法 | 参数 | 说明 |
|---|---|---|
setParameters |
(params, { query }) |
合并查询参数;query 不为 false 时立即重新查询并等待完成 |
getParameters |
(names?, { raw }) |
读取参数;names 省略则返回全部;raw 仅对仪表盘有区别(true 取控件原始值,缺省取含日期展开与联动叠加的生效值) |
query |
无 | 重新查询 |
reset |
无 | 重置查询条件 |
export |
(format) |
发起导出,受理即返回 { accepted: true },没有完成信号 |
print |
(command?) |
发起打印,受理即返回;仪表盘不支持 |
getState |
无 | 读取状态,含 reportId、fileType、loading 等;网格另含 parameters、variables、currentPage、totalPages、activeSheetId;仪表盘另含 activeTabId、parameters、crossFilter、lastUpdatedAt |
setSheet |
(sheetId) |
切换多 sheet 报表的页签;仪表盘不支持 |
getCellValue |
(cellName) |
读取首个同名单元格渲染后的显示值,返回 { cid, text },取不到返回错误;仅网格支持 |
getCellValues |
(cellName) |
读取同名单元格的全部实例,返回 { cid, text } 数组;仅网格支持 |
getTabs、setTab |
(tabId) |
仪表盘页签;仅仪表盘支持 |
getPrintProgress、continuePrint |
(options?) |
续打进度与续打;仅网格支持 |
在报表首次加载完成前,setParameters、query、reset、export、print、setSheet、continuePrint 返回 error: 'not-ready',宿主应等 report:loaded 后重试;读取类方法始终放行,getState().loading 可作为就绪探测。不存在的方法返回 unknown method: xxx;当前报表类型不支持的方法返回 unsupported: …,不会静默成功。
示例
页面加载后按用户所在科室过滤报表,并在查询完成后自适应 iframe 高度:
const iframe = document.getElementById('report');
const ORIGIN = 'https://your-domain';
let seq = 0;
function invoke(method, ...args) {
return new Promise((resolve, reject) => {
const id = 'req-' + (++seq);
const onMsg = (e) => {
const d = e.data;
if (e.origin !== ORIGIN || !d || d.protocol !== 'sight-report'
|| d.type !== 'invoke-result' || d.id !== id) return;
window.removeEventListener('message', onMsg);
d.ok ? resolve(d.data) : reject(new Error(d.error));
};
window.addEventListener('message', onMsg);
iframe.contentWindow.postMessage({ protocol: 'sight-report', type: 'invoke', id, method, args }, ORIGIN);
});
}
window.addEventListener('message', async (e) => {
if (e.origin !== ORIGIN || !e.data || e.data.protocol !== 'sight-report') return;
if (e.data.name === 'report:loaded') {
iframe.contentWindow.postMessage(
{ protocol: 'sight-report', type: 'subscribe', events: ['report:resize'] }, ORIGIN);
await invoke('setParameters', { dept: '内科' });
} else if (e.data.name === 'report:resize') {
iframe.style.height = e.data.payload.height + 'px';
}
});
注意事项
注意:一个 iframe 对应一张报表。报表内部钻取出来的子报表实例不参与宿主通道,宿主只会收到主报表的事件,也只有主报表响应
invoke。
提示:
export与report:error。
提示:宿主页面使用 Vue 组件(而非 iframe)直接嵌入报表视图时,事件通过组件的
report-event事件全量触发,不需要订阅。