# conch-report 接入文档(全站正文) 来源:https://report.doc.yijiangsoft.com 生成时间:2026-09-23 # 给 AI 的接入说明 这一页是写给**代码助手**的:把整页贴进上下文,或让它抓 `https://report.doc.yijiangsoft.com/md/ai.md`,它就能在宿主代码里把接入写对。内容自包含,不用再翻别的页面。 人也能读——只是密度高、没有铺垫。 > 纯文本索引:[`/llms.txt`](/llms.txt) 全站正文:[`/llms-full.txt`](/llms-full.txt) 本页原文:[`/md/ai.md`](https://report.doc.yijiangsoft.com/md/ai.md) --- ## TASK 把 conch-report 的**查看态页面**用 ` ``` 到这一步页面上就能看到报表了。`height` 先写个固定值,第四步再让它自适应。 ## 第三步:把筛选条件喂给报表 报表里的每个筛选条件都有一个 id(月份 `month`、门店 `store`、科室 `dept`……),直接写进链接: ```html ``` 打开就是 2026 年 8 月、S001 这家店的数。 宿主指定的口径不想让看的人改,加 `lock`: ``` &month=2026-08&lock=month ``` 被锁住的条件显示出来但改不了。更多写法看[链接参数](/guide/url)。 **条件 id 要对得上** 链接里写了、报表里没有的参数名会被忽略,页面顶上会列出来提醒你。看到提示就去核对报表筛选栏里的条件 id。 ## 第四步:让 iframe 高度跟着内容走 报表页会把自己的内容高度发给宿主页面,宿主照着调 iframe 高度,页面里就不会出现第二条滚动条。 ```html ``` 生产上把判断收紧成只认报表服务的域名: ```js if (e.origin !== 'https://report.example.com') return ``` ## 完整示例 一个能直接保存成 `.html` 打开的最小宿主页面: ```html 经营看板

经营看板

