Skip to content

给 AI 的接入说明

这一页是写给代码助手的:把整页贴进上下文,或让它抓 https://report.doc.yijiangsoft.com/md/ai.md,它就能在宿主代码里把接入写对。内容自包含,不用再翻别的页面。

人也能读——只是密度高、没有铺垫。

纯文本索引:/llms.txt 全站正文:/llms-full.txt 本页原文:/md/ai.md


TASK

把 conch-report 的查看态页面<iframe> 嵌进宿主系统的页面。宿主不引任何包、不改构建;所有输入通过 URL 查询串传入,运行期交互通过 postMessage

INPUTS(动手前必须确认,缺了就问人,不要编)

名称例子没有会怎样
报表服务地址 ORIGINhttps://report.example.com写不出 src
报表 id IDr260922123456写不出 src
筛选条件 id 及取值来源month ← 页面上的月份选择器;dept ← 登录用户的科室条件传不进去或拼错名字
哪些条件不让使用者改dept界面口径不对
要不要报表自带页头宿主已有标题栏 → 不要页面上出现两条标题栏

报表 id 从报表服务的报表列表页(每行的「嵌入」按钮)或 GET /api/reports 取。

HARD RULES

  1. 只嵌查看态view=1 必须有。少了它嵌进去的是设计器(带对话和属性面板),是错的。
  2. 报表必须已发布。没发布的报表打开只显示一句「还没有发布」;发布由做报表的人在报表服务里点,宿主侧做不了。
  3. 不要在链接里放 token、密码、签名、SQL。链接会进历史记录和 Referer。报表服务也不接受从客户端传 SQL。
  4. lock 不是权限。它只把控件设成只读,改链接就能绕开。按身份限制数据必须在报表服务侧用行级范围参数做,不能靠 lock 交差;写代码时如果用户以为 lock 能限权,要明确告诉他不能。
  5. 条件变化走 postMessage,不要重新拼 src。换 src 会让 iframe 整页重载、图表重画一遍。首次挂载用 URL,之后用 setParams
  6. postMessagetargetOrigin 写具体域名,不要 '*';接收侧校验 e.originmsg.source
  7. 不要替用户猜条件 id。猜错的表现是「页面上多一条提示、筛选没生效」,比报错更难被发现。

URL CONTRACT

{ORIGIN}/?id={ID}&view=1[&<条件id>=<值>][&lock=…][&chrome=0][&version=…][&theme=…]

保留字段(其余 key 一律按报表筛选条件解析):

key含义
id报表 id必填
view1必填,查看态
versionv3 / draft / 省略省略=当前发布版;draft 仅供发布前自查,不要给客户
locka,ball这些条件只读
chrome0去掉报表页头(标题、版本、刷新、导出)
themeprimary:%232c6ecb,bg:%23ffffff覆盖设计令牌;名字省略 --cr- 前缀;值里不能有逗号

条件值的写法(先 encodeURIComponent,用 URLSearchParams 就自动做了):

条件类型写法
文本 / 数字 / 日期 / 月份 / 单选 / 树id=值month=2026-08day=2026-08-31store=S001
日期区间id=起,止id_from=/id_to=range=2026-01-01,2026-03-31
数字区间id=小,大id_min=/id_max=amount=100,500
多选id=a,b 或重复 id=a&id=bstore=S001,S002
不限(清掉报表默认值)id=(空串)month=

链接上不写的条件 = 用报表自己的默认值。写了但报表里没有的 key 会被忽略,页面顶上列出来提示。

POSTMESSAGE CONTRACT

报表 → 宿主,{ source: 'conch-report', type, … }

type附带字段用途
readyid, title, version渲染完成;此前发给报表的消息会被丢掉
heightheight(px)内容高度变化;宿主据此调 iframe 高度,避免第二条滚动条
paramsparams(全部条件的当前值)使用者或宿主改了条件

宿主 → 报表,{ source: 'conch-report-host', type, … }

type附带字段用途
setParamsparams:条件 id → 值(只写要改的)改条件并重新取数
refresh按当前条件重新取数
setThemetokens:令牌名 → 值换配色

setParams 的值用 JS 原生类型:区间 ['2026-01-01','2026-03-31']、多选 ['S001','S002']、不限 null

REFERENCE IMPLEMENTATION(框架无关,照这个结构写)

