1. 页面概览

1.1 是什么

智能报表页(报表引擎)是 LightFoundry 在 V5 Stage 3(B3-2)交付的分析协作闭环能力,对标 OpenFoundry report-service 的「模板 + 调度 + 分发」三段式模型。页面把「报表定义 → 立即/定时生成快照 → 按渠道分发 → 历史回放」串成一条完整链路:

数据落库三张表:fr_reports(报表定义)、fr_report_runs(运行快照)、fr_notifications(internal 渠道站内通知)。块引用 metric 时自动记录血缘(RecordMetricToReport),分发与审计形成闭环。

1.2 核心价值

维度说明
三段式语义模板(报表块)→ 生成(快照)→ 分发(email/internal/feishu),对标 OpenFoundry report-service
三类块chart(图表引用)+ text(markdown 正文)+ table(取数表格,OOL/指标/图表三源)
双形态快照每次运行落 markdown + HTML 双渲染,供预览、归档与二次加工
调度自动化cron 定时生成(+分发),实现日报 / 周报自动产出
多渠道分发email(SMTP 邮件)、internal(站内通知落表 + 审计)、feishu(无引擎时降级标注)
血缘与审计块引用 metric 打血缘;internal 分发落 fr_notifications + 审计,全程可回放

1.3 一句话总结

在 Foundry 里「定义一张多块报表,立即或定时生成快照,再按渠道分发出去」——报表、快照、通知三段落库,历史随时可回放预览。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

页面采用左右两栏布局(rp-layout,左侧 240px 固定,右侧自适应):

┌───────────────┬──────────────────────────────────────────────────┐
│ 左栏:报表列表  │ 右栏:当前报表                                    │
│ ① 新建输入+新建 │ ② 报表定义卡片(名称/描述/调度/渠道/收件人+操作)  │
│ ③ 报表列表     │ ④ 报表块编辑器(chart/text/table 块 + 添加按钮)   │
│   (on/off)    │ ⑤ 运行历史卡片(表格 + 预览按钮)                 │
│                │ ⑥ 快照预览抽屉(Markdown/HTML/分发结果)           │
└───────────────┴──────────────────────────────────────────────────┘

各板块职责:

未选择报表时右栏显示占位文案「从左侧选择或新建一个报表开始。」

4. 交互元素详解

4.1 报表定义字段

