文档首页 / 宿主事件与方法调用

宿主事件与方法调用

嵌入报表与宿主页面之间的 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 与 print 只表示「已受理」。导出失败只在报表页面上提示,不会发 report:error。

提示:宿主页面使用 Vue 组件(而非 iframe)直接嵌入报表视图时,事件通过组件的 report-event 事件全量触发,不需要订阅。

相关文档

联系我们

请填写您的信息,我们将在 1 个工作日内与您联系。