js
function mountReport(container, { origin, id, params = {}, lock, chrome = true, version }) {
  const q = new URLSearchParams({ id, view: '1' })
  for (const [k, v] of Object.entries(params)) {
    if (v !== null && v !== undefined) q.set(k, Array.isArray(v) ? v.join(',') : String(v))
  }
  if (lock) q.set('lock', Array.isArray(lock) ? lock.join(',') : lock)
  if (chrome === false) q.set('chrome', '0')
  if (version) q.set('version', version)

  const frame = document.createElement('iframe')
  frame.src = `${origin}/?${q}`
  frame.style.cssText = 'width:100%;height:480px;border:0'
  container.appendChild(frame)

  let ready = false
  const queue = []
  const post = (msg) => {
    if (!ready) { queue.push(msg); return }            // ready 之前排队,别丢
    frame.contentWindow.postMessage({ source: 'conch-report-host', ...msg }, origin)
  }
  const onMessage = (e) => {
    if (e.origin !== origin || e.source !== frame.contentWindow) return
    const msg = e.data
    if (!msg || msg.source !== 'conch-report') return
    if (msg.type === 'height') frame.style.height = msg.height + 'px'
    if (msg.type === 'ready') { ready = true; queue.splice(0).forEach(post) }
  }
  window.addEventListener('message', onMessage)

  return {
    setParams: (p) => post({ type: 'setParams', params: p }),
    refresh: () => post({ type: 'refresh' }),
    setTheme: (t) => post({ type: 'setTheme', tokens: t }),
    destroy: () => { window.removeEventListener('message', onMessage); frame.remove() },
  }
}

Vue 3 / React 的成品组件见宿主代码示例(原文 /md/guide/examples.md),结构与上面一致:src 只在挂载时算一次,条件变化 watch/useEffect 里发 setParamsheight 存进组件状态。

HTTP API(只有在不嵌 iframe、要自己画的时候才用)

方法路径请求体回什么
GET/api/reports报表列表,publishedVersion 为 null 表示没发布
GET/api/reports/{id}/view?version=规格(不含 SQL)+ 初始条件 +(快照版的)数据;409 = 没发布
POST/api/reports/{id}/data{version, params}{queries:{<查询名>:{columns,columnTypes,rows,truncated,elapsedMs}}},单条失败带 error
POST/api/reports/{id}/drill{version, drill, args, params}穿透明细
POST/api/reports/{id}/export/xlsx|docx{version, params[, drill, args]}文件流
GET/api/reports/{id}/versions版本列表

错误统一 {"error":"…"}。多租户部署时租户来自请求头 X-Conch-Tenant,由反向代理注入,前端传不了。

CHECKLIST(交付前逐条核对)

  • [ ] src 里有 idview=1
  • [ ] 条件 id 是从报表里确认过的,不是猜的
  • [ ] 条件值用 URLSearchParams 拼,没有手工字符串拼接
  • [ ] 监听了 height 并调整 iframe 高度
  • [ ] 校验了 e.originmsg.sourcepostMessage 的 targetOrigin 不是 '*'
  • [ ] ready 之前发的消息有排队补发
  • [ ] 条件变化走 setParams,没有重拼 src
  • [ ] 组件卸载时移除了 message 监听
  • [ ] 宿主是 https 时报表地址也是 https
  • [ ] 跟用户确认过:lock 不等于数据权限

FAILURE MODES

现象原因处理
页面写「还没有发布」报表只有草稿让做报表的人发布;自查可用 version=draft
iframe 空白混合内容 / frame-ancestors 不含宿主域名 / 代理加了 X-Frame-Options / sandbox 太紧看浏览器控制台的拒绝原因,对号处理;sandbox 至少要 allow-scripts allow-same-origin,导出还要 allow-downloads
条件没生效,顶上有提示条件 id 拼错核对报表筛选栏里的 id
条件没生效,没有提示值的写法不对(区间、多选、编码)按上面的写法表改
出现第二条滚动条没监听 height加上高度同步
setParams 无反应发早了 / source 写错 / targetOrigin 不匹配 / 该条件被 lock 或快照锁了按顺序排查
报表改了页面没变只改了草稿没发布,或链接钉了 version重新发布;去掉 version

GLOSSARY

  • 查看态:只有筛选栏和画布的只读页面,view=1,宿主嵌的就是它。
  • 设计器:做报表的界面,带对话面板和属性面板,不要嵌给最终用户。
  • 发布版 / 草稿:嵌出去的链接跟发布版走;草稿是设计者正在改的那份。
  • 数据快照:发布时固化了数据的版本,打开不查库,条件只读。
  • 穿透明细:点图表或指标卡弹出的明细层,查看态里点了就有,宿主不用管。
  • 设计令牌--cr-* 开头的 CSS 变量,主题只换值不改样式源码。

给 AI 用的纯文本索引在 /llms.txt,每篇原文在 /md/ 下。