字段位置含义必填 / 默认值操作效果
名称 *报表定义卡片报表名称必填空名触发「立即生成 / 生成并分发」时前端提示「请先填写报表名称」
描述名称右侧报表说明非必填description(text 列)
调度第 2 行左cron 表达式(如 0 9 * * *非必填,留空不定时保存时后端按新 schedule 重注册调度任务
渠道第 2 行右逗号分隔:email / feishu / internal非必填保存时按逗号拆分、去空格、去空项
收件人第 3 行逗号分隔:邮箱地址或 user_id非必填同上拆分;email 用邮箱,internal 用 user_id

提交到后端时这些字段名分别为:namedescriptionschedulechannels(数组)、recipients(数组)。

4.2 报表操作按钮

按钮含义操作效果与触发调用
保存保存报表定义updateReport(id, toApiPayload(form));提示「报表已保存(调度/渠道变更即时生效)」并刷新列表
停用 / 启用切换 enabledupdateReport(id, {enabled: !form.enabled});成功提示「报表已停用」/「报表已启用」,左栏徽标切换 on/off
立即生成仅生成快照,不分发先保存编辑内容 → 调 runReport(id, false) → 自动加载快照抽屉
生成并分发生成快照并按渠道分发先保存编辑内容 → 调 runReport(id, true) → 后端 Dispatch → 加载快照抽屉查看分发结果

「立即生成」与「生成并分发」在生成前都会先调 updateReport 把当前编辑内容(调度 / 渠道 / 块配置)同步到后端,保证「以最新编辑为准」。

4.3 报表块编辑器

每块包含:类型徽标(bt-badge)、标题输入(block.title,占位「块标题」)、类型下拉(chart / text / table)、「移除」按钮(removeBlock(idx) 就地 splice)。

块类型参数区说明
chartref_chart_id *(数字)+ metric_name(血缘打点,可选)绑定仪表盘图表,运行时经 ChartDataProvider 取数;metric_name 填写后快照生成时调 RecordMetricToReport 打血缘
textcontent textarea(占位「markdown 正文」)+ 模板占位符提示与「预览替换结果」markdown 正文直取,快照直接内嵌;模板占位符由前端在生成快照前替换(见 4.3b)
table取数源三选一:ref_chart_id / metric_name / object_queryobject_query(OOL 对象查询)含 aggregate(sum/avg/count/min/max)、metric_fieldobject_type_iddimensions(逗号分隔)

object_query 仅在 block.object_query 已存在时展示(table 块新建时默认带 {aggregate:'sum', metric_field:'', object_type_id:0, dimensionsText:'', filters:[]})。提交时 normalizeBlock 会过滤空值:ref_chart_id / metric_name / content 非空才带,dimensions 由逗号拆分后非空才带,filters 非空数组才带。

4.3b text 块模板占位符(前端替换)

口径结论(读代码确认):后端不渲染占位符foundry/report/service.gofetchBlockBlockText 仅执行 br.Content = b.Content(markdown 正文直取),全包无模板变量替换逻辑(仅 table 单元格做了 |/换行转义)。因此占位符替换由前端完成,而非后端。

支持变量(花括号 {{name}} 记法,仅这几个内置运行时变量):

占位符含义取值
{{date}}当天日期YYYY-MM-DD(本地时区)
{{datetime}}当前时间YYYY-MM-DD HH:mm:ss(本地时区)
{{report_name}}报表名称form.name(即「名称 *」输入框)

行为与语义:

4.4 添加块按钮

按钮效果
+ chart 块addBlock('chart'){type, title:'', ref_chart_id:null, metric_name:''}
+ text 块addBlock('text'){type, title:'', content:'本节正文'}
+ table 块addBlock('table'){type, title:'', ref_chart_id:null, metric_name:'', object_query:{...}}

4.5 运行历史表格

含义
时间run.created_atfmtTime 格式化)
状态success(绿 status-active)/ failed / partial(红 status-error 徽标统一用 'status-' + (run.status === 'success' ? 'active' : 'error')
块数(run.snapshot && run.snapshot.blocks ? run.snapshot.blocks : []).length
摘要run.error(失败/降级摘要,红字单行省略)
操作「预览」按钮 → previewRun(run)getReportSnapshot(run.id) 打开快照抽屉

4.6 快照预览抽屉

Tab内容
Markdown<pre class="snapshot-md">{{ snapshot.markdown || '(空)' }}</pre>
HTML 渲染<div class="snapshot-html" v-html="snapshot.html || '<p>(空)</p>'"></div>
分发结果无分发显示「尚未分发」;否则 plain-table 逐渠道展示(渠道 / 状态 / 说明)

抽屉头部显示 run <id> 与快照 note(若有);「关闭」按钮或点击遮罩关闭。生成(立即生成 / 生成并分发)后会自动打开抽屉并定位到 Markdown Tab。

5. 后端关联

5.1 API 客户端

5.2 端点表(前缀 /api/v1,均挂 protected)

方法路径请求体 / 参数超时用途
GET/reports60s报表列表
POST/reports{name, description, blocks, schedule, channels, recipients}60s创建报表
GET/reports/:id60s报表详情
PUT/reports/:id指针语义(部分字段)+ 调度重注册60s更新报表
DELETE/reports/:id60s删除报表(级联 runs/notifications + 调度反注册)
POST/reports/:id/run{dispatch?: bool}120s生成快照(可选分发)
POST/reports/:id/dispatch{run_id?, channels?, recipients?}120s对最近/指定 run 按渠道分发
GET/reports/:id/runs?limit=查询参数 limit60s运行历史(快照摘要)
GET/report-runs/:id/snapshot60s单次运行快照(markdown/html/blocks)

5.3 响应结构(JSON 示例)

报表详情 GET /reports/:id(data 即 Report):

{ "code": 0, "data": {
  "id": "...", "rid": "report://foundry/...", "name": "运营日报",
  "description": "每日销售概览", "enabled": true, "owner": "550e8400-...",
  "schedule": "0 9 * * *",
  "channels": ["email", "internal"], "recipients": ["u1@example.com", "u2"],
  "blocks": [
    { "type": "text", "title": "摘要", "content": "本节正文" },
    { "type": "chart", "title": "销售趋势", "ref_chart_id": 12, "metric_name": "order_total" },
    { "type": "table", "title": "TOP 客户", "ref_chart_id": 13 }
  ],
  "created_at": "...", "updated_at": "..."
} }

运行 POST /reports/:id/run(dispatch=true 时 data 含 run + dispatch):

{ "code": 0, "data": {
  "run": { "id": "run-uuid", "report_id": "...", "status": "success",
           "error": "", "created_at": "...", "finished_at": "..." },
  "dispatch": [
    { "channel": "email", "status": "delivered", "note": "" },
    { "channel": "internal", "status": "delivered", "note": "" }
  ]
} }

快照 GET /report-runs/:id/snapshot

{ "code": 0, "data": {
  "run": { "id": "run-uuid", "report_id": "...", "status": "partial", "error": "feishu: 未配置渠道" },
  "snapshot": {
    "markdown": "# 运营日报\n\n## 摘要\n本节正文\n\n| 客户 | 金额 |\n|---|--:|\n| 张三 | 1200 |",
    "html": "<h1>运营日报</h1>...",
    "blocks": [ { "title": "摘要", "type": "text", "content": "本节正文" },
                { "title": "销售趋势", "type": "chart", "columns": ["date", "amount"], "rows": [["2026-08-29", 1200]] } ],
    "dispatch": [ { "channel": "email", "status": "delivered", "note": "" } ]
  }
} }

5.4 关联模块

后端包 / 表职责
foundry/report/models.goReport(fr_reports)、ReportRun(fr_report_runs)、ReportNotification(fr_notifications)、SnapshotDispatchItemOOLQuery
foundry/report/rest.goREST handler:reports CRUD + run / dispatch / runs / snapshot
foundry/report/service.go业务服务:GenerateSnapshotDispatchListRuns、调度注册
foundry/dashboardchart 块取数源 ChartDataProviderdashboard.GetChartData
foundry/semantictable 块 OOL 取数(semantic.TranslateOOL),经 reportOOLExecutor 适配器执行
foundry/metrictable 块指标取数 MetricProvidermetric.QueryMetric
platform/maileremail 渠道 MailSender 接口;foundry/serverfoundryMailer 注入
platform/schedulersched.Register("report:"+rid, schedule, ...) 定时生成 + 分发
platform/auditinternal 分发 + 血缘记录(SetAudit / SetLineage 注入)

5.5 关键机制

6. 核心流程详解

6.1 新建报表主流程

  1. 左栏输入新报表名称(或直接回车)→ 点「新建」。
  2. handleCreate:构造 emptyForm()(enabled=true、blocks=[]),blocks = [newBlock('text')] 默认带一个 text 块(content「本节正文」),调 createReport(toApiPayload(f))
  3. 成功提示「报表已创建」、清空输入、刷新列表,并 openReport(data.data) 拉详情填充表单与运行历史。

6.2 立即生成快照流程

  1. 打开报表,确认名称非空,点「立即生成」(handleGenerate(false))。
  2. 先调 updateReport(id, toApiPayload(form, {resolveTemplates:true})) 保存最新编辑(text 块占位符在此替换为运行时值)→ 调 runReport(id, false)
  3. 后端 GenerateSnapshot:逐块取数(chart 经 ChartDataProvider、table 经 OOL/指标、text 直取)→ 组装 markdown + HTML 快照 → 落 fr_report_runs
  4. 前端提示「快照已生成」,loadRuns() 刷新历史,随后 getReportSnapshot(run.id) 自动打开快照抽屉(Markdown Tab)。
  5. 生成流程 finally 中再调 updateReport(id, toApiPayload(form)) 把含占位符的原始定义写回(还原模板,见 4.3b)。

6.3 生成并分发流程

  1. 点「生成并分发」(handleGenerate(true))→ 先保存 → 调 runReport(id, true)
  2. 后端生成快照后,读取报表 channels / recipientsDispatch:email 走 mailer、internal 落 fr_notifications + 审计、feishu 无引擎时降级标注。
  3. 返回 {run, dispatch[]};前端提示「快照已生成并分发」,加载快照抽屉,可在「分发结果」Tab 查看各渠道状态(delivered / degraded / failed / skipped)。

6.4 快照回放流程

  1. 运行历史中点「预览」(或生成后自动打开)→ getReportSnapshot(run.id)
  2. 抽屉内三个 Tab 切换查看:Markdown(原始 markdown 文本)/ HTML 渲染(v-html 渲染)/ 分发结果(各渠道状态表)。
  3. 点「关闭」或点击遮罩收起;snapshot 置空。

6.5 调度定时流程

  1. 在「调度(cron 表达式,留空不定时)」填 0 9 * * *,点「保存」。
  2. 后端 sched.Register("report:"+rid, schedule, ...):到点自动 GenerateSnapshot,并按报表渠道分发。
  3. 定时生成的运行同样落 fr_report_runs,可在运行历史回放;enabled=false 或删除报表后调度停止/反注册。

7. 权限与安全

8. 常见问题与排错

8.1 生成快照报错 / 状态 failed

8.2 分发结果显示 degraded / failed

8.3 快照预览空白

8.4 保存后调度不生效

8.5 无法用「生成并分发」以外的方式单独分发

9. 已知缺陷与边界

项目说明
feishu 渠道降级Foundry 无飞书引擎,feishu 渠道未配 FeishuSender 时运行状态为 partial、分发结果为 degraded(仅标注);feishu 完整能力仅在 AIP
notify Hub 进程内站内通知 Hub 为进程内实例,跨产品不共享
text 块占位符前端替换后端 text 块直取 content不渲染占位符;模板变量替换由前端完成,内置变量仅 {{date}}/{{datetime}}/{{report_name}}。替换在生成快照前落库、生成后还原模板;不支持自定义变量、条件/循环等高级模板语法,未命中变量原样保留
快照数据量快照内嵌完整 blocks 结果(columns+rows),大报表会增大 fr_report_runs.snapshot 体积
调度精度cron 定时依赖 platform/scheduler 进程存活;重启后调度随服务启动重新注册

注:无独立分发入口已于 2026-09-06 修复(历史运行行新增「再分发」按钮,按 run_id 对该次运行重新分发)。

注:前端无删除报表入口已于 2026-09-06 修复(列表项新增「删除」按钮,confirm 确认后调 DELETE /reports/:id 并刷新列表)。