``` `chrome=0` 去掉了报表自带的页头(标题、刷新、导出),页头由宿主自己做。 ## 接下来 - 链接还能带什么:[链接参数](/guide/url) - 宿主怎么跟报表互相发消息:[与宿主页面通信](/guide/postmessage) - 换成你们系统的配色:[外观与主题](/guide/theme) - Vue / React / 后台框架里怎么写:[宿主代码示例](/guide/examples) - 上线前要配的东西:[部署与安全](/guide/security) --- # 链接参数 查看态的链接就是接口。除了下面这几个保留字段,链接上其余的 key **一律当成报表的筛选条件**。 ``` https://report.example.com/?id=<报表id>&view=1[&其它参数] ``` ## 保留字段 | 参数 | 必填 | 说明 | |---|---|---| | `id` | 是 | 报表 id,形如 `r260922123456` | | `view` | 是 | 写 `1` 表示查看态;不写就是设计器 | | `version` | 否 | 看哪一版,默认当前发布版。见下方[版本](#版本) | | `lock` | 否 | 哪些筛选条件只显示不让改,逗号分隔;写 `all` 锁住全部 | | `chrome` | 否 | 写 `0` 去掉报表自带的页头(标题、版本、刷新、导出) | | `theme` | 否 | 覆盖设计令牌,见[外观与主题](/guide/theme) | ## 筛选条件 条件的 id 在报表里定义(做报表的人能在属性面板看到),宿主按 id 传值。 | 条件类型 | 链接写法 | 例子 | |---|---|---| | 文本 | `id=值` | `&keyword=%E6%89%8B%E6%9C%AF` | | 数字 | `id=值` | `&min_qty=100` | | 日期 | `id=YYYY-MM-DD` | `&day=2026-08-31` | | 月份 | `id=YYYY-MM` | `&month=2026-08` | | 日期区间 | `id=起,止` 或 `id_from=起&id_to=止` | `&range=2026-01-01,2026-03-31` | | 数字区间 | `id=小,大` 或 `id_min=小&id_max=大` | `&amount=100,500` | | 单选、树形下拉 | `id=值` | `&store=S001` | | 多选下拉 | `id=a,b` 或重复写 `id=a&id=b` | `&store=S001,S002` | 几条规则: - **值要做 URL 编码**。中文、空格、`&`、`#` 不编码会被浏览器截断,`encodeURIComponent()` 一下就行。 - **写空串表示「不限」**:`&month=` 会把报表里这个条件的默认值清掉,查全部。 - **链接上不写的条件用报表自己的默认值**,不会被清空。 - **区间只写一头也行**:`&range_from=2026-01-01` 就是「这天以后」。 - **数字位置写了非数字**(`&min_qty=abc`)按「不限」处理,不会报错也不会查出错数。 ### 写错了会怎样 链接里写了、报表里没有这个条件 id,页面顶上会列出来: > 链接里的 `storeid` 不是这张报表的筛选条件,已忽略。处理:核对链接里的参数名与报表筛选栏一致。 报表照常显示,只是那个条件没生效。看到这句就去核对 id 的拼写。 ## 锁住条件 宿主指定的口径不该让看的人改——比如按登录用户所在门店限定数据: ``` &store=S001&lock=store ``` `store` 显示出来但是灰的,改不了。多个用逗号:`&lock=store,dept`。全部锁死:`&lock=all`。 **lock 是界面口径,不是数据权限** `lock` 只是把控件设成只读。**它挡不住会改链接的人**——把 `lock` 去掉、把 `store` 换个值,照样能看到别的门店。 真正的数据边界在服务端:只读账号、SQL 守卫,以及按登录身份下发的行级范围参数。要做到「这个人只能看到自己门店」,得走行级范围那条路,不是靠 `lock`。 ## 版本 | 写法 | 看到的内容 | |---|---| | 不写 | 当前发布版。报表重新发布,这条链接自动跟到新版本 | | `version=v2` | 钉死在第 2 版,之后再发布也不变 | | `version=draft` | 当前草稿。发布前自查用,**不要发给客户** | **数据快照版**(发布时选了「数据快照」)打开时不查数据库,数字和筛选条件都是发布那一刻固化下来的:条件一律只读,页头没有「刷新数据」,页面上会写明「这一版是数据快照」。 ## 一条完整的链接 ``` https://report.example.com/?id=r260922123456&view=1 &month=2026-08 &store=S001,S002 &lock=store &chrome=0 &theme=primary:%232c6ecb ``` (实际使用写成一行,不要换行。) 意思是:打开这张报表的当前发布版,看 2026 年 8 月、S001 与 S002 两家店,门店条件不让改,不要报表自带的页头,主色换成 `#2c6ecb`。 ## 拼链接的代码 ```js function reportUrl(base, id, params = {}, opts = {}) { const q = new URLSearchParams({ id, view: '1' }) for (const [key, value] of Object.entries(params)) { if (value === null || value === undefined) continue q.set(key, Array.isArray(value) ? value.join(',') : String(value)) } if (opts.lock) q.set('lock', Array.isArray(opts.lock) ? opts.lock.join(',') : opts.lock) if (opts.chrome === false) q.set('chrome', '0') if (opts.version) q.set('version', opts.version) if (opts.theme) q.set('theme', Object.entries(opts.theme).map(([k, v]) => `${k}:${v}`).join(',')) return `${base}/?${q}` } // reportUrl('https://report.example.com', 'r260922123456', // { month: '2026-08', store: ['S001', 'S002'] }, { lock: 'store', chrome: false }) ``` `URLSearchParams` 自己会做编码,不用再手工转义。 --- # 与宿主页面通信 链接参数管「打开时是什么样」,`postMessage` 管「打开之后要变」——改筛选条件、重新取数、换主题,都不用刷新 iframe。 所有消息都带 `source` 标记:报表发出的是 `conch-report`,宿主发来的是 `conch-report-host`。两边都按这个标记过滤,页面上别的库发的消息不会被当成指令。 ## 报表 → 宿主 ```js window.addEventListener('message', (e) => { if (e.origin !== 'https://report.example.com') return const msg = e.data if (!msg || msg.source !== 'conch-report') return // ... }) ``` | `type` | 什么时候发 | 带什么 | |---|---|---| | `ready` | 报表打开并渲染完 | `id` 报表 id、`title` 报表名、`version` 看的是哪一版 | | `height` | 内容高度变了(首次渲染、切筛选条件、窗口变宽变窄) | `height` 内容高度,单位 px | | `params` | 筛选条件变了(人在筛选栏上改的,或宿主发 `setParams` 改的) | `params` 当前全部条件的值 | ```js // 报表发出来的消息长这样 { source: 'conch-report', type: 'ready', id: 'r260922123456', title: '门店经营看板', version: 'v3' } { source: 'conch-report', type: 'height', height: 872 } { source: 'conch-report', type: 'params', params: { month: '2026-08', store: ['S001'] } } ``` 三件典型的事: - 收到 `height` 就调 iframe 高度,页面里不会出现第二条滚动条。 - 收到 `ready` 再把加载动画去掉,或者记一笔埋点。 - 收到 `params` 把条件写回宿主自己的地址栏,用户刷新页面还能回到同一个筛选状态。 ## 宿主 → 报表 ```js frame.contentWindow.postMessage( { source: 'conch-report-host', type: 'setParams', params: { month: '2026-07' } }, 'https://report.example.com', // 生产上写具体域名,别用 '*' ) ``` | `type` | 作用 | 带什么 | |---|---|---| | `setParams` | 改筛选条件,改完自动重新取数 | `params`:条件 id → 值,只写要改的那几个 | | `refresh` | 按当前条件重新取一次数 | 无 | | `setTheme` | 换设计令牌 | `tokens`:令牌名 → 值,见[外观与主题](/guide/theme) | 值的写法和链接参数一致,只是这里用真正的 JS 类型,不用拼字符串: ```js { month: '2026-08' } // 月份 { range: ['2026-01-01', '2026-03-31'] } // 日期区间:[起, 止] { amount: [100, 500] } // 数字区间:[小, 大] { store: ['S001', 'S002'] } // 多选 { keyword: null } // null = 不限 ``` **发早了会丢** iframe 还没加载完时发的消息没人接。等 `ready` 到了再发,或者在 `iframe.onload` 之后发。 ## 两边接起来的样子 ```js const REPORT_ORIGIN = 'https://report.example.com' const frame = document.getElementById('report') let ready = false const pending = [] window.addEventListener('message', (e) => { if (e.origin !== REPORT_ORIGIN) 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; pending.splice(0).forEach(send) } if (msg.type === 'params') syncToAddressBar(msg.params) }) function send(msg) { if (!ready) { pending.push(msg); return } frame.contentWindow.postMessage({ source: 'conch-report-host', ...msg }, REPORT_ORIGIN) } // 宿主自己的筛选器联动报表 document.getElementById('month').addEventListener('change', (e) => { send({ type: 'setParams', params: { month: e.target.value } }) }) ``` ## 用不上 postMessage 的场合 - **小程序 web-view**:不支持这套消息,只能用链接参数,条件要变就换 `src`。 - **`sandbox` 属性写死的 iframe**:加了 `sandbox` 就必须带上 `allow-scripts allow-same-origin`,否则报表里的脚本跑不起来,消息也发不出来。要用导出功能再加 `allow-downloads`。 --- # 外观与主题 报表的颜色、圆角、字体全部走 CSS 变量(设计令牌)。宿主**只换值,不改样式源码**——这样报表升级不会把你们改过的样式冲掉。 ## 换法一:写在链接上 ``` &theme=primary:%232c6ecb,bg:%23ffffff ``` - 写成 `名:值`,多个用逗号隔开。 - 名字可以省掉 `--cr-` 前缀:`primary` 等于 `--cr-primary`。 - `#` 在 URL 里要编码成 `%23`。 - **值里不能有逗号**(逗号是分隔符)。所以链接上用 `#2c6ecb`,不要用 `rgb(44,110,203)`。 ## 换法二:发消息 值里要带逗号、或者主题要跟着宿主的深浅色切换,用这个: ```js frame.contentWindow.postMessage({ source: 'conch-report-host', type: 'setTheme', tokens: { primary: '#2c6ecb', bg: 'rgb(248, 249, 251)', '--cr-radius': '4px', }, }, 'https://report.example.com') ``` ## 常用的令牌 改前四个就能让报表看起来像你们的系统,其余按需要挑。 | 令牌 | 管什么 | 默认值 | |---|---|---| | `primary` | 主色:按钮、选中态、链接 | `#2c6ecb` | | `bg` | 页面底色 | `#f5f6f8` | | `surface` | 卡片、组件底色 | `#ffffff` | | `text` | 正文字色 | `#1d2330` | | `primary-hover` / `primary-active` | 主色的悬浮态、按下态 | 深一档、再深一档 | | `primary-soft` / `primary-border` | 主色的浅底、浅描边 | `#eaf1fc` / `#b9d0f0` | | `surface-sunken` / `surface-hover` | 次级底色、悬浮底色 | `#fafbfc` / `#f2f4f7` | | `border` / `border-strong` / `border-subtle` | 三档描边 | `#e3e6ea` / `#cfd4da` / `#eceff2` | | `text-secondary` / `text-muted` | 次要文字、弱化文字 | `#4b5563` / `#8a919e` | | `success` / `warning` / `danger` | 语义色 | 绿 / 黄 / 红 | | `trend-up` / `trend-down` | 涨跌色(中文语境默认涨绿跌红,要互换就改这两个) | `#2f7d55` / `#b8453c` | | `chart-1` … `chart-8` | 图表分类色板,按顺序取 | 低饱和八色 | | `chart-seq-1` … `chart-seq-6` | 连续量色阶(热力图一类) | 单色阶 | | `chart-axis` / `chart-grid` / `chart-label` | 坐标轴、网格线、轴标签 | 浅灰系 | | `font-sans` / `font-mono` | 字体族 | 系统中文字体栈 | | `radius-sm` / `radius` / `radius-lg` | 圆角三档 | `3px` / `6px` / `10px` | | `font-size-md` / `font-size-lg` | 正文字号 | `13px` / `14px` | **图表颜色也跟着走** 图表用的是同一套令牌,不需要单独配。改 `chart-1` 到 `chart-8` 就能让图表配色统一到你们的视觉规范上。 ## 页头要不要 `&chrome=0` 去掉报表自带的页头(报表名、版本标记、刷新数据、导出)。适合宿主自己已经有标题栏和工具条的场合。 去掉之后这些能力也跟着没了: - **刷新数据** → 宿主发 `{ type: 'refresh' }` 代替。 - **导出 Excel / Word / PDF** → 留着页头,或者由宿主调[导出接口](/reference/api#导出)。 ## 窄屏与手机 宽度小于等于 760px 时,报表自动从 12 栏栅格换成**整列堆叠**,顺序按组件在画布上从上到下、从左到右排。宿主侧栏里嵌、手机 web-view 里打开都能看。 组件的高度按它在设计时占的行数留,图表不会被压扁。 ## 打印和存 PDF 报表页头、提示条这些在打印时自动隐藏,打印出来的就是画布本身。让使用者用浏览器打印(或在报表页头的「导出」里选「导出 PDF」,走的也是浏览器打印)。 --- # 宿主代码示例 四种宿主形态的可用代码,复制过去改掉 `REPORT_ORIGIN` 就能跑。 ## 原生 HTML 一个可复用的小封装,不依赖任何框架: ```html
``` ## Vue 3 `ConchReport.vue`: ```vue